Red/black deletion Augmenting data structures Red/black deletion Say we want to delete a node a. If a has two children, we find the successor of a in the tree (which must be an incomplete node), replace the key of a with the key of the successor, and delete the successor. If a has one child, we replace the key at a with the key of the child (which must be either the successor or predecessor of a), then delete the child. Thus we can assume a is a leaf. If a is red, we can just delete it--the red/black properties (1) and (2) from last lecture are preserved. So assume a is black. By (1), sibling(a) must exist. Case 1: sibling(a) is red. By (1), both a's niece and nephew must exist. By (2), niece(a), nephew(a), and parent(a) must all be black. Rotate once and recolor as shown. * * / \ ==> / \ a * o o * / \ / \ * * a * * Thus we can assume that sibling(a) is black. Case 2,3,4: at least one of niece(a), nephew(a) exists. We can resolve the situation by rotation and recoloring so that a ends up red and can be deleted. c c / \ 1 / \ a * * ==> * * / \ / \ o o a o o c c / \ 2 / \ a * * ==> * * / / o a o c c / \ 1 / \ a * * ==> * * \ / o a o Case 5: neither niece(a) nor nephew(a) exists, parent(a) is red. Just recolor and delete a. o * / \ ==> / \ a * * a o o Case 6: neither niece(a) nor nephew(a) exists, parent(a) is black. This is the difficult case. Put an extra black token on parent(a). Recolor a, sibling(a) red. Delete a. * ** / \ ==> / \ a * * a o o Now we have to figure out how to get rid of the extra token. Case 1: the token is on the root. Just remove it. Case 2: the token is on a red node. Recolor the node black and remove the token. Otherwise the token is on a black node. Both the niece and nephew must exist by (1). Case 3: sibling is red. By (2), the parent, niece, and nephew must be black. Rotate and recolor as shown. * * / \ ==> / \ ** o o * / \ / \ * * ** * Now sibling is black, so we are in one of the other cases. Assume then that the sibling is black. Cases 4: nephew and niece are black: recolor sibling red, move token to parent. This pushes the problem up in the tree. c c* / \ / \ ** * ==> * o / \ / \ * * * * This can temporarily introduce a red violation if c is red, but only if it will be resolved by case 2 in the next step. Cases 5,6: one of nephew, niece is red. Rotate and recolor as shown. c c / \ / \ ** * ==> * * / \ / \ d o * d c c / \ / \ ** * ==> / \ / \ / \ o d * * / \ / \ / \ * * * * * d All the other cases are symmetric. Augmenting Data Structures We have shown how to maintain a balanced tree scheme that supports insert, delete, lookup, max, min, predecessor, and successor in O(log n) time per operation. By extending the data structure with extra pointers and fields, we can often get more at very little extra cost. Example: return the i^th smallest element in O(log n) time. Extra info: keep the number of nodes in each subtree at the root of the subtree. To find the i^th element, walk down the tree starting at the root. If the left subtree contains exactly i-1 nodes, the current node is the i^th. If the left subtree contains more than i-1 nodes, the i^th is in the left subtree. If the left subtree contains less than i-1 nodes, the i^th is in the right subtree. But we have to show how to maintain this extra info efficiently. Insert: go up the tree, incrementing each node on the path from the new node up to the root. Delete: same, but decrement. Rotate: change the counts locally. a+b+c+2 a+b+c+2 / \ ==> / \ a b+c+1 a+b+1 c / \ / \ b c a b Example: min, max in O(1). Extra info: pointers to the leftmost and rightmost node in the tree, respectively. Insert: if the new key is smaller than the current min, update the min pointer. Similarly for max. Delete: if the element being deleted is the min, find its successor (which is its right child if it exists, otherwise its parent) and update the min pointer. Similarly for max. Rotate: nothing to do. Example: predecessor, successor in O(1). Extra info: links at each node pointing to predecessor and successor of the node. Insert: if I am a left child: -- my successor := my parent -- my predecessor := my parent's old predecessor -- my parent's old predecessor's successor := me -- my parent's predecessor := me Right child case is symmetric. This is like linking a new element into a doubly linked list. Rotate: nothing to do--rotations do not change predecessor/successor relationships. Example: median element We can find the median in O(log n) time by the above construction for finding the i^th element: just take i = n/2. But we can do better: O(1). Extra info: two extra variables: median -- pointer to the current median diff -- (# of nodes < median) - (# of nodes > median) diff=0 if the tree has an odd number of nodes, otherwise either +1 or -1. Insert: if (new key > median) diff++; else diff--; if (diff == 2) { median = median.successor; diff = 0; } if (diff == -2) { median = median.predecessor; diff = 0; } Delete: similar. Rotate: nothing to do. Rotate does not change the median.