日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

二叉树 跳表_面试题之跳表

發(fā)布時(shí)間:2023/12/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 二叉树 跳表_面试题之跳表 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本文主要講解跳表的原理、代碼實(shí)現(xiàn)以及與之相關(guān)的常見(jiàn)面試題。
跳表本質(zhì)上是一種查找結(jié)構(gòu),相比于平衡樹(shù),不僅實(shí)現(xiàn)簡(jiǎn)單,而且插入、刪除、查找的時(shí)間復(fù)雜度均為O(logN)。跳表其實(shí)就是鏈表,只是對(duì)有序的鏈表增加上附加的前進(jìn)鏈接(增加是以隨機(jī)化的方式進(jìn)行的),所以在列表中的查找可以快速的跳過(guò)部分列表從而快速檢索。
由于跳表在Redis的使用,導(dǎo)致面試中經(jīng)常會(huì)被提及,所以深入了解跳表的實(shí)現(xiàn)非常必要。

代碼實(shí)現(xiàn)

第一種實(shí)現(xiàn)方法

這種實(shí)現(xiàn)方法構(gòu)建的跳表整體如下圖所示:

這種實(shí)現(xiàn)方式中的SkipNode定義如下所示。

private class SkipNode<K, V> {private K key;private V value;private SkipNode<K, V>[] forward;public SkipNode( K key, V value, int levels ) {this.key = key;this.value = value;this.forward = (SkipNode<K, V>[]) new SkipNode[levels+1];for (int i = 0; i <= levels; i++)this.forward[i] = null;}public K key() { return this.key; }public V value() { return this.value; }······}

SkipNode定義中需要留意就是forward數(shù)組,這個(gè)數(shù)組的大小是隨機(jī)的,大小代表這個(gè)節(jié)點(diǎn)的高度,數(shù)組中每個(gè)元素代表了這一層的下一個(gè)SkipNode。

下面來(lái)看insert的代碼實(shí)現(xiàn)。

/*** 將新值插入到鏈表中* @param k* @param newValue*/public void insert(K k, V newValue) {int newLevel = randomLevel();// 如果隨機(jī)的層大于現(xiàn)在的最大層, 進(jìn)行層調(diào)整if (newLevel > level)adjustHead(newLevel);this.level = newLevel;SkipNode<K, V>[] update = (SkipNode<K, V>[]) new SkipNode[level+1];SkipNode<K, V> x = this.head;// 找尋每一層的插入位置for (int i=level; i>=0; i--) {while((x.forward[i] != null) &&((k.compareTo(x.forward[i].key())) > 0))x = x.forward[i];update[i] = x;}// 創(chuàng)建新節(jié)點(diǎn)x = new SkipNode<K, V>(k, newValue, newLevel);// 類(lèi)似于鏈表插入for (int i=0; i <= newLevel; i++) {x.forward[i] = update[i].forward[i];update[i].forward[i] = x;}this.size++;}

從上面的代碼中可以看出insert主要有幾個(gè)步驟:

首先隨機(jī)產(chǎn)生層數(shù),創(chuàng)建新節(jié)點(diǎn)
每層遍歷,得到新節(jié)點(diǎn)在每層插入的前一個(gè)節(jié)點(diǎn)
逐層插入新節(jié)點(diǎn)(類(lèi)似于鏈表插入)

下面來(lái)看find的代碼實(shí)現(xiàn)。

public V find(K searchKey) {SkipNode<K, V> x = this.head;// 類(lèi)似于一個(gè)下樓梯的過(guò)程for (int i=level; i>=0; i--)while ((x.forward[i] != null) &&(searchKey.compareTo(x.forward[i].key()) > 0))x = x.forward[i];x = x.forward[0];if ((x != null) && (searchKey == x.key()))return x.value();else return null;}

find的過(guò)程比較簡(jiǎn)單,類(lèi)似于生活中下樓梯,具體過(guò)程見(jiàn)上圖中的紅線所示。

第二種實(shí)現(xiàn)方法

這種方式最終創(chuàng)建的跳表如下所示。

SkipListEntry的定義如下。

public class SkipListEntry<K extends Comparable, V> {public K key;public V value;public int pos;public SkipListEntry up, down, left, right;// 構(gòu)造函數(shù)public SkipListEntry(K k, V v) {key = k;value = v;up = down = left = right = null;} }

skipList的初始化操作。定義了頭和尾節(jié)點(diǎn),并且把它們相連接。

public SkipList(){SkipListEntry p1, p2;p1 = new SkipListEntry<K, V>(null, null);p2 = new SkipListEntry<K, V>(null, null);head = p1;tail = p2;p1.right = p2;p2.left = p1;n = 0;h = 0;r = new Random();}

查找操作如下所示。查找操作依然類(lèi)似于下樓梯。

public SkipListEntry<K, V> findEntry(K k) {SkipListEntry<K, V> p = head;while ( true ) {//首先向右走while ( p.right.key != null &&p.right.key.compareTo(k) <= 0 ) {p = p.right;}// 向下走if ( p.down != null ){p = p.down;}else break; }return(p);}

添加節(jié)點(diǎn)的實(shí)現(xiàn)如下。

public Object put (K k, V v){SkipListEntry p, q;int i;// 待插入的前一個(gè)位置p = findEntry(k);if ( k.equals( p.getKey())) {Object old = p.getValue();p.value = v;return old;}q = new SkipListEntry(k, v);q.left = p;q.right = p.right;p.right.left = q;p.right = q;i = 0; // Current level = 0// 隨機(jī)插入while ( r.nextDouble() < 0.5 ){// 當(dāng)前插入的是第i層if ( i >= h ) {SkipListEntry p1, p2;h = h + 1;p1 = new SkipListEntry(null,null);p2 = new SkipListEntry(null,null);p1.right = p2;p1.down = head;p2.left = p1;p2.down = tail;head.up = p1;tail.up = p2;head = p1;tail = p2;}while ( p.up == null ){p = p.left;}p = p.up;SkipListEntry e = new SkipListEntry(k, null); e.left = p;e.right = p.right;e.down = q;p.right.left = e;p.right = e;q.up = e;q = e; i = i + 1; }n = n + 1;return null;}

和第一種實(shí)現(xiàn)方式不同的是,第二種實(shí)現(xiàn)方法的插入操作在每一層都需要重新創(chuàng)建節(jié)點(diǎn)進(jìn)行插入,空間浪費(fèi)。所以推薦第一種實(shí)現(xiàn)方法。

常見(jiàn)面試題

redis為什么使用跳表,為什么不用紅黑樹(shù)

相比于紅黑樹(shù)、平衡二叉樹(shù),跳表不僅查找、插入、刪除時(shí)間復(fù)雜度都是O(logN),并且實(shí)現(xiàn)簡(jiǎn)單很多。

跳表數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)

可以參考第一種實(shí)現(xiàn)方法。

參考文章

http://www.mathcs.emory.edu/~cheung/Courses/323/Syllabus/Map/skip-list-impl.htmlhttps://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html

總結(jié)

以上是生活随笔為你收集整理的二叉树 跳表_面试题之跳表的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。