1 / 64

Review

Review. ArrayStack ( ArrayList ), [ ArrayDeque , and DualArrayDeque ] implement the List interface using one or two arrays. get(i) , set(i,x) take constant time. add(size(),x) , remove(size()-1) [ add(0,x) , remove(0) ] take constant amortized time. Can waste a lot of space

ravi
Download Presentation

Review

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Review • ArrayStack (ArrayList), [ArrayDeque, and DualArrayDeque] implement the List interface using one or two arrays • get(i), set(i,x) take constant time • add(size(),x), remove(size()-1) [add(0,x), remove(0)] take constant amortized time • Can waste a lot of space • 2/3 of the array positions can be empty • Not suitable for real-time applications • grow(), shrink(), and balance() take O(size()) time.

  2. Coming up • RootishArrayStack: A list implementation with • get(i) and set(i,x) in constant time • add(i,x) and remove(i) in O(1 + size()-i) time • no more than O(size()1/2) wasted space • Suitable for real-time applications • in some languages (not Java) • DualRootishArrayDeque • A 2-ended version

  3. RootishArrayStack • Store the stack as a List of blocks (arrays) • block k has size (k+1), for k=0,1,2,...,r • at most 2 blocks not full blocks publicclassRootishArrayStack<T> extendsAbstractList<T> { List<T[]> blocks; intn; ... } 0 a 1 b c 2 3 d e f 4 g h i j k l

  4. Space analysis • How much space is wasted? • r blocks have room for r(r+1)/2 elements • To store n elements we need • r(r+1)/2 ≥ n • r ≥ (2n)1/2blocks are sufficient • We only waste O(n1/2) space keeping track of the blocks • The size of the last 2 blocks is at most 2r + 3 • Only waste O(n1/2) space on non-full blocks • Wasted space is only O(n1/2) r r+1

  5. RootishArrayStack – add(i,x) • As usual: • grow() if necessary • shift elements i,...,size()-1 right by one position publicvoid add(inti, T x) { int r = blocks.size(); if (r*(r+1)/2 < n + 1) grow(); n++; for (intj = n-1; j > i; j--) set(j, get(j-1)); set(i, x); }

  6. RootishArrayStack – remove(i) • Also as usual: • shift elements i+1,...,size()-1 left by one position • shrink() if necessary public T remove(inti) { T x = get(i); for (intj = i; j < n-1; j++) set(j, get(j+1)); n--; shrink(); returnx; }

  7. RootishArrayStack – grow() • Add another block of size r • runs in constant time in languages not requiring array initialization • otherwise, takes O(r) = O(size()1/2) time. protectedvoid grow() { blocks.add(f.newArray(blocks.size()+1)); }

  8. RootishArrayStack – shrink() • Removeblocks until there are at most 2 partially empty blocks protectedvoid shrink() { intr = blocks.size(); while (r > 0 && (r-2)*(r-1)/2 >= n) { blocks.remove(blocks.size()-1); r--; } }

  9. RootishArrayStack get(i) and set(i,x) • Find the block index b that contains element i(function i2b(i) ) • access element i- b(b+1)/2 within that block public T get(inti) { intb = i2b (i); intj = i - b*(b+1)/2; returnblocks.get(b)[j]; } public T set(inti, T x) { intb = i2b(i); intj = i - b*(b+1)/2; T y = blocks.get(b)[j]; blocks.get(b)[j] = x; returny; }

  10. RootishArrayStack- i2b(i) • Converting the List index i into a block number b • 0,...,i consists of i+1 elements • Blocks 0,...,b can store (b+1)(b+2)/2 elements • We want to find minimum integer b such that • (b+1)(b+2)/2 ≥ i + 1 • Solve (b+1)(b+2)/2 = i + 1 using the quadratic equation • quadratic equation gives a non-integer solution b’ • actually two solutions, but only one is positive • set b = Γb’˥

  11. RootishArrayStack - summary • Theorem: A RootishArrayStack • can perform get(i) and set(i,x) in constant time • can perform add(i,x) and remove(i) in O(1+size()-i) time • uses only O(size()1/2) memory in addition to what is required to store its elements • Key points: • Real-time • no amortization • Low-memory overhead • O(n1/2) versus O(n) for other array-based stacks

  12. Optimality of RootishArrayStack • Theorem: Any data structure that allows insertions will, at some point during a sequence of n insertions be wasting at least n1/2space. • Proof: If the data structure uses more than n1/2 blocks • Real-time • n1/2pointers (references) are being wasted just keeping track of blocks • Otherwise, the data structure uses k ≤ n1/2blocks • some block B has size at least n/k ≥ n1/2 • when B was allocated, it was empty and therefore was a waste of n1/2 space

  13. Practical Considerations • The use of many arrays to store data means that we can't do shifting with 1 call to System.arraycopy() • Slower than other implementations when i is small • The solution to the quadratic formula in i2b(i) requires the square root operation • This can be slow • This can lead to rounding errors • can be corrected by checking that • (b+1)/2 < i ≤ (b+1)(b+2)/2 • Lookup tables can speed things • we only want an integer square root

  14. DualRootishArrayDeque • Using a RootishArrayStack for the internal stacks within a DualArrayDeque we obtain: • Theorem: A DualRootishArrayDeque • can perform get(i), set(i,x) in constant time • can perform add(i,x) remove(i) in • O(1 + min{i,size()-i}) amortized time • uses only O(size()1/2) memory in addition to what is required to store its elements • A real-time version is possible, see • Brodnik, Carlsson, Demaine, Munro, and Sedgewick. Resizeable arrays in optimal time and space. Proceedings of WADS 1999

  15. Review • Array-based implementations of Lists, Queues, Stacks, and Deques have many advantages • Constant-time access by position [get(i), set(i,x)] • Constant-amortized time addition and removal at the ends • Space-efficient versions use only O(n1/2) extra space • Big disadvantage • Additions and removals in the interior are slow • Running time is at least Ω(min{i, size()-i})

  16. Coming up… • Lists and queues based on (singly and doubly) linked lists • It might use an array of length 2n to store n elements of data • get(i), set(i,x) are slow • add(), remove() with an iterator take constant time • Space-efficient linked lists

  17. Coming up… • Singly-linked lists • Efficient stacks and queues • Doubly-linked lists • Efficient deques • Space-efficient doubly-linked lists • Time/space tradeoff

  18. Singly-linked lists • A list is a sequence of Node: • Node contains • a data value x • a pointer next to the next node in the list protectedclass Node { T x; Node next; } null a b c d e

  19. Singly-linked lists (cont'd) • We keep track of the first node in the list (head) • We might also keep track of the last node (tail) publicclassSLList<T> extendsAbstractQueue<T> { Node head; Node tail; intn; ... } tail ... null a b y z head

  20. Queues as singly-linked lists • A singly-linked list can implement a queue • enqueue at the tail • dequeue at the head • Requires special care to manage head and tail correctly • when adding to empty queue • when removing last element from queue tail ... null a b y z head back of the line front of the line

  21. Dequeuing (removing) an element tail ... null a b y z head public T poll() { T x = head.x; head = head.next; if (--n == 0) tail = null; returnx; } tail e null head

  22. publicboolean offer(T x) { Node u = new Node(); u.x = x; if (n == 0) { head = u; tail = u; } else { tail.next = u; tail = u; } n++; returntrue; } Enqueuing x tail null head tail x null head a b

  23. Delicateness publicboolean offer(T x) { Node u = new Node(); u.x = x; if (n == 0) { head = u; tail = u; } else { tail = u; tail.next = u; } n++; returntrue; } • This code is wrong • can you see why?

  24. Stacks as singly-linked lists • A singly-linked list can also be used as a stack • push and pop are done by manipulating head tail ... null a b y z head bottom of the stack top of the stack

  25. Stack - push tail a null b c d e head public T push(T x) { Node u = new Node(); u.x = x; u.next = head; head = u; if (n == 0) tail = u; n++; return x; } e tail null head

  26. Arbitrary insertion and deletions • In a singly-linked list, we can even do arbitrary insertions/deletions • if we are given a pointer to the preceding element • Getting a pointer to the ith node takes O(i+1) time protected Node getNode(inti) { Node u = head; for (int j = 0; j < i; j++) u = u.next; return u; } u tail ... null a b y z head

  27. Deleting a node • Does not work for first node • no preceding node u! u d e protectedvoiddeleteNext(Node u) { if (u.next == tail) tail = u; u.next = u.next.next; }

  28. Adding a node • Does not work for first node • no preceding node u! v u e protectedvoidaddAfter(Node u, Node v) { v.next = u.next; u.next = v; if (u == tail) tail = v; }

  29. In-Class Exercise • Write code for • add(i,x) • remove(i) • Code should run in O(1+i) time 29

  30. Singly-linked list summary • Singly-linked lists support: • push(x), pop(), enqueue(x), dequeue() in constant time (in the worst case) • add(i,x), remove(i) in O(1+i) time • One Node is created per list item • Memory allocation overhead • Node contains data + 1 pointer/reference (next) • At least n pointers for a list of size n

  31. Doubly-linked lists • Singly-linked lists fall just short of being able to implement a deque • No way to remove elements from the tail tail ... null a b y z head can't access this node except through head

  32. Doubly-linked lists • Doubly-linked lists maintain two pointers (references) per node • next - points to next node in the list • prev - points to previous node in the list protectedclass Node { Node next, prev; T x; } tail head ... null null a b y z

  33. Removing a node (incorrect) • This code is incorrect – Why? u d e p.next p.prev p protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; }

  34. Removing a node (incorrect) • Doesn't correctly handle boundary cases • p == head (so p.prev == null) • p == tail (so p.prev == tail) • p == head and p == tail • (sp p.prev == p.tail == null) head d e u d tail head d tail protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; }

  35. protected void remove(Node p) { if (p == head && p == tail) { head = null; tail = null; } elseif (p == head) { head = p.next; p.next.prev = null; } elseif (p == tail) { tail = p.prev; p.prev.next = null; } else { p.prev.next = p.next; p.next.prev = p.prev; } n--; } Versus protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; }

  36. Code is error prone • Code for boundary cases is troublesome • hard to write correctly • lots of cases • slow to execute (on some architectures) • We would like to get rid of boundary cases • need to get rid of head and tail protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; }

  37. The dummy node technique • Replace head and tail with a dummy Node • dummy.next replaces head • dummy.prev replaces tail • dummy is always present; even in an empty list ... a b y z dummy publicclassDLList<T> extendsAbstractSequentialList<T> { protected Node dummy; protected intn; ... }

  38. Creating a new (empty) list publicDLList() { dummy = new Node(); dummy.next = dummy; dummy.prev = dummy; n = 0; } dummy

  39. Removing a node • Now removing a node is easy u d e p.next p.prev p protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; }

  40. Removing a node • The same code works even when removing the last node p protectedvoid remove(Node p) { p.prev.next = p.next; p.next.prev = p.prev; n--; } p dummy p.prev == p.next == dummy

  41. Adding a node • Add the new Node u just before Node p protected Node add(Node u, Node p) { u.next = p; u.prev = p.prev; u.next.prev = u; u.prev.next = u; n++; return u; } u u d p p.prev p

  42. Exercise • This code is not correct. Why? protected Node add(Node u, Node p) { u.next = p; u.next.prev = u; u.prev = p.prev; u.prev.next = u; n++; return u; } u u d p p.prev p

  43. Finding a node protected Node getNode(inti) { Node p = null; if (i < n/2) { p = dummy.next; for (int j = 0; j < i; j++) p = p.next; } else { p = dummy; for (int j = n; j > i; j--) p = p.prev; } return(p); } • To find the ith node search • from the front if • i < size()/2 • from the back otherwise • O(1+min{i, size()-i}) time • Fast • when i~0 (head) • when i~size() (tail)

  44. Removing and Adding • add(i,x) and remove(i) are now easy • Find the appropriate node p • Add x before p (or remove p) • Takes O(1 + min{i, size()-i}) time publicvoid add(inti, T x) { add(getNode(i), x); } public T remove(inti) { Node p = getNode(i); remove(p); returnp.x; }

  45. Getting and setting • get(i) and set(i,x) are easy too • and take O(1 + min{i, size()-i}) time public T get(inti) { returngetNode(i).x; } public T set(inti, T x) { Node u = getNode(i); T y = u.x; u.x = x; return y; }

  46. Doubly-linked lists - summary • Doubly-linked lists support • add(i,x), remove(i) in O(1 + min{i,size()-i}) time • deque operations run in constant time per operation • get(i), set(i,x) in O(1+min{i,size()-i}) time • insertion/removal of any node in constant time • given a reference to the node being deleted or • a reference to the node after the insertion

  47. Memory-efficient linked lists • Linked lists are great, except • Each value is stored in its own list node • Each insertion requires allocating a new node • Each node stores 2 pointers • Wasted space is at least • 2 × size() × sizeof(pointer) • If data values are small (e.g., Integer) then wasted space can exceed the space for data

  48. Memory-efficient linked lists • Idea: • group list elements into blocks (arrays) • blocks have size b+1 • each block stores b-1, b, or b+1 values • except the last block, which can be more empty • store the blocks in a linked list a b c d e f g h i j b = 3 last block - partly full

  49. Space analysis • The number of blocks is at most • 1 + size()/(b-1) • each block wastes a constant [O(1)] amount of space • wasted space is O(b+n/(b-1)) • By making b larger we can reduce the wasted space • limit is b ~ n1/2 a b c d e f g h i j

  50. Block data structure • We represent each block as a BoundedArrayDeque • ArrayDeque with size of backing array a set fixed • a.length = b+1 • no grow() or shrink() operations • Sometimes we will want to • move the last element in node u to the front u.next • move the first element in block i to the back of block i-1 • These operations take constant time in a BoundedArrayDeque

More Related