二叉树 跳表_面试题之跳表
本文主要講解跳表的原理、代碼實(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)題。
- 上一篇: python开源系统_搭建轻量级的开源推
- 下一篇: vscode 导入文件_VScode中误