Chapter 07
Chapter 07
In [1]:
import copy
Exercises
R-7.2
Describe a good algorithm for concatenating two singly linked lists L and M, given only reference to the
first node of each list, into a single list L′ that contains all the nodes of L followed by all nodes of M.
Answer
In [2]:
class Empty(Exception):
pass
In [3]:
class LinkedStack:
"""LIFO Stack implementatino using a singly linked list for storage"""
class _Node:
__slots__ = '_element', '_next'
def __init__(self):
self._head = None
self._size = 0
def __len__(self):
return self._size
def __iter__(self):
cur = self._head
while cur is not None:
yield cur._element
cur = cur._next
def is_empty(self):
return self._size == 0
def top(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._head._element
def pop(self):
if self.is_empty():
raise Empty('Stack is empty')
answer = self._head._element
self._head = self._head._next
self._size -= 1
return answer
In [4]:
L = LinkedStack()
In [5]:
L.push(5)
L.push(4)
L.push(3)
In [6]:
[i for i in L]
Out[6]:
[3, 4, 5]
In [7]:
M = LinkedStack()
M.push(3)
M.push(2)
M.push(1)
In [8]:
[i for i in M]
Out[8]:
[1, 2, 3]
Making a new LinkedStack is less efficient (n pop & n push for each list) than just making the base list's
last node to designate the head of the list to be appended (n references for traversing).
In [9]:
def concat_singly_linked_stack(base, append):
base = copy.deepcopy(base)
append = copy.deepcopy(append)
base_last = None
cur = base._head
while True: # Need to traverse whole list since LinkedStack does not
keep its last node.
if cur._next is None:
base_last = cur
break
cur = cur._next
base_last._next = append._head
base._size += append._size
return base
In [10]:
concat_list = concat_singly_linked_stack(L, M)
In [11]:
[i for i in concat_list]
Out[11]:
[3, 4, 5, 1, 2, 3]
R-7.3
Describe a recursive algorithm that counts the number of nodes in a singly linked list.
Answer
In [12]:
def recursive_count(list_head):
"""
list_head: head reference to a list
"""
if list_head._next is None:
return 1
else:
return 1 + recursive_count(list_head._next)
In [13]:
recursive_count(concat_list._head)
Out[13]:
6
R-7.7
Our CircularQueue class provides a rotate() method that has semantics equivalent
to Q.enqueue(Q.dequeue()), for a nonempty queue. Implement such a method for the LinkedQueue class
without the creation of any new nodes.
Answer
In [14]:
class LinkedQueue:
class _Node:
__slots__ = '_element', '_next'
def __init__(self):
self._head = None
self._tail = None
self._size = 0
def __len__(self):
return self._size
def __iter__(self):
cur = self._head
while cur is not None:
yield cur._element
cur = cur._next
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty():
raise Empty("Queue is empty")
return self._head._element
def dequeue(self):
if self.is_empty():
raise Empty("Queue is empty")
answer = self._head._element
self._head = self._head._next
self._size -= 1
if self.is_empty():
self._tail = None
return answer
rotate method in LinkedQueue should dequeue the first element and insert it back into its tail by enqueue
In [15]:
def rotate(self):
if self._size > 0:
old_head = self._head
self._head = old_head._next
self._tail._next = old_head
old_head._next = None
R-7.13
Update the PositionalList class to support an additional method find(e), which returns the position of the
(first occurrence of )element e in the list (or None if not found).
In [19]:
class _DoublyLinkedBase:
"""A base calss providing a doubly linked list representation."""
class _Node:
__slots__ = '_element', '_prev', '_next'
def __init__(self):
self._header = self._Node(None, None, None)
self._trailer = self._Node(None, None, None)
self._header._next = self._trailer
self._trailer._prev = self._header
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
class Position:
"""An abstraction representing the location of a single element."""
def element(self):
return self._node._element
def first(self):
return self._make_position(self._header._next)
def last(self):
return self._make_position(self._trailer._prev)
def __iter__(self):
cursor = self.first()
while cursor is not None:
yield cursor.element()
cursor = self.after(cursor)
In [21]:
def find(self, e):
cur = self.first()
while True:
if cur is None:
return None
if cur.element() == e:
return cur
cur = self.after(cur)
R-7.14
Repeat the previous process using recursion. Your method should not contain any loops. How much space
does your method use in addition to the space used for L?
In [31]:
def find_recursive(self, cur_pos, e):
if cur_pos is None:
return None
elif cur_pos.element() == e:
return cur_pos
else:
cur_pos = self.after(cur_pos)
return find_recursive(self, cur_pos, e)
C-7.24
Give a complete implementation of the stack ADT using a singly linked list that includes a header sentinel.
In [38]:
class LinkedStackwithSentinel:
"""LIFO Stack implementatino using a singly linked list for storage"""
class _Node:
__slots__ = '_element', '_next'
def __init__(self):
self._header = self._Node(None, None)
self._header._next = None
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def top(self):
if self.is_empty():
raise Empty('Stack is empty')
return self._header._next._element
def pop(self):
if self.is_empty():
raise Empty('Stack is empty')
pop_node = self._header._next
self._header._next = pop_node._next
answer = pop_node._element
pop_node = None
self._size -= 1
return answer
In [39]:
x = LinkedStackwithSentinel()
In [40]:
x.push(5)
x.push(3)
x.push(1)
In [41]:
x.top()
Out[41]:
1
In [42]:
print(x.pop())
print(x.pop())
print(x.pop())
1
3
5
In [43]:
try:
x.pop()
except Empty:
print("proper error")
proper error
C-7.25
Give a complete implementation of the queue ADT using a singly linked list that includes a header
sentinel.
In [44]:
class LinkedQueuewithSentinel:
class _Node:
__slots__ = '_element', '_next'
def __init__(self, element, nxt):
self._element = element
self._next = nxt
def __init__(self):
self._header = self._Node(None, None)
self._header._next = None
self._tail = None
self._size = 0
def __len__(self):
return self._size
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty():
raise Empty("Queue is empty")
return self._header._next._element
def dequeue(self):
if self.is_empty():
raise Empty("Queue is empty")
dequeue_node = self._header._next
self._header._next = dequeue_node._next
answer = dequeue_node._element
dequeue_node = None
self._size -= 1
if self.is_empty():
self._tail = None
return answer
In [45]:
x = LinkedQueuewithSentinel()
In [46]:
x.enqueue(5)
x.enqueue(4)
x.enqueue(3)
In [47]:
x.first()
Out[47]:
5
In [48]:
x.dequeue()
Out[48]:
5
In [49]:
x.dequeue()
Out[49]:
4
In [50]:
x.enqueue(10)
In [51]:
x.dequeue()
Out[51]:
3
In [52]:
x.first()
Out[52]:
10
In [53]:
x.dequeue()
Out[53]:
10
In [54]:
try:
x.dequeue()
except Empty:
print("proper error")
proper error