二叉树 跳表_面试题之跳表
本文主要講解跳表的原理、代碼實現以及與之相關的常見面試題。
跳表本質上是一種查找結構,相比于平衡樹,不僅實現簡單,而且插入、刪除、查找的時間復雜度均為O(logN)。跳表其實就是鏈表,只是對有序的鏈表增加上附加的前進鏈接(增加是以隨機化的方式進行的),所以在列表中的查找可以快速的跳過部分列表從而快速檢索。
由于跳表在Redis的使用,導致面試中經常會被提及,所以深入了解跳表的實現非常必要。
代碼實現
第一種實現方法
這種實現方法構建的跳表整體如下圖所示:
這種實現方式中的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數組,這個數組的大小是隨機的,大小代表這個節點的高度,數組中每個元素代表了這一層的下一個SkipNode。
下面來看insert的代碼實現。
/*** 將新值插入到鏈表中* @param k* @param newValue*/public void insert(K k, V newValue) {int newLevel = randomLevel();// 如果隨機的層大于現在的最大層, 進行層調整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;}// 創建新節點x = new SkipNode<K, V>(k, newValue, newLevel);// 類似于鏈表插入for (int i=0; i <= newLevel; i++) {x.forward[i] = update[i].forward[i];update[i].forward[i] = x;}this.size++;}從上面的代碼中可以看出insert主要有幾個步驟:
首先隨機產生層數,創建新節點每層遍歷,得到新節點在每層插入的前一個節點
逐層插入新節點(類似于鏈表插入)
下面來看find的代碼實現。
public V find(K searchKey) {SkipNode<K, V> x = this.head;// 類似于一個下樓梯的過程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的過程比較簡單,類似于生活中下樓梯,具體過程見上圖中的紅線所示。
第二種實現方法
這種方式最終創建的跳表如下所示。
SkipListEntry的定義如下。
public class SkipListEntry<K extends Comparable, V> {public K key;public V value;public int pos;public SkipListEntry up, down, left, right;// 構造函數public SkipListEntry(K k, V v) {key = k;value = v;up = down = left = right = null;} }skipList的初始化操作。定義了頭和尾節點,并且把它們相連接。
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();}查找操作如下所示。查找操作依然類似于下樓梯。
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);}添加節點的實現如下。
public Object put (K k, V v){SkipListEntry p, q;int i;// 待插入的前一個位置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// 隨機插入while ( r.nextDouble() < 0.5 ){// 當前插入的是第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;}和第一種實現方式不同的是,第二種實現方法的插入操作在每一層都需要重新創建節點進行插入,空間浪費。所以推薦第一種實現方法。
常見面試題
redis為什么使用跳表,為什么不用紅黑樹
相比于紅黑樹、平衡二叉樹,跳表不僅查找、插入、刪除時間復雜度都是O(logN),并且實現簡單很多。
跳表數據結構的實現
可以參考第一種實現方法。
參考文章
http://www.mathcs.emory.edu/~cheung/Courses/323/Syllabus/Map/skip-list-impl.htmlhttps://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html
總結
以上是生活随笔為你收集整理的二叉树 跳表_面试题之跳表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python开源系统_搭建轻量级的开源推
- 下一篇: vscode 导入文件_VScode中误