跳躍表(skip list)基本原理及C/C++實現
阿新 • • 發佈:2019-02-03
1、基本原理
不想好好寫字走的freestyle......其實可以直接看程式碼,我的註釋還是很詳細的,後面也有說明性的插圖。
後面關於時間複雜度的分析證明沒有貼(總之我們都知道它效能棒棒噠就行了)。
2、C/C++實現
比較符合MIT公開課的實現是這篇博文:跳躍表實現
而其他多數人都是抄襲這個實現:另一種實現
下面是自己的實現,因為最難的操作就是insert,所以目前只寫了insert:
#include<stdlib.h> #include<time.h> #include<iostream> #include<vector> using namespace std; #define Tail false // 表示拋硬幣得到反面 #define Face true// 表示拋硬幣得到正面 typedef int ElementType;// 定義儲存元素型別 // 定義連結串列節點 typedef struct Node { ElementType value;// 節點中儲存的資料 // 三個方向的指標,橫向是單鏈表,縱向是雙鏈表 Node* next; Node* up; Node* down; }Node; // 跳躍表結構定義 typedef struct SkipList { int level;// 記錄當前的跳躍表有幾層 vector<Node*> heads; // 管理頭節點,便於以後從最高層往最底層查詢 }SkipList; // 建立一個只含頭節點的表 SkipList* createSkipList() { SkipList* ptrSkipList=new SkipList; ptrSkipList->level=1; // 建立第一個節點 Node* head=new Node; head->value=INT_MIN;// 頭節點的資料統一設定為-∞ // 初始化三個指標為空 head->next=nullptr; head->up=nullptr; head->down=nullptr; ptrSkipList->heads.emplace_back(head);// 將節點指標加入vector srand(time(0));// 播撒隨機種子 return ptrSkipList; } // 產生隨機數,決定插入的元素要不要往上走 bool flipCoin() { if(rand()%2==1) return Face; else return Tail; } // 跳躍表的插入 void insert(SkipList* ptrSkipList,ElementType value) { // 首先插入最底層的連結串列,注意插入時要保證有序性 Node* ptrNewNode=new Node; Node* p,*q;// 輔助指標 ptrNewNode->value=value; ptrNewNode->next=nullptr; ptrNewNode->up=nullptr; ptrNewNode->down=nullptr; // 尋找插入的合適位置 for(p=ptrSkipList->heads[0]->next,q=ptrSkipList->heads[0];(p!=nullptr)&&(value>p->value);p=p->next,q=q->next) { // 注意不要把p=p->next;寫成p++;,會出事的 } // 應該插在q後面 q->next=ptrNewNode; ptrNewNode->next=p; // 拋硬幣,如果是反面,結束,否則向上走 int currentLevel=0;// 記錄當前的層數,方便後面的連線,0在這裡是一個沒有意義的數,最小的level是1 Node* move=ptrNewNode;// 輔助指標move,每次promote指向相同的節點 // 開始拋硬幣,如果是正面就上升(promote) while(flipCoin()!=Tail) { cout<<"插入"<<value<<"時得到正面"<<endl; currentLevel++; // 新建一個節點包含這個元素 Node* promote=new Node; promote->value=value; // 和下面含有相同元素的節點互連 move->up=promote; promote->down=move; promote->next=nullptr; move=promote;// 更新move // 如果沒有頭節點,新建一個頭節點,每次頭節點也要promote if(ptrSkipList->heads.size()<currentLevel+1) { Node* head=new Node; head->value=INT_MIN; head->next=nullptr; // 連線兩個頭節點 ptrSkipList->heads[currentLevel-1]->up=head; head->down=ptrSkipList->heads[currentLevel-1]; ptrSkipList->heads.emplace_back(head);// 加入新的頭節點 ptrSkipList->level++;// 更新層數 } // 最後連線相同level的節點 Node* r,*s;// 輔助指標 for(r=ptrSkipList->heads[currentLevel]->next,s=ptrSkipList->heads[currentLevel];(r!=nullptr)&&(value>r->value);r=r->next,s=s->next) { // promote的時候也要保證元素之間的順序,從小到大排列 } s->next=move; move->next=r; } cout<<value<<"插入完畢"<<endl; } int main(int argc, char* argv[]) { SkipList* mylist=createSkipList(); insert(mylist,4); insert(mylist,3); insert(mylist,0); insert(mylist,13); insert(mylist,2); insert(mylist,50); insert(mylist,7); cout<<"跳躍表層數:"<<mylist->level<<endl; for(int i=mylist->level-1;i>=0;i--) { cout<<"第"<<i+1<<"層元素是:"; for(Node* p=mylist->heads[i];p!=nullptr;p=p->next){ cout<<p->value<<" "; } cout<<endl; } }
寫完以後才發現橫向上其實也需要兩個指標。。。這樣查詢演算法就比較貼近公開課裡講的了,看起來更優雅,不過一個指標其實也是可以的,反正萬變不離其宗,將就吧。。
這是測試結果:
做了一個視覺化:
真美啊~