3.7

From The Algorithm Design Manual Solution Wiki
Jump to navigation Jump to search

Idea – keep a “pointer to the pointer that leads to each node”.
For every node x we store, in addition to key and next, a field pprev that points to the variable that currently holds the reference to x (either the list’s head or some other node’s next).

With this extra field
• changing the predecessor’s pointer becomes
*x->pprev = x->next
• and, if x is not the last node, we also repair
x->next->pprev = x->pprev
Both are O(1), so deletion is O(1) even when we start with a direct pointer to x only.

C-style pseudocode
struct Node { // one extra word
  Item key;
  Node *next;
  Node **pprev; // “pointer to the pointer that points to me”
};

Node *head = NIL;

// INSERT AT HEAD
void push_front(Item k){
  Node *x = new Node;
  x->key = k;
  x->next = head;
  x->pprev = &head
  if(head) head->pprev = &x->next;
  head = x;
}

// DELETE GIVEN ONLY A POINTER x
void erase(Node *x){
  if(x->next) x->next->pprev = x->pprev;
  *x->pprev = x->next;
  free(x);
}

// SEARCH (unchanged)
Node* find(Item k){ for(Node *p=head; p; p=p->next) if(p->key==k) return p; return NIL; }


Complexities
• push_front, erase: O(1) time, O(1) extra space per node (the pprev word).
• search: O(n) time (inherent for unordered lists).

Thus we fulfil the footnote’s promise: every list operation is as fast as in a doubly-linked list, yet we keep only one real next link; the second “link” is a light-weight pointer-to-pointer used solely for updates.


Back to Chapter 3