• Tidak ada hasil yang ditemukan

Linked Lists

Dalam dokumen Kent D. Lee Steve Hubbard (Halaman 131-137)

Sequences

4.10 Linked Lists

Sequences can be organized in several different ways. ThePyList sequence was a randomly accessible list. This means that we can access any element of the list in O(1) time to either store or retrieve a value. Appending an item was possible in O(1) time usingamortizedcomplexity analysis, but inserting an item took O(n) time wheren was the number of items after the location where the new item was being inserted.

If a programmer wants to insert a large number of items towards the beginning of a list, a different organization for a sequence might be better suited to their needs.

A linked list is an organization of a list where each item in the list is in a separate node. Linked lists look like the links in a chain. Each link isattached to the next link by a reference that points to the next link in the chain. When working with a linked list, each link in the chain is called aNode. Each node consists of two pieces of information, anitem, which is the data associated with the node, and a link to the next node in the linked list, often callednext. The code in Sect.4.10.1defines the Node class that can be used to create the nodes in a linked list.

118 4 Sequences

4.10.1 The Node Class

1 class Node:

2 def __init__(self,item,next=None):

3 self.item = item

4 self.next = next

5

6 def getItem(self):

7 return self.item

8

9 def getNext(self):

10 return self.next

11

12 def setItem(self, item):

13 self.item = item

14

15 def setNext(self,next):

16 self.next = next

In the Node class there are two pieces of information: theitemis a reference to a value in the list, and thenextreference which points to the next node in the sequence.

Figure4.10is a linked list with three elements added to it. There are four nodes in this figure. The first node is a dummy node. Having one extra dummy node at the beginning of the sequence eliminates a lot of special cases that must be considered when working with the linked list. An empty sequence still has a dummy node. Nodes in linked lists are represented as a rounded rectangle with two halves. The left half of a node is a reference to the item or value for that node in the sequence. The right half is a reference to the next node in the sequence or to the special valueNoneif it is the last node in the sequence.

Figure4.10 depicts a linked list consisting of three pieces of information. We keep a reference to the first node in the sequence so we can traverse the nodes when necessary. The reference to the last node in the list makes it possible to append an item to the list in O(1) time. We also keep track of the number of items so we don’t have to count when someone wants to retrieve the list size.

The table of operations in Fig.4.11 contains the computational complexity of various list operations on the LinkedList datatype presented in this section. Many

Fig. 4.10 A Sample LinkedList Object

Operation Complexity Usage Method List creation O(len(y)) x = LinkedList(y) calls __init__(y)

indexed get O(n) a = x[i] x.__getitem__(i)

indexed set O(n) x[i] = a x.__setitem__(i,a)

concatenate O(n) z = x + y z = x.__add__(y)

append O(1) x.append(a) x.append(a)

insert O(n) x.insert(i,e) x.insert(i,e))

delete O(n) del x[i] x.__delitem__(i)

equality O(n) x == y x.__eq__(y)

iterate O(n) for a in x: x.__iter__()

length O(1) len(x) x.__len__()

membership O(n) a in x x.__contains__(a)

sort N/A N/A N/A

Fig. 4.11 Complexity of LinkedList Operations

of the operations appear to have the same complexity as thelistdatatype operations presented in Fig.4.11. There are some important differences though. The following sections will provide the implementations for some of these operations and point out the differences as compared to thelistdatatype operations given in Fig.4.11.

4.10.2 The LinkedList Constructor

1 class LinkedList:

2

3 # This class is used internally by the LinkedList class. It is

4 # invisible from outside this class due to the two underscores

5 # that precede the class name. Python mangles names so that they

6 # are not recognizable outside the class when two underscores

7 # precede a name but aren’t followed by two underscores at the

8 # end of the name (i.e. an operator name).

9 class __Node:

10 def __init__(self,item,next=None):

11 self.item = item

12 self.next = next

13

14 def getItem(self):

15 return self.item

16

17 def getNext(self):

18 return self.next

19

20 def setItem(self, item):

21 self.item = item

22

23 def setNext(self,next):

24 self.next = next

25

26 def __init__(self,contents=[]):

27 # Here we keep a reference to the first node in the linked list

120 4 Sequences

28 # and the last item in the linked list. They both point to a

29 # dummy node to begin with. This dummy node will always be in

30 # the first position in the list and will never contain an item.

31 # Its purpose is to eliminate special cases in the code below.

32 self.first = LinkedList.__Node(None,None)

33 self.last = self.first

34 self.numItems = 0

35

36 for e in contents:

37 self.append(e)

Creating aLinkedListobject has exactly the same complexity has constructing alist object. If an empty list is created, then the time taken is O(1) and if a list is copied, then it is a O(n) operation. ALinkedListobject has references to both ends of the linked list. The reference to the head of the list points to a dummy node. Having a dummy node at the beginning eliminates many special cases that would exist when the list was empty if no dummy node were used.

Declaring theNodeclass within theLinkedList class, and preceding the name with two underscores, hides the__Nodeclass from any code outside the LinkedList class. The idea here is that only the LinkedList class needs to know about the__Node class. Initially, both thefirstand thelastreferences point to the dummy node. The appendmethod is used to add elements to the LinkedList should a list be passed to the constructor.

