数据结构 Skip List 实现了二分查找

  • 数组可以用下标进行索引。
  • 一个排序的数组可以用二分法查找
  • 二分法需要计算「中间节点」的下标
  • 链表不能用下标进行索引
  • 因此链表不能直接使用二分法
  • 变通的方法是二分查找树,即二叉树
  • 插入元素时保持树的平衡很重要
  • Skip List 实质上也是实现了二分查找
  • 逐层抽样出「中间节点」

Node 节点

// 普通的 List Node
struct SkipNode {
  const char *key;
  Node *next[1];
}

Skip List 的 Node 需要多个 next 指针,分别用于不同的 level 。

// Skip List Node
struct SkipNode {
  const char *key;
  Node *next[1];
}

实际使用时 next 字段需要动态分配大小。

Skip List Search

具体到某个特定的 level, 是一个线性的 List Search,找到目标值所在的区间。

SkipNode * skiplist_search(
  const char *key,
  int level,
  SkipNode **head,
  SkipNode **tail
){
  SkipNode *node, *prev;

  for(node=*head, prev=NULL; node!=*tail; prev=node , node=node->next[level]){
    int rc = strcmp(key, node->key);
    if(rc>0){
      continue;
    } else if(rc==0){        // target found
      return node;
    } else if(rc<0){         // range found: (prev, node)
      *head = prev;
      *tail = node;
      return NULL;
    }
  }
                             // target miss
  *head = NULL;
  return NULL;
}

从最顶层开始,逐层向下逼近搜索结果。

SkipNode * skiplist_find(const char *key, SkipNode *head){
  const Node *sub_head = head;
  const Node *sub_tail = NULL;

  int level = skiplist_level(head);
  while(level--){
    int found = 0;
    Node *target = skiplist_search(key, level, &sub_head, &sub_tail);
    if(target!=NULL){
       return target;
    } else if(sub_head==NULL){
       break;
    }
  }
  return NULL;
}

需要知道 Skip List 的层数。一个“取巧”的办法是在 head 节点里间接地通过 next 数组的大小来反映。

int skiplist_level(SkipNode *head){
   int level = 0;
   while(head->next[level] != NULL) levle++;
   return level;
}

实际上就是下面这种技巧的应用。

const char *string_list[] = {
   "abc",
   "def",
   NULL
}