• Tidak ada hasil yang ditemukan

Circular and Two-Way Linked Lists

Dalam dokumen Pelajari tentang Advanced topics in Java (Halaman 106-110)

Linked Lists

3.16 Circular and Two-Way Linked Lists

So far, our discussion has been primarily about one-way (singly linked) lists. Each node contains one pointer that tells us the location of the next item. The last node has a null pointer, indicating the end of the list. While this is the most commonly used type of list, two common variations are the circular list and the two-way (or doubly linked) list.

3.16.1 Circular Lists

In a circular list, we let the last item point back to the first, as follows:

Now, there is no null pointer to tell us when we have reached the end of the list, so we must be careful in traversing that we do not end up in an infinite loop. In other words, say we were to write something like this:

Node curr = top;

while (curr != null) {

//do something with node pointed to by curr curr = curr.next;

top

This loop will never terminate since curr never becomes null. To avoid this problem, we can save the pointer of our starting node and recognize when we have returned to this node. Here’s an example:

Node curr = top;

do {

//do something with node pointed to by curr curr = curr.next;

} while (curr != top) {

Alert readers will observe that since the body of a do...while loop is executed at least once, we should ensure that the list is not empty before going into the loop and trying to dereference a null pointer.

Circular lists are useful for representing situations that are, well, circular. For example, in a card or board game in which players take turns, we can represent the order of play using a circular list. If there are four players, they will play in the order 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, and so on. After the last person plays, it’s the turn of the first.

In the children’s game count-out, the children are arranged in a circle and some variation of “eenie, meenie, mynie, mo; sorry, child, you’ve got to go” is used to eliminate one child at a time. The last remaining child wins the game.

We will write a program that uses a circular list to find the winner of the game described as follows:

The count-out game: n children (numbered 1 to n) are arranged in a circle. A sentence consisting of m words is used to eliminate one child at a time until one child is left. Starting at child 1, the children are counted from 1 to m and the mth child is eliminated. Starting with the child after the one just eliminated, the children are again counted from 1 to m and the mth child is eliminated. This is repeated until one child is left. Counting is done circularly, and eliminated children are not counted. Write a program to read values for n and m (> 0), play the game as described, and print the number of the last remaining child.

It is possible to use an array (child, say) to solve this problem. To declare the array, we would need to know the maximum number (max, say) of children to cater for. We could set child[1] to child[n] to 1 to indicate that all n children are initially in the game. When a child (h, say) is eliminated, we would set child[h] to 0 and start counting out from the next child still in the game.

As the game progresses, several entries in child will be set to 0, and when we count, we must ensure that 0s are not counted. In other words, even when a child has been eliminated, we must still inspect the array item and skip it if 0. As more children are eliminated, we will need to inspect and skip more zero entries. This is the main disadvantage of using an array to solve this problem.

We can write a more efficient solution using a circular linked list. First, we create the list with n nodes. The value at each node is the child’s number. For n = 4, the list will look like the following, assuming curr points to the first child:

Suppose m = 5. We start counting from 1; when we reach 4, the count of 5 takes us back to child 1, which is eliminated. The list will look like this:

4 curr

1 2 3

4 curr

1 2 3

As shown, child 1 is no longer in the list; the storage for this node would be reclaimed eventually by Java. We count to 5 again, starting from child 2. The count ends at child 3, which is eliminated by setting child 2’s pointer to point to child 4. The list will look like this:

Finally, we count to 5 starting at child 4. The count ends at child 4, which is eliminated. Child 2 is the winner.

Note that this solution (as opposed to the array version) really does eliminate a child from the game by deleting its node. Eliminated children are neither inspected nor counted since they are gone! This is more in keeping with the way the game is played.

Program P3.7 plays the game and finds the winner using a linked list representation. We keep the solution simple and faithful to the description of the game. As such, we do not use the LinkedList class. Rather, we use a Node class with two fields: an int to hold the number of a child and a pointer to the next child.

After getting the number of children and the length of the count-out, the program calls linkCircular to create a circular linked list of the children and then playGame to eliminate all but one of the children.

Program P3.7

import java.util.*;

public class CountOut {

public static void main(String[] args) { Scanner in = new Scanner(System.in);

int m, n;

do {

System.out.printf("Enter number of children and length of count-out: ");

n = in.nextInt();

m = in.nextInt();

} while (n < 1 || m < 1);

Node last = linkCircular(n); //link children in a circular list Node winner = playGame(last, n-1, m); //eliminate n-1 children System.out.printf("The winning child: %d\n", winner.num);

} //end main

public static Node linkCircular(int n) { //link n children in a circular list;

//return pointer to last child; this will point to the first Node first, np;

first = np = new Node(1); //first child for (int h = 2; h <= n; h++) { //link the others np.next = new Node(h);

np = np.next;

}

np.next = first; //set last child to point to first return np;

} //end linkCircular

4 curr

2

public static Node playGame(Node last, int x, int m) { //Eliminate x children with countout length of m;

//last points to the last child which points to the first child Node prev = last, curr = last.next; //curr points to first child //eliminate x children

for (int h = 1; h <= x; h++) {

//curr is pointing at the first child to be counted;

//count m-1 more to get to the mth child for (int c = 1; c < m; c++) {

prev = curr;

curr = curr.next;

}

//delete the mth child prev.next = curr.next;

curr = prev.next; //set curr to the child after the one eliminated }

return curr;

} //end playGame } //end class CountOut class Node {

int num;

Node next;

public Node(int n) { num = n;

next = null;

}

} //end class Node

The following is a sample run of Program P3.7:

Enter number of children and length of count-out: 9 10 The winning child: 8

3.16.2 Two-Way (Doubly Linked) Lists

As the name implies, each node will contain two pointers; one points to the next node, and the other points to the previous node. While this requires more work to implement and maintain, there are some advantages.

The obvious one is that it is now possible to traverse the list in both directions, starting from either end.

If required, reversing the list is now a simple operation.

If we land at a node (the current node) in a singly linked list, there is no way to get to (or know) the previous node unless that information was kept as the list was traversed. With a doubly linked list, we have a direct pointer to the previous node so we can move in either direction.

One possible disadvantage is that more storage is required for the extra link. Another is that adding and deleting nodes is more complicated since more pointers have to be set.

EXERCISES 3

1. Write an instance method in the LinkedList

class that returns

true

if the list is sorted in ascending order and

false

otherwise.

2. Write an instance method to reverse the nodes of a linked list by creating a new list. The

method returns the newly created list.

3. Write a method to sort a linked list of integers as follows:

(a) Find the largest value in the list.

(b) Delete it from its position and insert it at the head of the list.

(c) Starting from what is now the second element, repeat (a) and (b).

(d) Starting from what is now the third element, repeat (a) and (b).

Continue until the list is sorted.

4. Write a function that takes three arguments—a pointer to a linked list of integers and two

integers

n

and

j

—and inserts

n

after the

j

th element of the list. If

j

is

0

,

n

is inserted at the head of the list. If

j

is greater than the number of elements in the list,

n

is inserted after the last one.

5. The characters of a string are held on a linked list, one character per node.

(a) Write a method that, given a pointer to a string and two characters,

c1

and

c2

, replaces all occurrences of

c1

with

c2

.

(b) Write a function that, given a pointer to a string and a character,

c

, deletes all occurrences of

c

from the string. Return a pointer to the modified string.

(c) Write a function that creates a new list consisting of the letters only in the given list, all converted to lowercase and stored in alphabetical order. Return a pointer to the new list.

(d) Write a function that, given pointers to two strings, returns true if the first is a substring of the other and false otherwise.

6. Write a function that, given an integer n

, converts

n

to binary and stores each bit in one node

Dalam dokumen Pelajari tentang Advanced topics in Java (Halaman 106-110)