4.10.3 LinkedList Get and Set

1 def __getitem__(self,index):

2 if index >= 0 and index < self.numItems:

3 cursor = self.first.getNext()

4 for i in range(index):

5 cursor = cursor.getNext()

6

7 return cursor.getItem()

8

9 raise IndexError("LinkedList index out of range")

10

11 def __setitem__(self,index,val):

12 if index >= 0 and index < self.numItems:

13 cursor = self.first.getNext()

14 for i in range(index):

15 cursor = cursor.getNext()

16

17 cursor.setItem(val)

18 return

19

20 raise IndexError("LinkedList assignment index out of range")

Implementations for the indexed get and set operations are included in Sect.4.10.4 largely as an example of traversing a linked list. They are of little practical value.

If random access to a list is desired, then thelistclass should be used. Linked lists are not randomly accessible. They require linear search through the datatype to access a particular location in the list. Each of these operations is O(n) where n is the value of the index.

4.10.4 LinkedList Concatenate

1 def __add__(self,other):

2 if type(self) != type(other):

3 raise TypeError("Concatenate undefined for " + \

4 str(type(self)) + " + " + str(type(other)))

5

6 result = LinkedList()

7

8 cursor = self.first.getNext()

9

10 while cursor != None:

11 result.append(cursor.getItem())

12 cursor = cursor.getNext()

13

14 cursor = other.first.getNext()

15

16 while cursor != None:

17 result.append(cursor.getItem())

18 cursor = cursor.getNext()

19

20 return result

Concatenation is an accessor method that returns a new list comprised of the two original lists. The operation is once again O(n) for linked lists as it was for the PyList datatype presented in Fig.4.1. In this concatenation code a variable calledcursor is used to step through the nodes of the two lists. This is the common method of stepping through a linked list. Set the cursor to the first element of the linked list.

Then use a while loop that terminates when the cursor reaches the end (i.e. the special valueNone). Each time through the while loop the cursor is advanced by setting it to the next node in the sequence.

Notice in the code that the dummy node from both lists (i.e.self andother) is skipped when concatenating the two lists. The dummy node in the new list was created when the constructor was called.

4.10.5 LinkedList Append

1 def append(self,item):

2 node = LinkedList.__Node(item)

3 self.last.setNext(node)

4 self.last = node

5 self.numItems += 1

The code in Sect.4.10.6is the first time we see a small advantage of a LinkedList over a list. Theappendoperation has a complexity of O(1) for LinkedLists where with lists the complexity was an amortized O(1) complexity. Each append will always take the same amount of time with a LinkedList. A LinkedList is also never bigger than it has to be. However, LinkedLists take up about twice the space of a randomly accessible list since there has to be room for both the reference to the item and the reference to the next node in the list.

122 4 Sequences The code for the append method is quite simple. Since theself.last reference points at the node immediately preceding the place where we want to put the new node, we just create a new node and make the last one point at it. Then we make the new node the newself.lastnode and increment the number of items by 1.

4.10.6 LinkedList Insert

1 def insert(self,index,item):

2 cursor = self.first

3

4 if index < self.numItems:

5 for i in range(index):

6 cursor = cursor.getNext()

7

8 node = LinkedList.__Node(item, cursor.getNext())

9 cursor.setNext(node)

10 self.numItems += 1

11 else:

12 self.append(item)

Theinsertoperation, while it has the same complexity asinserton a list, is quite a bit different for linked lists. Inserting into a list is a O(n) operation wherenis the number of elements that are in the listafterthe insertion point since they must all be moved down to make room for the new item. When working with aLinkedListthe nis the number of elements that appearbeforethe insertion point because we must search for the correct insertion point.

This means that while inserting at the beginning of a list is a O(n) operation, inserting at the beginning of aLinkedListis a O(1) operation. If you will have to do many inserts near the beginning of a list, then a linked list may be better to use than a randomly accessible list.

4.10.7 Other Linked List Operations

The other linked list operations are left as an exercise for the reader. In many cases, the key to working with a linked list is to get a reference to the node that preceds the location you want to work with. For instance, to delete a node from a linked list you merely want to make thenextfield of the preceeding node point to the node following the node you wish to delete. Consider the sample linked list in Fig.4.10.

To delete the second item (i.e. the “b”) from this list we want to remove the node that contains the reference to the “b”. To do this we can use acursorto find the node preceding the one that references the “b”. Once found, the next field of the cursor can be made to point to the nodeafterthe “b” as shown in Fig.4.12.

Changing the next pointer of the cursor’s node to point to the node after the “b”

results in the node containing the “b” dropping out of the list. Since there are no other references to the node containing the “b”, and actually to the “b” itself, the two objects are garbage collected.

Fig. 4.12 Deleting a Node from a Linked List

Finally, thesortoperation is not applicable on linked lists. Efficient sorting algo- rithms require random access to a list. Insertion sort, a O(n2) algorithm, would work, but it would be highly inefficient. If sorting were required, it would be much more efficient to copy the linked list to a randomly accessible list, sort it, and then build a new sorted linked list from the sorted list.

Dalam dokumen Kent D. Lee Steve Hubbard (Halaman 131-137)