問題: 2.2 若一個表從不修改,則可使用一種更爲簡單的方法來實現表的元素的查找。爲了有效地訪問第 i 個元素,向單鏈表的每個元素中添加第二個指針,使其指向表中其它元素來減少查找所需時間。
需要自己注意的是在獨立實現代碼的時候不僅僅只是需要簡單的瞭解大概的算法,而是
1)確定需要使用的數據結構,如今天的代碼中的SKNode, SkipList.
2)如何初始化這個數據結構,這樣才能得到求解問題循環結束條件。(初始化條件很重要)
3)先自己按照算法流程,插入、查找、刪除一個一個寫好僞代碼,這樣才不至於在寫代碼的時候沒有關注點,思維混亂。可以先從最簡單也是最和核心的查找算法寫起。
4)最後是調試,碰到不對的步驟,可以添加打印,我現在對於數據結構還有太底層的調試時看不出來太多信息的,只能一步一步的試探。這也是一個軟肋,以後有空一定要再學學編程範式這種比較底層的東西,還有內存管理也很重要。
參考的博文地址如:
跳躍鏈表簡介
二叉樹是一種常見的數據結構。它支持包括查找、插入、刪除等一系列操作。但它有一個致命的弱點,就是當數據的隨機性不夠時,會導致其樹形結構的不平衡,從而直接影響算法的效率。
跳躍鏈表(Skip List)是1987年才誕生的一種嶄新的數據結構,它在進行查找、插入、刪除等操作時的期望時間複雜度均爲O(logn),有着近乎替代平衡樹的本領。而且最重要的一點,就是它的編程複雜度較同類的AVL樹,紅黑樹等要低得多,這使得其無論是在理解還是在推廣性上,都有着十分明顯的優勢。
跳躍鏈表的最大優勢在於無論是查找、插入和刪除都是O(logn),不過由於跳躍鏈表的操作是基於概率形成的,那麼它操作複雜度大於O(logn)的概率爲,可以看出當n越大的時候失敗的概率越小。
另外跳躍鏈表的實現也十分簡單,在平衡樹中是最易實現的一種結構。例如像複雜的紅黑樹,你很難在不依靠工具書的幫助下實現該算法,但是跳躍鏈表不一樣,你可以很容易在半個小時內就完成其實現。
跳躍鏈表的空間複雜度的期望爲O(n),鏈表的層數期望爲O(logn).
如何改進普通的鏈表?
我們先看看一個普通的鏈表
可以看出查詢這個鏈表O(n),插入和刪除也是O(n).因此鏈表這種結構雖然節省空間,但是效率不高,那有沒有什麼辦法可以改進呢?
我們可以增加一條鏈表做爲快速通道。這樣我們使用均勻分佈,從圖中可以看出L1層充當L0層的快速通道,底層的結點每隔固定的幾個結點出現在上面一層。
我們這裏主要以查找操作來介紹,因爲插入和刪除操作主要的複雜度也是取決於查找,那麼兩條鏈表查找的最好的時間複雜度是多少呢?
一次查找操作首先要在上層遍歷<=|L1|次操作,然後在下層遍歷<=(L0/L1)次操作,至多要經歷
次操作,其中|L1|爲L1的長度,n爲L0的長度.
那麼最好的時間複雜度,也就怎麼設置間隔距離才能使查找次數最少有
我們對|L1|的長度求導得
把上式代入函數,查找次數最小也就是.這意味着下層每隔個結點在上層就有一個結點作爲快速跑道。
那麼三條鏈表呢
同理那麼我們讓L2/L1=L1/L0,然後同樣列出方程,求導可得L2=,查找次數爲3*
........................................................................
第k條鏈條.....查找次數爲
我們這裏取k=logn,代入的查找次數爲2logn.
到此爲主,我們應該知道了,期望上最好的層數是logn層,而且上下層結點數比爲2,這樣查找次數常數最小,複雜度保持在O(logn)。
跳躍鏈表的結構
跳躍表由多條鏈構成(L0,L1,L2 ……,Lh),且滿足如下三個條件:
- 每條鏈必須包含兩個特殊元素:+∞ 和 -∞(可以需要,可以不需要,我的實現是採用了-∞作爲header之後的 節點,即第一個節點,+∞作爲最後一個節點)
- L0包含所有的元素,並且所有鏈中的元素按照升序排列。
- 每條鏈中的元素集合必須包含於序數較小的鏈的元素集合。
結點結構源代碼
我覺得如果不考慮空間效率是可以將數據在每層上面分開存儲,但是也可以辦到在一個n的空間存儲,跳躍表就是在這些數據之間建立links,使用links替代樹形結構,如Btreap樹堆,R-B Trees, AVL trees。 現在我給的是基於一個n空間存儲。
- class SKNode
- {
- public:
- int key;
- SKNode* forward[MAXLEVEL];
-
- SKNode()
- {
- key=0;
- for(int i =0;i<MAXLEVEL;i++)
- {
- forward[i]= NULL;
- }
- }
- SKNode& operator=(const SKNode* & node)
- {
- key=node->key;
- for(int i=0;i<MAXLEVEL;i++)
- {
- forward[i] = node->forward[i];
- }
- return *this;
- }
- };
- //skip list, it has a header, this header have maxlevel pointers
- class SkipList
- {
- public:
- SKNode *hdr; /* list Header */
- int listLevel; /* current level of list */
- int insert(int key);
- SKNode* search(int key);
- int deleteNode(int key);
- void printList();
- SkipList()
- {
- hdr = new SKNode;
- listLevel = 0;
- hdr->key = -INT_MAX;
- SKNode* end = new SKNode;
- SKNode* first = new SKNode;
- first->key=-INT_MAX;
- end->key=INT_MAX;
- for(int i =0;i<MAXLEVEL;i++)
- {
- hdr->forward[i]=first;
- hdr->forward[i]->forward[i] = end;
- }
- printList();
- }
- ~SkipList()
- {
- delete hdr;
- }
- };
MAXlevel可以是log(n)/log(2)
跳躍鏈表查找操作
目的:在跳躍表中查找一個元素x
在跳躍表中查找一個元素x,按照如下幾個步驟進行:
1、 p = hdr; 從最上層的鏈(Lh)的開頭開始,進入到各層
2、 for i(listLevel-1, 0) , 從最上層的鏈(Lh)的開頭開始, 如L3到L0
3、假設當前位置爲p,p初始值爲hdr, p在i層的下一個節點爲q = p->forward[i], 它向右指向的節點爲q(p與q不一定相鄰)。將x和q.key做比較
(1)if x>q.key 在i層繼續向右走,
then p = p->forward[i].
(2) else x<=q.key,
then i--, 即將當前指針直接下移到下一層。
for循環結束,i變爲了0, 即在最後一層, p = p->foward[0]
4、判斷p,
(1) if p!= NULL && p->key = x
return p
(2) return NULL
如我們查找29, 先從入口到l3,29>11,p = 11, 判斷知道往下走L2,29<30, 繼續往下L1,29<30,L1,29>15, 前進,29<=29, 結束, p在15的位置,所以最後指針調整到p = p->forward[0], 判斷p值。
(今天因爲我從listLevel開始,浪費了好多調試時間。。。。)
元素53的查找路徑
下面是C++實現代碼:
- SKNode* SkipList::search(int key)
- {
- SKNode* current = new SKNode;
- current = hdr;
- int i = listLevel-1;
- for(;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- //否則i--,轉到下一層
- }
- current = current->forward[0];
- if(current!= NULL && current->key == key)
- {
- cout<<"find"<<key<<endl;
- return current;
- }
- return NULL;
-
- }
跳躍鏈表插入操作
目的:向跳躍表中插入一個元素x
首先明確,向跳躍表中插入一個元素,相當於在表中插入一列從S0中某一位置出發向上的連續一段元素。有兩個參數需要確定,即插入列的位置以及它的“高度”。
1)關於在L0層的插入的位置,我們先利用跳躍表的查找功能, x<=q.key,所以x的位置一定是在x之後,q之前。 同樣可以推論在L1,L2 ,L listLevel層的位置是在循環中生成他們這層最後的位置,就是在search的while之後記錄一個這個位置爲S[i]。最後需要在所有的S[i]之後重連數據之間的鏈接。 (但是需要注意的是爲了,如果我們不想加入重複數據,需要判斷p->forward[0]的值,如果相等,就是找到了,不需要再插入。當然如果我們不介意重複數據,也可以不加這個判斷。)
2)需要插入的高度,決定在L1-listLevel的哪些位置加。 而插入列的“高度”較前者來說顯得更加重要,也更加難以確定。由於它的不確定性,使得不同的決策可能會導致截然不同的算法效率。爲了使插入數據之後,保持該數據結構進行各種操作均爲O(logn)複雜度的性質,我們引入隨機化算法(Randomized Algorithms)。
僞代碼如下:
Skip List Insertion(x)
p = hdr;
newlevel = getlevel();
s[listLevel] = hdr(注意需要全部初始化爲hdr,爲了newlevel增長了,但是增長的層次s[i]卻沒有數據,沒有初始化,應該從頭結點開始)
for i(listLevel-1, 0)
while(q!=+∞ && x>key)
then p = p->forward[i].
//else x<=q.key,
//then i--
s[i]=p; (s[i]爲i層探索的最後一個節點,最後需要在這之後插入x)
last = p->forward[0] //判斷是否相等,。。。。
//插入數據,重連鏈表
if(newlevel>level) level = newlevel
for i(newlevel-1 - 0)
node->forward[i]= s[i]->forward[i]
s[i]->forward[i] = node
我定義一個隨機決策模塊,它的大致內容如下,但是這個代碼不能保證完全隨機,其實每次的運行結果都是一樣的:
- int getInsertLevel()
- {
- int upcount = 0;
- for(int i=0;i<MAXLEVEL;i++)
- {
- int num = rand()%10;
- if(num<5)
- {
- upcount++;
- }
- }
- return upcount;
- }
如插入43,查找路徑如下。
43的下一個數接到的40下一個數45。
40的下一個數接到43
紫色的箭頭表示更新過的指針
- int SkipList::insert(int key)
- {
- int level = getInsertLevel();
- SKNode* node = new SKNode;
- node->key=key;
-
- SKNode *s[MAXLEVEL];
- SKNode* current = new SKNode;
- SKNode* last = new SKNode;
- for(int i =0;i<MAXLEVEL;i++)
- {
- s[i]=hdr->forward[i];//initiation
- }
- current = last = hdr;
- cout<<"hdr"<<hdr->key<<endl;
- int i = listLevel-1;
- for(;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- s[i] = current;//保存每一層位置上的最後指針的前驅
- }
- last=current->forward[0];
- if(last != NULL && last->key == key)
- {
- cout<<"inset key:"<<key<<"already existed"<<endl;
- return 0;
- }
- if(level>listLevel)//更新層數
- {
- listLevel = level;
- }
-
- for(int k = 0; k <listLevel;k++)
- {
- node->forward[k]=s[k]->forward[k];
- s[k]->forward[k]=node;
-
- }
- if(level>listLevel)
- {
- listLevel = level;
- }
- return 1;
-
- }
跳躍鏈表的刪除
目的:從跳躍表中刪除一個元素x
刪除鏈表和插入幾乎一模一樣的,只是在最後重接鏈表不同:
在跳躍表中查找到這個元素的位置,如果未找到,則退出
否則將該元素所在整列從表中刪除
將多餘的“空鏈”刪除
Skip List Deletion(x)
p = hdr;
//newlevel = getlevel();
s[listLevel] = hdr(注意需要全部初始化爲hdr,爲了newlevel增長了,但是增長的層次s[i]卻沒有數據,沒有初始化,應該從頭結點開始)
fori(listLevel-1, 0)
while(q!=+∞ && x>key)
then p = p->forward[i].
//else x<=q.key,
//then i--
s[i]=p; (s[i]爲i層探索的最後一個節點,最後需要在這之後插入x)
last = p->forward[0] //判斷是否相等,。。。。
if(last->key != x)
return
//刪除數據,重連鏈表
for i(listlevel-1 - 0)
s[i]->forward[i]=s[i]->forward[i]->forward[i];
這段代碼如下:
- intSkipList::deleteNode(int key)
- {
- SKNode *s[MAXLEVEL];
- SKNode* current = new SKNode;
- SKNode* last = new SKNode;
- for(inti =0;i<MAXLEVEL;i++)
- {
- s[i]=hdr->forward[i];//initiation
- }
- current = last = hdr;
- for(inti = listLevel-1;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- s[i] = current;//保存每一層位置上的最後指針的前驅
- }
- last=current->forward[0];
- if(last->key != key)
- {
- cout<<"delete key:"<<key<<"does not existed"<<endl;
-
- return 0;
- }
- for(inti = 0; i<listLevel;i++)
- {
- s[i]->forward[i]=s[i]->forward[i]->forward[i];
- }
- return 1;
- }
整個C++程序如下
- #include <iostream>
- #include <vector>
- using namespace std;
-
- #define MAXLEVEL 4 //最多2 power n=16個數
- /*skip list node,they are keys and pointers*/
- classSKNode
- {
- public:
- int key;
- SKNode* forward[MAXLEVEL];
-
- SKNode()
- {
- key=0;
- for(inti =0;i<MAXLEVEL;i++)
- {
- forward[i]= NULL;
- }
- }
- SKNode& operator=(constSKNode* & node)
- {
- key=node->key;
- for(inti=0;i<MAXLEVEL;i++)
- {
- forward[i] = node->forward[i];
- }
- return *this;
- }
- };
- //skip list, it has a header, this header have maxlevel pointers
- classSkipList
- {
- public:
- SKNode *hdr; /* list Header */
- intlistLevel; /* current level of list */
- int insert(int key);
- SKNode* search(int key);
- intdeleteNode(int key);
- voidprintList();
- SkipList()
- {
- hdr = new SKNode;
- listLevel = 0;
- hdr->key = -INT_MAX;
- SKNode* end = new SKNode;
- SKNode* first = new SKNode;
- first->key=-INT_MAX;
- end->key=INT_MAX;
- for(inti =0;i<MAXLEVEL;i++)
- {
- hdr->forward[i]=first;
- hdr->forward[i]->forward[i] = end;
- }
- printList();
- }
- ~SkipList()
- {
- deletehdr;
- }
- };
- intgetInsertLevel()
- {
- intupcount = 0;
- for(inti=0;i<MAXLEVEL;i++)
- {
- intnum = rand()%10;
- if(num<5)
- {
- upcount++;
- }
- }
- returnupcount;
- }
- SKNode* SkipList::search(int key)
- {
- SKNode* current = new SKNode;
- current = hdr;
- inti = listLevel-1;
- for(;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- //否則i--,轉到下一層
- }
- current = current->forward[0];
- if(current!= NULL && current->key == key)
- {
- cout<<"find"<<key<<endl;
- return current;
- }
- return NULL;
-
- }
-
- intSkipList::insert(int key)
- {
- int level = getInsertLevel();
- SKNode* node = new SKNode;
- node->key=key;
-
- SKNode *s[MAXLEVEL];
- SKNode* current = new SKNode;
- SKNode* last = new SKNode;
- for(inti =0;i<MAXLEVEL;i++)
- {
- s[i]=hdr->forward[i];//initiation
- }
- current = last = hdr;
- cout<<"hdr"<<hdr->key<<endl;
- inti = listLevel-1;
- for(;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- s[i] = current;//保存每一層位置上的最後指針的前驅
- }
- last=current->forward[0];
- if(last != NULL && last->key == key)
- {
- cout<<"inset key:"<<key<<"already existed"<<endl;
- return 0;
- }
- if(level>listLevel)//更新層數
- {
- listLevel = level;
- }
-
- for(int k = 0; k <listLevel;k++)
- {
- node->forward[k]=s[k]->forward[k];
- s[k]->forward[k]=node;
-
- }
- if(level>listLevel)
- {
- listLevel = level;
- }
- return 1;
-
- }
- intSkipList::deleteNode(int key)
- {
- SKNode *s[MAXLEVEL];
- SKNode* current = new SKNode;
- SKNode* last = new SKNode;
- for(inti =0;i<MAXLEVEL;i++)
- {
- s[i]=hdr->forward[i];//initiation
- }
- current = last = hdr;
- for(inti = listLevel-1;i>=0;i--)
- {
- while(current->forward[i]->key != INT_MAX && key>current->forward[i]->key)//key大於下一個數據的值。轉到本層下一個元素
- {
- current = current->forward[i];
- }
- s[i] = current;//保存每一層位置上的最後指針的前驅
- }
- last=current->forward[0];
- if(last->key != key)
- {
- cout<<"delete key:"<<key<<"does not existed"<<endl;
-
- return 0;
- }
- for(inti = 0; i<listLevel;i++)
- {
- s[i]->forward[i]=s[i]->forward[i]->forward[i];
- }
- return 1;
- }
- voidSkipList::printList()
- {
- SKNode* current = hdr;
- for(inti = listLevel -1;i>=0;i--)
- {
- current = hdr->forward[i];
- cout<<"level "<<i<<"................................"<<endl;
- while(current->forward[i] != NULL)//key大於下一個數據的值。轉到本層下一個元素
- {
- cout<<" "<<current->key;
- current = current->forward[i];
- }
- cout<<" "<<current->key<<endl;
- }
- }
-
- int main()
- {
- SkipListsk;
- constint n = 7;
- intnum[n]={30,15,45,37,11,53,17};
- cout<<"test insert............."<<endl;
- for(inti = 0;i<n;i++)
- {
- sk.insert(num[i]);
- }
- sk.printList();
- cout<<"test search............"<<endl;
- sk.search(17);
- cout<<"test delete................."<<endl;
- sk.deleteNode(30);
- sk.printList();
- system("pause");
- return 0;
- }
跳躍鏈表的搜索時間複雜度爲O(logn)
定理:n個元素的跳躍鏈表的每一次搜索的時間複雜度有很高的概率爲O(logn).
高概率:事件E以很高的概率發生意味着對於a>=1,存在一個合適的常數使得事件E發生的概率Pr{E}>=1-O(1/n^a).
其中a是任意選擇的一個數,不同的a影響搜索時間複雜度的常數,即a*O(logn),這個在後面介紹.
我們要證跳躍鏈表的時間複雜度,不能只是證明一次搜索的複雜度爲,是要證明全部的搜索都是O(logn),因爲這是基於概率的算法,如果光一次有效率並沒有多大作用.
我們定義時間Ei爲某一次搜索失敗的概率,那麼假設k次搜素,我們先假定失敗的概率爲O(1/n^a),其中至少有一次失敗的概率爲
可以估算出k次有一次失敗的概率爲1/n^(a-c),那麼我們只要讓a>=c+1或者a取無窮大,就可以證明每一次搜索都具有高概率成功。