• ArrayStack (ArrayList), [ArrayDeque, and DualArrayDeque] implement the List interface using one or two arrays Review o get(i), set(i,x) take constant time o add(size(),x), remove(size()-1) [add(0,x), remove(0)] take constant amortized time o Can waste a lot of space − 2/3 of the array positions can be empty o Not suitable for real-time applications − grow(), shrink(), and balance() take O(size()) time.
64
Embed
ArrayStack (ArrayList), [ArrayDeque, and DualArrayDeque] implement the List interface using one or two arrays Review o get(i), set(i,x) take constant time.
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
• ArrayStack (ArrayList), [ArrayDeque, and DualArrayDeque] implement the List interface using one or two arrays
Review
o get(i), set(i,x) take constant time
o add(size(),x), remove(size()-1) [add(0,x), remove(0)] take constant amortized time
o Can waste a lot of space
− 2/3 of the array positions can be emptyo Not suitable for real-time applications
− grow(), shrink(), and balance() take O(size()) time.
• 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
o in some languages (not Java)
Coming up
• DualRootishArrayDeque
− A 2-ended version
• 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
RootishArrayStack
a
b c
d fe
g jh i
k l
0
1
2
3
4
blocks public class
RootishArrayStack<T> extends AbstractList<T> { List<T[]> blocks; int n; ...}
• 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/2 blocks 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)
Space analysis
r+1
r
• As usual:
− grow() if necessary
− shift elements i,...,size()-1 right by one position
RootishArrayStack – add(i,x)
public void add(int i, T x) { int r = blocks.size(); if (r*(r+1)/2 < n + 1) grow(); n++; for (int j = n-1; j > i; j--) set(j, get(j-1)); set(i, x);}
• Also as usual:
− shift elements i+1,...,size()-1 left by one position
− shrink() if necessary
RootishArrayStack – remove(i)
public T remove(int i) { T x = get(i); for (int j = i; j < n-1; j++) set(j, get(j+1)); n--; shrink(); return x;}
• Add another block of size r
− runs in constant time in languages not requiring array initialization
• Remove blocks until there are at most 2 partially empty blocks
RootishArrayStack – shrink()
protected void shrink() { int r = blocks.size(); while (r > 0 && (r-2)*(r-1)/2 >= n) { blocks.remove(blocks.size()-1); r--; }}
• Find the block index b that contains element i (function i2b(i) )
• access element i - b(b+1)/2 within that block
RootishArrayStack get(i) and set(i,x)
public T get(int i) { int b = i2b (i); int j = i - b*(b+1)/2; return blocks.get(b)[j];}
public T set(int i, T x) { int b = i2b(i); int j = i - b*(b+1)/2; T y = blocks.get(b)[j]; blocks.get(b)[j] = x; return y;}
• 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
o (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’
o actually two solutions, but only one is positive
− set b = Γb’˥
RootishArrayStack - i2b(i)
• 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
RootishArrayStack - summary
• Key points:− Real-time
• no amortization− Low-memory overhead
• O(n1/2) versus O(n) for other array-based stacks
• Theorem: Any data structure that allows insertions will, at some point during a sequence of n insertions be wasting at least n1/2 space.
Optimality of RootishArrayStack
• Proof: If the data structure uses more than n1/2 blocks− Real-time− n1/2 pointers (references) are being wasted just
keeping track of blocks− Otherwise, the data structure uses k ≤ n1/2 blocks
• 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
• 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
Practical Considerations
• 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
• 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
DualRootishArrayDeque
• A real-time version is possible, see−Brodnik, Carlsson, Demaine, Munro, and
Sedgewick. Resizeable arrays in optimal time and space. Proceedings of WADS 1999
• 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
Review
• Big disadvantage−Additions and removals in the interior are slow
• Running time is at least Ω(min{i, size()-i})
• 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 slowadd(), remove() with an iterator take constant time
• Space-efficient linked lists
Coming up…
• Singly-linked lists
–Efficient stacks and queues
• Doubly-linked lists
–Efficient deques
• Space-efficient doubly-linked lists
–Time/space tradeoff
Coming up…
• A list is a sequence of Node:
• Node contains–a data value x–a pointer next to the next node in the list
Singly-linked lists
a b c d e null
protected class Node { T x; Node next;}
• We keep track of the first node in the list (head)
• We might also keep track of the last node (tail)
Singly-linked lists (cont'd)
public class SLList<T> extends AbstractQueue<T> { Node head; Node tail; int n; ...}
a b y zhead
tail ..
. null
• A singly-linked list can implement a queue
− enqueue at the tail
− dequeue at the head
Queues as singly-linked lists
• Requires special care to manage head and tail correctly
− when adding to empty queue
− when removing last element from queue
a b y zhead
tail ..
. null
front of the linefront of the line back of the lineback of the line
Dequeuing (removing) an element
a b y zhead
tail ..
. null
e
head
tail
null
public T poll() { T x = head.x; head = head.next; if (--n == 0) tail = null; return x;}
a b
x
head
tail
null
public boolean offer(T x) { Node u = new Node(); u.x = x; if (n == 0) { head = u; tail = u; } else { tail.next = u; tail = u; } n++; return true;}
Enqueuing
x
head
tail
null
Delicateness
public boolean offer(T x) { Node u = new Node(); u.x = x; if (n == 0) { head = u; tail = u; } else { tail = u; tail.next = u; } n++; return true;}
• This code is wrong
−can you see why?
• A singly-linked list can also be used as a stack
−push and pop are done by manipulating head
Stacks as singly-linked lists
a b y zhead
tail ..
. null
top of the stacktop of the stack
bottom of the stackbottom of the stack
Stack - push
a
b ehead
tail
null
e
head
tail
null
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;}
c d
• 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
Arbitrary insertion and deletions
protected Node getNode(int i) { Node u = head; for (int j = 0; j < i; j++) u = u.next; return u;}
• 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 ge f h i j
Space analysis
• 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
Block data structure
• 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
• To find the ith element we find the block that contains it
− Takes time O(1 + (min{i, size()-i} /
b))
− faster than a standard linked list
Finding an elementpublic T get(int i) { if (i < n/2) { Node u = first; int b = 0; while (b + u.x.size() < i + 1) { b += u.x.size(); u = u.next; } return u.x.get(i-b); } else { ...
a b c d ge f h i j
b=0b=0
b=3b=3
b=7b=7
b=9b=9
• To insert into block j– check if any of blocks j, j+1, j+2,...,j+b-1 are not full
• if yes, then there is space, so do shifting to make room• requires at most 2b deque operations• requires shifting at most b elements in one of the deques ( O(b) time )
Insertion - easy case O(b) time
a db c e hf g i j k
a db c e hf g i j kx
Insert x hereInsert x here
• If blocks j, j+1, ... j+b-1 are all full– these b blocks contain a total of b(b+1) elements– repartition them into b+1 blocks each containing b elements– then insert into the (now not full) block
Insertion – full case O(b2) time
Insert x hereInsert x herea db c e hf g i lj k
a db c e hf g i lj k
a dx b e hf g i lj kc
O(b2) timeO(b2) time
• To remove an element from block j– if any of blocks j, j+1, j+2,... contain more than b-1
elements• do shifting so that block j contains at least b elements• remove element from block j
Removal- easy case O(b) time
delete thisdelete this
a c d e f gb
a dc e f gcb
a d e f gc
Removal– hard case O(b2) time
delete thisdelete this
O(b2) timeO(b2) time
• If blocks j,...,j+b-1 each contain b-1 elements–we have b blocks each containing b-1 elements– redistribute so that we have b-1 blocks each
containing b elements– delete the element
a c d e fb
a dc e fb
a dc e f
• A CompactDLList has the following properties
–wasted space is O(size()/b)
–get(i) and set(i,x) each take
•O(1 + min{i, size()-i}/b) time
– remove(i) and add(i,x) take time
•O(b + min{i, size()-i}/b) usually
•O(b2 + min{i, size()-i}/b) occasionally
• What do we mean by usually and occasionally?
Space-Efficient Linked List (SElist)
• We use a credit scheme–A block with b+1 elements or b-1 elements has 1 credit–A block with b elements has 0 credits
• Main idea:–When insertion and removal take b2 time, we will take away b spare credits–With every insertion and removal we create at most one new credit
• Conclusion: At most one out of every b insertion/removals takes b2 time
Amortized analysis of CompactDLLists
• A hamburger costs 8 credits – [analogous to: operation that takes O(b2) time]
Weight-watchers analogy
• Every hour of workout gives you one credit– [analagous to: operation that takes O(b) time]
• The maximum number of hamburgers you are allowed to eat is– (# hours spent working out)/8
• At most one credit is created by insertion– (maybe none)
Analysis of insertion (not full case)
a db c e hf g i j k
a db c e hf g i j kx
insert x hereinsert x here
₡₡ ₡₡
₡₡₡₡₡₡
• b credits are freed up and one credit is added
Analysis of insertion (full case)
a db c e hf g i lj k
a db c e hf g i lj k
a dx b e hf g i lj kc
₡₡ ₡₡ ₡₡
₡₡
3 credits freed now3 credits freed now
insert x hereinsert x here
1 credit is added1 credit is added
• At most one new credit is added
Analysis of removal (easy case)
a c d e f gb
delete thisdelete this
a dc e f gcb
a d e f gc
₡₡₡₡
₡₡₡₡
₡₡₡₡₡₡
• b credits are freed and one credit is added.
Analysis of insertion (sparse case)
a c d e fb
delete thisdelete this
a dc e fb
a dc e f
₡₡₡₡₡₡
₡₡
3 credits freed3 credits freed
1 credit is added1 credit is added
• In a sequence of n add/remove operations–At most n/b takes O(b2) time–Others take O(b) time
Analysis wrap up
• Total time is–O(nb + (n/b)b2) = O(nb)–O(b) amortized time per operation
Compact doubly-linked list (summary)
• Theorem: A CompactDLList is an implementation of the List interface with the following properties:– get(i) and set(i,x) each take•O(1 + min{i, size()-i}/b) time
– remove(i) and add(i,x) take•O(b + min{i, size()-i}/b) amortized time
– The amount of memory used beyond that needed to store the data is O(n/b)– The number of memory allocation/deallocations
during a sequence of n add/remove operations is O(n/b)