Data Structures Intro2CS – week 11 1
Stack ADT (Abstract Data Type) A container with 3 basic actions: – push(item) – pop() – is_empty() Semantics: – Push inserts an item into the stack – Pop will remove and return an item (if the stack isn’t empty). The item that is returned: the last item inserted (that was not already removed) – Is_empty will return “True” if every item that has been inserted, was also removed. “False” otherwise LIFO (last-in-first-out) 2
Example 3
A simple implementation 4
Using stacks Stacks are useful data structures. They can be used to solve interesting algorithmic problems. Example that you’ve encountered: Matching brackets. Suppose we want to check if an expression has well balanced brackets. – Brackets may be of several types. [ [ ( ) ] ] {( )} balanced [ } not balanced [(]) not balanced [()not balanced 5
Some helper functions: The main idea: Opening brackets are placed in the stack, waiting to be “closed”. Closing brackets are matched to the last opened ones that weren’t closed (at the top of the stack) 6
7
Linked Lists 8
Python lists in memory Python lists are represented as continuous blocks in memory. If we want to make the block size larger, we have to re-allocate memory and copy the contents. Many times Python does this for us behind the scenes 9 HELLOWORLD HELLOWORL
Linked Lists Have many blocks instead of one Chain them: remember address of the next one Now we can easily add blocks 10 H L L EO W
Linked Lists 11 next data next data next data next data head None
How to work with linked lists To access the data in the linked list: But this is cumbersome. 12 head.data head.next.data head.next.next.data … next data next data next data next data head None
How to work with linked lists Instead, we usually work with a moving reference 13 cur = head print(cur.data) cur = cur.next print(cur.data) … next data next data next data next data head None cur
Iterating over lists So in order to print a linked list: 14 cur = head while cur!=None: print(cur.data) cur = cur.next
Inserting into a list Inserting before the head: 15 next data next data next data next data head None next data new_node = Node("new val", head) head = new_node Or simply write: head = Node(“new val”, head)
Inserting into a list Inserting at position cur: 16 next data next data next data next data head None next data cur.next = Node(“some val”,cur.next) cur Notice that insertion takes O(1), while in python lists (arrays) it takes O(n)
Removing from a list Removing the first item: 17 next data next data next data next data head None head = head.next
Removing from a list Removing the node after position cur: What if we want to remove the node at position cur? 18 next data next data next data next data head None cur.next = cur.next.next cur
Some list operations are more costly. How do we get to the node before the current one? How quickly can we get to the k’th node in the list? How quickly can we print the list in reverse? – What if we only have O(1) memory to use? 19
A stack using a linked list 20
Improving the stack class To easily convert the stack into strings: 21
Recursion and linked lists Recursion is often very natural on linked lists. They have a “recursive” structure: If you remove the head of a linked list, you have a shorter list. Example: – Let’s compute the size of our stack recursively. 22
Queues 23
The Queue ADT A container with 3 basic actions: – enqueue(item) – dequeue() – is_empty() Semantics: FIFO (first-in-first-out) – “enqueue” inserts an item – “dequeue” will return an item (if the queue isn’t empty). The item that is returned: the first item inserted (that was not already removed) – Is_empty will return “True” if every item that has been inserted, was also removed. “False” otherwise. 24
Implementing the Queue Insertion requires all items to be “pushed” forward. So enqueue actually runs in O(n) where n is the number of items in the queue. Not efficient. 25
Implementation with a linked list 26 Where new items will be inserted. We will keep track of the size of the list to avoid having to count it every time.
27
Let’s also add an iterator to the Queue class, so that we can run over it with loops. (The iterator here doesn’t remove items) We create the iterator using a generator: 28
General Graphs Nodes (Objects) can have more than one reference. Example: think of implementing a Class “Person” Each Person has a list of friends, which are also of the same “Person” class. A “Social Network” 29 Joe Jill John Jack Jen
Extra – Another Queue Implementation 30
An efficient implementation using Python lists The idea: hold start index & size of current data. Enqueue at end (at location start+size). Dequeue at start. Let index “loop around”. end = (start+size)%len(list) 31 OWORLDHELL start end
32
33