摘要:從這里我們可以看到,從插入時(shí)我們只要保證上一層的元素個數(shù)為下一層元素個數(shù)的,我們的跳躍表就能成為理想的跳躍表。
數(shù)據(jù)結(jié)構(gòu)之跳躍鏈表 簡介
原理總的來說跳躍鏈表最大的好處就是提高了檢索了的速率,可以說說是大幅度的提高,相對于單鏈表來說是一種高效率的檢索結(jié)構(gòu)
代碼實(shí)現(xiàn)(java語言) 節(jié)點(diǎn)定義跳躍表的結(jié)構(gòu)是:假如底層有10個節(jié)點(diǎn), 那么底層的上一層理論上就有5個節(jié)點(diǎn),再上一層理論上就有2個或3個節(jié)點(diǎn),再上一層理論上就有1個節(jié)點(diǎn)。所以從這里可以看出每一層的節(jié)點(diǎn)個數(shù)為其下一層的1/2個元素,以此類推。從這里我們可以看到,從插入時(shí)我們只要保證上一層的元素個數(shù)為下一層元素個數(shù)的1/2,我們的跳躍表就能成為理想的跳躍表。那么怎么才能保證我們插入時(shí)上層元素個數(shù)是下層元素個數(shù)的1/2呢,?很簡單 拋硬幣就可以解決了,假設(shè)元素X要插入跳躍表,最底層是肯定要插入X的,那么次低層要不要插入呢,我們希望上層元素個數(shù)是下層的1/2,那么我們有1/2的概率要插入到次低層,這樣就來拋硬幣吧,正面就插入,反面就不插入,次次底層相對于次低層,我們還是有1/2的概率插入,那么就繼續(xù)拋硬幣吧 , 以此類推元,素X插入第n層的概率是(1/2)的n次。這樣,我們能在跳躍表中插入一個元素了?;镜臉幼尤缦聢D:
package skip; public class Node { public Integer value; //插入的數(shù)據(jù) public Node left; //分別對應(yīng)的四個方向的指針 public Node down; public Node right; public Node up; public Node(Integer value) //構(gòu)造函數(shù) { this.value=value; down=up=right=left=null; } }表的實(shí)現(xiàn)
package skip; import java.util.Random; public class SkipList { private Node head; //最上面一側(cè)的頭結(jié)點(diǎn),這里使用的是雙鏈表 private Node tail; //最上面一層的尾節(jié)點(diǎn),這里的頭尾節(jié)點(diǎn)是不存儲數(shù)據(jù)的,數(shù)據(jù)域全是null private int level; //表中的最高的層數(shù),就是總共的層數(shù) private int size; //插入節(jié)點(diǎn)的個數(shù),頭尾節(jié)點(diǎn)除外 private Random random; //用來判斷是否需要增加高度的隨機(jī)函數(shù) public SkipList() { level = 0; //level默認(rèn)是0層,就是只有最下面的一層 head = new Node(null); tail = new Node(null); head.right = tail; //這里初始化成一個只有一層的雙鏈表 tail.left = head; size = 0; //size初始化為0 random = new Random(); } //這個函數(shù)的作用是找到插入節(jié)點(diǎn)的前面一個節(jié)點(diǎn),這里默認(rèn)的是將表升序存儲 public Node findFirst(Integer value) { Node p = head; while (true) { //判斷要插入的位置,當(dāng)沒有查到尾節(jié)點(diǎn)并且要插入的數(shù)據(jù)還是比前面的大的話,就將節(jié)點(diǎn)右移,知道找到合適的位置 //這里需要注意的是這里的head代表不一定是最底層的,因此這里的查找都是從最高層進(jìn)行查找的,如果滿足條件就要向下移動 //直到最底層 while (p.right.value != null && p.right.value <= value) { p = p.right; } //向下移動,直到到達(dá)最后一層 if (p.down != null) { p = p.down; } else { //到達(dá)最底層跳出即可 break; } } return p; //此時(shí)這里的p就是要插入節(jié)點(diǎn)的前面一個節(jié)點(diǎn) } //這是插入函數(shù),這里先執(zhí)行插入,然后判斷是否需要增加高度 public void insert(int value) { Node curr = findFirst(value); //先找到插入位置的前面一個節(jié)點(diǎn) Node q = new Node(value); //新建一個插入的節(jié)點(diǎn) //下面執(zhí)行插入步驟,這個和雙鏈表是一樣的步驟 q.right = curr.right; q.left = curr; curr.right.left = q; curr.right = q; int i = 0; //表示當(dāng)前節(jié)點(diǎn)所在的層數(shù),開始插入的是在下面插入的,所以開始的時(shí)候是在0層 //這里判斷是否需要增加高度,每一層相對域下面來說都有二分之一的概率,也就是說每一層增加的概率是(1/2)^n //通俗的說就是每一層的節(jié)點(diǎn)是將會保證是下面一層的1/2 while (random.nextDouble() < 0.5) { if (i >= level) { //如果當(dāng)前插入的節(jié)點(diǎn)所處的層數(shù)大于等于最大的層數(shù),那么就需要增加高度了,因?yàn)檫@里要保證頭尾節(jié)點(diǎn)的高度是最高的 //下面的代碼就是在頭尾節(jié)點(diǎn)的上插入新的節(jié)點(diǎn),以此來增加高度 Node p1 = new Node(null); Node p2 = new Node(null); p1.right = p2; p1.down = head; p2.left = p1; p2.down = tail; head.up = p1; //將頭尾節(jié)點(diǎn)上移,成為最頂層的節(jié)點(diǎn),這就是為什么每次插入和查詢的時(shí)候都是最上面開始查詢的,因?yàn)檫@里的head默認(rèn)的就是從最上面開始的 tail.up = p2; head = p1; tail = p2; level++; //最高層數(shù)加一 } while (curr.up == null) { //當(dāng)然增加高度就是在插入節(jié)點(diǎn)上面新插入一個節(jié)點(diǎn),然后將之與插入的節(jié)點(diǎn)相連 //既然這里新插入節(jié)點(diǎn)增高了,那么就需要找到與新插入節(jié)點(diǎn)上面的那個節(jié)點(diǎn)相連接,這里我們將新插入節(jié)點(diǎn)的前面的同等高度的節(jié)點(diǎn)與之相連 curr = curr.left; } curr = curr.up; //通過前面的一個節(jié)點(diǎn)找到與之相連的節(jié)點(diǎn) //下面就是創(chuàng)建一個節(jié)點(diǎn)插入到插入節(jié)點(diǎn)的頭上以此來增加高度,并且這個節(jié)點(diǎn)與前面一樣高的節(jié)點(diǎn)相連 Node e = new Node(value); e.left = curr; e.right = curr.right; curr.right.left = e; //此時(shí)的curr就是與之同等高度的節(jié)點(diǎn) curr.right = e; e.down = q; q.up = e; q = e; //將新插入的節(jié)點(diǎn)上移到最上面,因?yàn)楹竺婵赡苓€要在這里增加高度,就是在最上面插入新的一模一樣的節(jié)點(diǎn) i++; //增加當(dāng)前所處的高度,這里一定能要記得寫上,如果還要繼續(xù)增加的話,需要判讀是否需要增加頭尾節(jié)的高度 } size++; //節(jié)點(diǎn)加一 } //下面是打印每一層節(jié)點(diǎn)的情況 public void display() { while (level >= 0) { Node p = head; while (p != null) { System.out.print(p.value + "-------->"); p = p.right; } System.out.println(); System.out.println("*****************************"); level--; head = head.down; } } /*在鏈表中查找某個值是否存在,如果存在找到的節(jié)點(diǎn),當(dāng)然先從最高層開始查找,如果找到了在比這個值小的最后一個值,那么就順著這個值的下面開始尋找,按照上面的步驟 再次尋找,如過這個值正好等于要找的值,就返回true,形象的來說就是形成一個梯度的感覺。注意這里返回的節(jié)點(diǎn)一定是最底層的節(jié)點(diǎn),利于下面的刪除操作 * */ public Node search(int value) { Node p = head; while (true) { /*這里一定要寫成p.right.value!=null,如果寫成p.right!=null運(yùn)行可能有錯誤, 因?yàn)檫@里的尾節(jié)點(diǎn)的值為null,但是它的節(jié)點(diǎn)不是空的,如果成這樣的話,那么節(jié)點(diǎn)可能會找到尾節(jié)點(diǎn)都沒有找到,此時(shí)在判斷value的值就出現(xiàn)錯誤 相當(dāng)與判斷tail.right.value<=value,這個肯定是不行的,因?yàn)檫@個節(jié)點(diǎn)不存在,是空的更別說值了 */ //從最高層開始判斷找到比這個小的最后一個值,就是找到一個節(jié)點(diǎn)的前面比value小的,后面的節(jié)點(diǎn)的值比value大的 while (p.right.value != null && p.right.value <= value) { p = p.right; //如果沒有找到就后移直到找到這個節(jié)點(diǎn) } //如果找到的這個節(jié)點(diǎn)不是最底層的話,就向下移動一層,然后循環(huán)再次尋找,總之就是從最高層開始,一層一層的尋找 if (p.down != null) { //這個表示上面的循環(huán)沒有找到的相等的,那么就向下移動一層 p = p.down; } else { //如果到了最底層了,這里的值仍然沒有找到這個值,那么就表示不存在這個值 if (p.value == value) { //判斷是否存在value相等的值 // System.out.println(p.value + "----->"); return p; //返回節(jié)點(diǎn) } return null; //仍然沒有找到返回null } } } /* 這里是利用上面的查找函數(shù),找到當(dāng)前需要刪除的節(jié)點(diǎn),當(dāng)然這個節(jié)點(diǎn)是最底層的節(jié)點(diǎn),然后循環(huán)從最底層開始刪除所有的節(jié)點(diǎn) * */ public void delete(int value) { Node temp = search(value); //這里返回的必須是最底層的節(jié)點(diǎn),因?yàn)橐獜淖钕旅娴耐厦嫒縿h除所有層的節(jié)點(diǎn),否則的話可能在某一層上仍然存在這個節(jié)點(diǎn) while (temp != null) { temp.left.right = temp.right; temp.right.left = temp.left; temp = temp.up; //節(jié)點(diǎn)上移,繼續(xù)刪除上一層的節(jié)點(diǎn) } } public static void main(String args[]) { SkipList skipList = new SkipList(); Random random = new Random(); skipList.insert(33); skipList.insert(44); skipList.insert(11); skipList.insert(10); skipList.insert(22); skipList.insert(22); for (int i = 0; i < 500; i++) { int value = (int) (random.nextDouble() * 1000); skipList.insert(value); // System.out.println(value); } Node p = skipList.search(22); if (p != null) { System.out.println(p.value); } else System.out.println("沒有找到"); skipList.delete(33); skipList.display(); } }源碼地址
參考文章跳躍鏈表
雙鏈表
單鏈表
更多文章請移步本人博客java實(shí)現(xiàn)跳躍鏈表
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/69998.html
摘要:跳躍表的空間復(fù)雜度為。不過,二叉查找樹是有可能出現(xiàn)一種極端的情況的,就是如果插入的數(shù)據(jù)剛好一直有序,那么所有節(jié)點(diǎn)會偏向某一邊。例如這種接結(jié)構(gòu)會導(dǎo)致二叉查找樹的查找效率變?yōu)檫@會使二叉查找樹大打折扣。假如我們要用某種數(shù)據(jù)結(jié)構(gòu)來維護(hù)一組有序的int型數(shù)據(jù)的集合,并且希望這個數(shù)據(jù)結(jié)構(gòu)在插入、刪除、查找等操作上能夠盡可能著快速,那么,你會用什么樣的數(shù)據(jù)結(jié)構(gòu)呢? 數(shù)組 一種很簡單的方法應(yīng)該就是采用數(shù)組了...
摘要:前言本章將介紹中和的基本使用和內(nèi)部原理因?yàn)檫@兩種數(shù)據(jù)結(jié)構(gòu)有很多相似的地方所以把他們放到一章中介紹并且重點(diǎn)介紹內(nèi)部一個很重要的數(shù)據(jù)結(jié)構(gòu)跳躍表基本介紹先來看看中集合很像中鍵值對無序唯一不為空值重復(fù)無序是中最特別的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)其他幾個都能和大致對 前言 本章將介紹 Redis中 set 和 zset的基本使用和內(nèi)部原理.因?yàn)檫@兩種數(shù)據(jù)結(jié)構(gòu)有很多相似的地方所以把他們放到一章中介紹.并且重點(diǎn)介紹...
閱讀 2727·2021-11-17 17:00
閱讀 2019·2021-10-11 10:57
閱讀 3848·2021-09-09 11:33
閱讀 1042·2021-09-09 09:33
閱讀 3621·2019-08-30 14:20
閱讀 3427·2019-08-29 11:25
閱讀 2876·2019-08-26 13:48
閱讀 820·2019-08-26 11:52