The three parts of an recursive algorithm are the base (or stopping) part, the recursive part, and the part that decides between the base and recursive part. In its simplest form, a recursive algorithm has an if statement with one branch that returns (or at least makes no further recursive calls) and another branch that calls the algorithm recursively.
The problem with wide size variations is running out of storage. Wide size variations wouldn’t cause storage exhaustion when 1) the maximum possible size variation can be covered by the maximum possible size (that is, the variations are large but the overall storage requirements are small) and 2) both the variations and maximum possible size is large, but can be covered by the allocation (that is, the maximum possible size is large, but can be covered by the allocation).
By the big-oh definition, there exists some function g such that Cf2(n) ≥ g(n) for all n sufficiently large, where f2(n) is the constant function that returns 2 for any value of n.
By arithmetic, f2 = 2f1, where f1(n) is the constant function that returns 1 for any value of n. Substituting back into the previous inequality gives
C2f1(n) ≥ g(n)C2 is just another constant, C' say, giving
C'f1(n) ≥ g(n)In other words, g is also O(1).
Each node in the node array is either being used or is free to be allocated. Initially all nodes are free to allocated, which means that initializing the node array involves making sure all nodes are marked as being free. How that gets done depends on how free nodes are marked.
One way to mark free nodes is to use a boolean, in which case initialization would be
for i = 1 to n nodes[i].free = true
where n
is the number of nodes in the node array. This code touches every
node, and is O(n).
Using a boolean per node requires searching the node array to allocate a node, which is also O(n). A free list provides a faster way to allocate nodes. All free nodes are linked together in a singly-linked list, and the free list points to the head of the list. Initially all nodes are on the free list, leading to the initialization code
for i = 1 to n - 1 nodes[i].left = i + 1 nodes[n].left = -1 free-list = 1
This initialization is also linear, but allocation now requires only a constant (and small) number of pointer manipulations.
Because initialization has to touch every node, it seems the lower bound would also have to be linear. It turns out that a small trick can be used to spread out and delay initialization to make it effectively constant.
The trick is to divide the node array into two parts: one part contains
initialized nodes that are being freed and allocated and the other part
contains uninitialized nodes not being used. Assume the right half of the node
array contains the uninitialized nodes and the variable first-uninit
is the
index of the first uninitialized node (that is, the leftmost end of the
uninitialized part). Initialization is now
free-list = -1 first-uninit = 1
In this case the initialized nodes are being managed by a free list. Initialization is constant time. Allocation is a bit more complicated, but it’s still constant time:
if free-list ≠ -1 // allocate a node. else // no more nodes; get an uninitialized node. if first-uninit > n // no more nodes, die. else first-uninit++ // allocate node
This page last modified on 15 September 2008. |