binarytreeIterator:
stack, prevn
ctor(tree):
if tree == null:
throw
stack = new
prevn = null
if tree.root != null:
stack.push(tree.root)
bool hasnext():
return stack.any()
//O(n)
bool isVisited(n, visited):
return visited.has(n)
//O(1), and O(logn) due to stack
bool isVisited(n, prevn):
return prevn == n
// pre-order
x current():
if stack.isEmpty():
throw "empty"
n = stack.pop()
if n.right != null:
stack.push(n.right)
if n.left != null:
stack.push(n.left)
return n.value
// in-order
x current():
if stack.isempty():
throw "empty"
while stack.any():
n, isVisited = stack.pop()
if !isVisited && n.left != null:
stack.push(n, isVisited: true)
stack.push(n.left, isVisited: false)
continue
if n.right != null:
stack.push(n.right, isVisited: false)
return n
// post-order
x current():
if stack.isEmpty():
throw "empty"
while stack.any():
n = stack.pop()
if n.left != null
&& !isVisited(n.left, prevn) && !isVisited(n.right, prevn):
stack.push(n)
stack.push(n.left)
continue
if n.right != null && !isVisited(n.right, prevn):
stack.push(n)
stack.push(n.right)
continue
prevn = n
return n.value
throw "invalid op"
[Hat tip to J]
Showing posts with label data structures. Show all posts
Showing posts with label data structures. Show all posts
Saturday, March 24, 2018
binary tree iterators
Monday, November 13, 2017
ordered hashset
OrderedHashSet:
map // {T, {node{T}}
dq // {T}
add(x):
if has(x):
// don't change position in queue
// as it's a subtle behavior, instead let
// the caller explicity call remove(x) and add(x)
return
map[x] = dq.enqueue(x)
bool has(x):
return map.hasKey(x)
bool remove(x):
if !has(x):
return false
dq.removeNode(map[x])
map.remove(x)
return true
[] foreach():
for x in dq:
yield x
// deque interface
dq:
node enqueue(x):
bool removeNode(node):
Wednesday, November 01, 2017
implement hashmap
k cannot be null as cannot calculate hash of k.
hashmap:
store = new // deque{{k, v}}[]
add(k, v):
i = index(k)
dq = store[i]
if dq == null:
dq = new dq()
store[i] = dq
for kv in dq:
if kv.key == k:
kv.value = v
return
dq.enqueue(new {k, v})
bool hasKey(k):
dq = store[index(k)]
if dq == null:
return false
for kv in dq:
if kv.key == k:
return true
return false
bool removeKey(k):
i = index(k)
dq = store[i]
if dq == null:
return false
// dq.nodes() is a copy
for node in dq.nodes():
if node.kv.key == k:
dq.removeNode(node)
if dq.isEmpty():
store[i] = null
return true
return false
// O(n)
bool remove(v):
for i = 0, i < store.length(), i++:
dq = store[i]
if dq == null:
continue
// dq.nodes() is a copy
for node in dq.nodes():
if node.kv.value == v:
dq.removeNode(node)
if dq.isEmpty():
store[i] = null
return true
return false
// helpers
int index(k):
return hash(k) %store.length()
int hash(k):
if k == null:
throw
// ...
Thursday, October 19, 2017
if binary tree is full, perfect, complete
full: each node has 0 or 2 child nodes.
tip: The depths of left and right subtrees are equal at all levels.
tip: After encountering a null child, no nodes in queue should have any children.
// full
x
x x
x x
// not full
x
x x
x x x
bool isfull(n):
if n == null:
return true
if n.left == null != n.right == null: // same as ^
return false
return (n.left) && (n.right)
perfect: tree has 2^depth -1 nodes (i.e. each level is full).tip: The depths of left and right subtrees are equal at all levels.
// perfect
x
x x
x x x x
bool isPerfect(n):
return depthPerfect(n) != null
int? depthPerfect(n):
if n == null:
return 0
ldepth = (n.left)
if rdepth == null:
return null
rdepth = (n.right)
if rdepth == null:
return null
if ldepth != rdepth:
return null
return ldepth +1 // or rdepth +1
complete: nodes at last level are filled from left.tip: After encountering a null child, no nodes in queue should have any children.
// complete
x
x x
x x x
// not complete
x
x 6
x x 7
4
2 6
1 5
// bfs
bool isComplete(node):
if node == null:
return true
q = new
q.enqueue(node)
ended = false
while !q.isEmpty():
n = q.dequeue()
if ended && n.left != null:
return false
if n.left != null:
q.enqueue(n.left)
else:
ended = true
if ended && n.right != null:
return false
if n.right != null:
q.enqueue(n.right)
else:
ended = true
return true
// bfs
bool isComplete(node):
if node == null:
return true
q = new
q.enqueue(node)
ended = false
while !q.isEmpty():
n = q.dequeue()
if ended && (n.left != null || n.right != null):
return false
if n.left == null && n.right != null:
return false
ended = n.left == null || n.right == null
if n.left != null:
q.enqueue(n.left)
if n.right != null:
q.enqueue(n.right)
return true
// dfs
bool isComplete(n):
return (n, count(n) -1)
bool isComplete(n, maxi, i = 0):
if n == null:
return true
if i > maxi:
return false
// faster to check right subtree first as
// right subtree returns false if not complete
return isComplete(n.right, maxi, i*2 +2)
&& isComplete(n.left, maxi, i*2 +1)
int count(n):
if n == null:
return 0
return 1 + count(n.left) + count(n.right)
// dfs
bool isComplete(n):
isComplete(n, count: {value: 0}, maxi: {value: 0}, i: 0)
return count.value < i.value +1
isComplete(n, count, maxi, i):
if n == null:
return
count.value++
maxi.value = max(maxi.value, i)
(n.left, count, maxi, i*2 +1)
(n.right, count, maxi, i*2 +2)
// bfs
bool isComplete(node):
if node == null:
return true
q = new
q.enqueue((node, iexp: 0))
i = 0
while !q.isEmpty():
n, iexp = q.dequeue()
if iexp > i:
return false
i++
if n.left != null:
q.enqueue((n.left, iexp*2 +1))
if n.right != null:
q.enqueue((n.right, iexp*2 +2))
return true
[Hat tip to ctci]
Thursday, October 12, 2017
queue with 1 stack
Using recursion and backtracking, i.e. using the method call stack as the "other" stack to hold on to the elements.
// O(n)
enqueue(x):
if stack.isEmpty():
stack.push(x)
return
temp = stack.pop()
enqueue(x)
stack.push(temp)
// O(1)
x dequeue():
return stack.pop()
// O(1)
enqueue(x):
stack.push(x)
// O(n)
x dequeue():
temp = stack.pop()
if stack.isEmpty():
x = temp
else:
x = dequeue()
stack.push(temp)
return x
[Hat tip to SO]
Monday, October 03, 2016
reentrant lock, unlock
Given two primitive locking constructs, write reentrant acquire() and release().
acquire(obj) cannot be the 1st statement for lock(obj) to be reentrant. It is ok if 1+ threads pass the if as acquire(obj) will allow only 1st thread and block others.
Calling release(obj) without acquire(obj) will throw.
// blocks others acquire(obj): // unblocks others, throws ex if called without lock(obj) first release(obj):acquire(obj) should be called as early as possible. release(obj) should be called as late as possible.
acquire(obj) cannot be the 1st statement for lock(obj) to be reentrant. It is ok if 1+ threads pass the if as acquire(obj) will allow only 1st thread and block others.
Calling release(obj) without acquire(obj) will throw.
locker:
obj
count = 0
thread = null
//ctor
locker(obj):
obj = obj
lock():
// any other thread gets blocked (reentrant)
if thread != thread.current:
acquire(obj)
thread = thread.current
count++
unlock():
if thread != thread.current:
throw //as release called without lock
count--
if count == 0:
thread = null
// unblocks blocked threads
release(obj)
Used as:
lockobj1 = new locker(obj1) lockobj1.lock() //1st // critical section // ok to call lock() again lockobj1.lock() //2nd ... lockobj1.unlock() //2nd lockobj1.unlock() //1st[Hat tip to PK]
Saturday, July 30, 2016
if array is in heap property
bool isminheap(a):
if a == null:
throw
lasti = a.length() -1
lastparent = (lasti-1) /2
for i = 0, i <= lastparent, i++:
left = i*2 +1
right = left +1
// left is never out of range
if left < a.length() && a[i] > a[left]:
return false
// right may be out of range
if right < a.length() && a[i] > a[right]:
return false
return true
Tuesday, March 29, 2016
staque, a stack+queue combo data structure
staque:
a
count = start = end = 0
staque(capacity):
a = new [capacity]
push(x):
if isfull():
throw
a[end] = x
count++
end = mod(end+1, a.length())
pop(x):
if isempty():
throw
end = mod(end-1, a.length())
count--
return a[end]
enqueue(x):
if isfull():
throw
a[end] = x
count++
end = mod(end+1, a.length())
x dequeue():
if isempty():
throw
x = a[start]
start = mod(start+1, a.length())
count--
return x
isempty():
return count == 0
isfull():
return count == a.length()
clear():
count = start = end = 0
top():
if isempty():
throw
return a[end]
start():
if isempty():
throw
return a[start]
end():
if isempty():
throw
return a[end]
mod(x, n):
m = x % n
if m < 0:
m += n
return m
Wednesday, August 05, 2015
min() and max() for stack in O(1)
//example 1 1 - 1 1 - 6 - 6 6 - 6 5 5 5 main min max
stack:
main = new stack()
min = new stack()
max = new stack()
push(x):
main.push(x)
if min.isempty() || x <= min.peek():
min.push(x)
if max.isempty() || x >= max.peek():
max.push(x)
x pop():
if main.isempty():
throw
x = main.pop()
if x == min.peek():
min.pop() //ignore
if x == max.peek():
max.pop() //ignore
return x
// time: O(1), space: O(n)
x min():
if min.isempty():
throw
return min.peek()
// time: O(1), space: O(n)
x max():
if max.isempty():
throw
return max.peek()
Thursday, July 09, 2015
bitset
int has 32 bits
long has 64 bits
x /32 = x >> 5
x %32 = x & 0x1f
[Hat tip to ctci, NH]
long has 64 bits
x /32 = x >> 5
x %32 = x & 0x1f
bitset:
int[] flags
long max
//0..n
bitset(long n):
if n < 0:
throw "out of range"
// n >> 5 is always 1 less, so add 1
//1 11111 : 1 +1 <- no bits wasted
// 11111 : 0 +1 <- no bits wasted
//1 00000 : 1 +1 <- 31 bits wasted
// 00000 : 0 +1 <- 31 bits wasted
long size = n >> 5 +1
max = n
flags = new int[size]
set(long x):
if x < 0 || x > max:
throw "out of range"
index = x >> 5
offset = x & 0x1f
flags[index] |= (1 << offset)
clear(long x):
if x < 0 || x > max:
throw "out of range"
index = x >> 5
offset = x & 0x1f
flags[index] &= ~(1 << offset)
toggle(long x):
if x < 0 || x > max:
throw "out of range"
index = x >> 5
offset = x & 0x1f
flags[index] ^= (1 << offset)
has(long x):
if x < 0 || x > max:
throw "out of range"
index = x >> 5
offset = x & 0x1f
return ((flags[index] >> offset) & 1) == 1
[Hat tip to ctci, NH]
Thursday, June 18, 2015
Saturday, May 30, 2015
heap / max heap / priority queue
Using array. Could use list as a backing store. All heap-changing operations are O(logn) - occasionally O(n) when list resizes. When top has to be replaced, displace() avoids having to call delete() and insert().
heapify() is efficient. Heapify() using siftdown() is faster than siftup() as siftdown() only has ~n swaps (0*n/2 + 1*n/4 + 2*n/8 + h*1 = n) i.e O(n) but siftup() has nlogn swaps (h*n/2 + (h-1)*n/4 + (h-2)*n/8... + 2*1 + 0*1) i.e. O(nlogn).
// interface
heap:
insert(T) // uses siftup()
T delete() // uses siftdown()
T displace(T) // replaces top, uses siftdown()
T peek()
int size()
int capacity()
[] foreach()
// max-heap uses array (or list) as a backing store
max-heap: heap
store
max-heap(capacity)
if capacity < 0:
throw
store = new [capacity]
// add, siftup
insert(x):
if isfull():
throw
store[count] = x
count++
siftup()
// return 0th, [0] = [last], siftdown
delete():
if isempty():
throw
x = store[0]
store[0] = store[count-1]
count--
siftdown()
return x
// replace top, siftdown
displace(x):
if isempty():
throw
top = store[0]
store[0] = x
siftdown()
return top
peek():
if isempty():
throw
return store[0]
siftup(end = count-1):
i = end
while i > 0:
parent = (i-1) /2
if store[parent] >= store[i]:
break
swap(store, i, parent)
i = parent
siftdown(start = 0):
i = start
while i < count:
left = i*2 +1
right = left +1
max = i
if left < count && store[left] > store[max]:
max = left
if right < count && store[right] > store[max]:
max = right
if max == i:
break
swap(store, i, max)
i = max
//or count()
size():
return count
isempty():
return count == 0
isfull():
return count == store.length()
capacity():
return capacity
[] foreach():
for i = 0, i < count, i++:
yield return store[i]
append(x) is useful in some cases where a delayed call to
isheapified = true
append(x):
if isfull():
throw
store[count] = x
count++
isheapified = false
// note: heapify using siftdown is faster
heapify():
heapify-siftdown()
isheapified = true
// O(n)
heapify-siftdown():
start = count/2 -1 // last parent
while start >= 0:
siftdown(start)
start--
// O(nlogn)
heapify-siftup():
end = count-1
while end > 0:
siftup(end)
end--
// delayed call to heapify()
peek():
if isempty():
throw
if !isheapified:
heapify()
return store[0]
Wednesday, May 13, 2015
lru cache for n items
lru cache for n items with O(1) add, remove, has, get.
With queue, add() is not O(1), get() is difficult/hacky to implement. So deque.
With queue, add() is not O(1), get() is difficult/hacky to implement. So deque.
// caches n recently used items
lru-cache:
n // items
ctor(n):
n = n
count = 0
map
q
add(key, value):
if map.has(key):
// update value, move node to end of dq
item = map[key]
item.value = value
// difficult: item.node need to be put at end
return
if count == n:
// remove lru item
// handle delayed removal from queue
lrukey = q.dequeue()
while !map.has(lrukey):
lrukey = q.dequeue()
map.remove(lrukey)
count--
// add key
count++
q.enqueue(key)
map.add(key, value)
remove(key):
if !map.has(key):
return // or throw "error"
count--
// only remove from map, delay removal from queue during add
map.remove(key)
has(key):
return map.has(key)
get(key):
// difficult/hacky to implement
dq = new deque{key}()
nodemap = new map{key, node{key}}()
valuemap = new map{key, value}()
add(key, value):
if key == null || value == null: throw
if has(key):
// nodemap does not change
dq.makeTail(nodemap[key])
valuemap[key] = value
return
if count == n:
// remove lru
lrukey = dq.peek()
removeInternal(lrukey)
// add to end
addInternal(key, value)
value removeInternal(key):
dq.removeNode(nodemap[key])
nodemap.remove(key)
value = valuemap[key]
valuemap.remove(key)
count--
return value
addInternal(key, value):
node = dq.enqueue(key)
nodemap[key] = node
valuemap[key] = value
count++
bool remove(key):
if key == null: throw
// do not throw if key no exists
if !has(key):
return false
removeInternal(key)
return true
value get(key):
if key == null: throw
// do not throw if key no exists
if !has(key):
return null // default(value)
dq.makeTail(nodemap[key])
return valuemap[key]
bool haskey(key):
if key == null: throw
return valuemap.haskey(key)
int count():
return count
// deque interface
deque{key}:
// for removal in O(1) time
node{key} enqueue(key):
removeNode(node{key})
// moves dq node to end
makeTail(node):
key peek():
// not used
key dequeue():
Thursday, May 07, 2015
semaphore
//counting semaphore
semaphore:
maxsize
count = 0
lock = new
int acquire():
lock(lock):
while count == maxsize:
monitor.wait(lock)
if count == 0:
monitor.pulseall(lock)
return ++count // returns 1..maxsize
int release():
lock(lock):
while count == 0:
monitor.wait(lock)
if count == maxsize:
monitor.pulseall(lock)
return --count // returns 0..maxsize-1
// below statements are equivalent
lock(lock):
// do something
monitor.enter(lock)
try:
// do something
finally:
monitor.exit(lock)
Note:- pulse/all when count is 1+ does nothing
- pulse/all when count is (maxsize-1)- does nothing
As there will be no threads in waiting queue to be moved to ready queue.
[Hat tip to MG]
Wednesday, May 06, 2015
circular queue, overwrite when full
Used for last n items from a stream.
[Hat tip to ctci]
circular-queue:
capacity
buffer
head = 0
tail = 0
count = 0
//ctor
circular-queue(capacity):
if capacity <= 0:
throw
capacity = capacity
buffer = new [capacity]
enqueue(x):
buffer[tail] = x
if count < capacity:
count++
tail = wrapindex(tail +1, n)
//when queue is full, head = tail
if count == capacity:
head = tail
x dequeue():
if isempty():
throw "empty"
x = buffer[head]
count--
head = wrapindex(head +1, n)
return x
bool isempty():
return count == 0
int size():
return count
clear():
head = tail = count = 0
x peek():
if isempty():
throw "empty"
return a[head]
x[] foreach():
index = head
for i = 0, i < count, i++:
yield buffer[index]
index = wrapindex(index +1)
int wrapindex(i, n):
index = i %n // -1 or -6 %5 is -1
if index < 0:
index += n
return index
last-n-items(stream, n):
if n < 0:
throw
if n == 0:
return [] //empty
q = new circular-queue(n)
foreach item in stream:
q.enqueue(item)
for item in queue:
yield item
[Hat tip to ctci]
Monday, April 13, 2015
trie
// interface
add-word(word)
bool is-word(word)
string[] get-all-words()
string[] get-words-by(prefix)
string[] get-longest-words()
remove-word(word)
trienode:
// members
char c
map<char, trienode> children
int wordcount
trienode parent
// ctors
trienode(c, parent):
c = c
children = new
wordcount = 0
parent = parent
// methods
is-word():
return wordcount > 0
trie:
// members
root = new trienode(' ', parent: null)
// methods
add-word(word):
node = root
foreach c in word:
child = node.children[c]
if child == null:
child = new trienode(c: c, parent: node)
node.children[c] = child
node = child
node.wordcount++
is-word(word):
node = get-trienode-by(word)
return node != null && node.is-word()
get-all-words():
return get-words-wrapper(root, prefix: "")
get-words-by(prefix):
node = get-trienode-by(prefix)
if node != null:
words = get-words-wrapper(node, prefix)
else:
words = new // empty if prefix does not exists
return words
get-words-wrapper(node, prefix):
words = new
get-words-recursive(words, node, new buffer(prefix))
return words
get-words-recursive(words, node, buffer):
if node.is-word():
words.add(buffer.tostring())
foreach child in node.children:
buffer.append(child.c)
get-words-recursive(words, child, buffer):
buffer.length--
get-trienode-by(s):
node = root
foreach c in s:
node = node.children[c]
if node == null:
break
return node
get-longest-words():
words = new
length = 0
get-longest-words-recursive(words, root, new buffer(), ref length):
return words
get-longest-words-recursive(words, node, buffer, ref length):
if node.is-word():
if buffer.length > length:
words.clear()
length = buffer.length
if buffer.length == length:
words.add(buffer.tostring())
foreach child in node.children:
buffer.append(child.c)
get-longest-words-recursive(words, child, buffer, ref length):
buffer.length--
remove-word(word):
node = get-trienode-by(word)
if node == null || !node.is-word():
throw
node.wordcount = 0
while node.children.count == 0 && !node.is-word() && node != root:
parent = node.parent
parent.children.remove(node.c)
node.parent = null
node = parent
Thursday, March 26, 2015
circular stack
Used for undo operations.
[Hat tip to 2048]
circular-stack:
a, capacity
count = 0
top = 0
circular-stack(capacity):
capacity = capacity
a = new [capacity]
push(x):
a[top] = x
top = wrapIndex(top +1, capacity)
if count < capacity:
count++
x pop():
if isempty():
throw
top = wrapIndex(top -1, capacity)
count--
return a[top]
x peek():
if isempty():
throw
return a[wrapIndex(top -1, capacity)]
isempty():
return count == 0
count():
return count
clear():
top = count = 0
foreach():
tos = top
for i = 0, i < count, i++:
tos = wrapIndex(tos -1, capacity)
yield a[tos]
wrapIndex(i, n):
index = i %n
if index < 0:
index += n
return index
[Hat tip to 2048]
Friday, February 14, 2014
stack with array
stack
a = new T[SIZE]
i = 0
push(T x):
if i == a.length():
resize()
a[i++] = x
T pop():
if i == 0:
throw "empty"
return a[--i]
resize():
anew = new[a.length() * 2] //overflow possible
for j=0 to <a.length():
anew[j] = a[j]
a = anew
Monday, February 10, 2014
queue with 2 stacks, stack with 2 queues
queue with 2 stacks. for enqueue, out->in and push into in. for dequeue, in->out and pop from out.
stack with 2 queues. for push, enqueue into non-empty queue (or q2). for pop, dequeue all from non-empty queue, enqueue into other and return last.
enqueue(x):
while !out.isEmpty():
in.push(out.pop())
in.push(x)
x dequeue():
while !in.isEmpty():
out.push(in.pop())
return out.pop()
Elements in out are already in dequeue order so no need to move to in.
enqueue(x):
in.push(x)
x dequeue():
if !out.isEmpty():
while !in.isEmpty():
out.push(in.pop())
return out.pop()
stack with 2 queues. for push, enqueue into non-empty queue (or q2). for pop, dequeue all from non-empty queue, enqueue into other and return last.
push(T x):
if !q1.isEmpty():
q = q1
else
q = q2
q.enqueue(x)
T pop():
if q1.isEmpty() && q2.isEmpty():
throw "empty"
if !q1.isEmpty():
q = q1
other = q2
else
q = q2
other = q1
n = null
while !q.isEmpty():
n = q.dequeue()
if q.isEmpty():
break;
other.enqueue(n)
return n
Saturday, February 08, 2014
deque
node: next prev value dq : head, tail = null node enqueue(T t): n = new node(t) if head == tail == null: head = tail = n else: tail.next = n n.prev = tail tail = n return n T dequeue(): if head == null: throw "empty" n = head if head == tail: head = tail = null else: head = head.next n.next = head.prev = null return n.value delete(n): if n == null || head == null || tail == null: throw if n == head && head == tail: head = tail = null else if n == head: head = head.next n.next = head.prev = null else if n == tail: tail = tail.prev n.prev = tail.next = null else: n.prev.next = n.next n.next.prev = n.prev n.prev = n.next = null // make an existing node in deque the tail. // note: deque cannot be empty, as it has at least 1 node // to make the tail. makeTail(n): if n == null || head == null || tail == null: throw if n == tail: return if n == head: head = head.next head.prev = null else: n.prev.next = n.next n.next.prev = n.prev n.prev = n.next = null tail.next = n n.prev = tail tail = n
Subscribe to:
Posts (Atom)
