Java實(shí)現(xiàn)跳躍表的示例詳解
跳表全稱叫做跳躍表,簡稱跳表,是一個(gè)隨機(jī)化的數(shù)據(jù)結(jié)構(gòu),實(shí)質(zhì)就是一種可以進(jìn)行二分查找的有序鏈表。跳表在原有的有序列表上面增加多級(jí)索引,通過索引來實(shí)現(xiàn)快速查找。跳表不僅能提高搜索性能,同時(shí)也提高插入和刪除的性能,redis中的有序集合set就是用跳表實(shí)現(xiàn)的,面試時(shí)候也經(jīng)常會(huì)問。

這里我們原始數(shù)據(jù)個(gè)數(shù)n=10,以間隔k=2建立索引,則第一層索引10/2=5個(gè),第二層⌈10/2^2⌉=3個(gè),第三層⌈10/2^3⌉=2個(gè),第四層⌈10/2^4⌉=1個(gè)。根據(jù)上圖我們來分析一下,跳表的結(jié)構(gòu)是一棵樹(除原始數(shù)據(jù)層外),樹的左指針指向?qū)?yīng)的下一層鏈表的節(jié)點(diǎn),右指針指向當(dāng)前鏈表的下一個(gè)節(jié)點(diǎn),且樹高為log(n),對(duì)于每一層需要比較的次數(shù)最多為k,則時(shí)間復(fù)雜度為O(k*log(n)),k為常數(shù)項(xiàng),所以跳表查詢時(shí)間復(fù)雜度為O(log(n))。因?yàn)樾枰~外的空間存儲(chǔ)索引,是典型的以空間換時(shí)間,空間復(fù)雜度為O(n)。
接下來我們自己實(shí)現(xiàn)一個(gè)跳表:
節(jié)點(diǎn)數(shù)據(jù)結(jié)構(gòu)定義:根據(jù)跳表結(jié)構(gòu),節(jié)點(diǎn)首先需要一個(gè)value存儲(chǔ)當(dāng)前節(jié)點(diǎn)值,需要一個(gè)next指針指向同一層的下一個(gè)節(jié)點(diǎn),需要一個(gè)nodeValue指針指向下一層對(duì)應(yīng)節(jié)點(diǎn),但是這里為了插入刪除方便,引入了一個(gè)prev指針,指向同一層的上一個(gè)節(jié)點(diǎn)。
class Node {
//當(dāng)前節(jié)點(diǎn)值
private Integer value;
//當(dāng)前節(jié)點(diǎn)所屬鏈表下一個(gè)節(jié)點(diǎn)
private Node next;
//當(dāng)前節(jié)點(diǎn)所屬鏈表上一個(gè)節(jié)點(diǎn)
private Node prev;
//當(dāng)前節(jié)點(diǎn)指向的另一個(gè)索引鏈表/原始值鏈表節(jié)點(diǎn)
private Node nodeValue;
Node(Integer value) {
this.value = value;
}
}初始化一個(gè)跳表:跳表的建立需要在數(shù)據(jù)有序的基礎(chǔ)上,然后從下往上在下一層的基礎(chǔ)上,間隔k生成當(dāng)前層的節(jié)點(diǎn),新生成的節(jié)點(diǎn)需要與當(dāng)前層上一個(gè)節(jié)點(diǎn)連接起來,并且指向生成它的下一層節(jié)點(diǎn)。
/**
* 原始數(shù)據(jù)鏈表
*/
private Node head ;
/**
* 最終的跳表結(jié)構(gòu):保存索引鏈表及原始鏈表
*/
private List<Node> indexList;
/**
* 跳表層數(shù)
*/
private int level;
/**
* 初始化
*/
public void init() {
//帶頭節(jié)點(diǎn)的鏈表,便于操作
head = new Node(-1);
head.next = head;
indexList = new ArrayList<>();
level = 0;
}
/**
* 初始化跳表
* @param k 間隔
* @param nums 原始數(shù)據(jù)(已排序)
*/
public void init(int k, int[] nums) {
//初始化數(shù)據(jù)鏈表
Node temp = head;
for (int num : nums) {
Node cur = new Node(num);
cur.prev = temp;
temp.next = cur;
temp = temp.next;
}
//新節(jié)點(diǎn)保存(最底層)
indexList.add(head);
//循環(huán)生成索引結(jié)構(gòu),結(jié)束條件,當(dāng)層僅一個(gè)元素
temp = head.next;
while (true) {
//當(dāng)前鏈表第幾個(gè)元素
int i = 0;
//生成另一條鏈表長度
int size = 0;
Node indexNode = new Node(-1);
indexNode.next = indexNode;
Node indexNodeTemp = indexNode;
while (null != temp) {
//間隔k生成節(jié)點(diǎn)
if (i % k == 0) {
Node curNode = new Node(temp.value);
curNode.nodeValue = temp;
curNode.prev = indexNodeTemp;
indexNodeTemp.next = curNode;
indexNodeTemp = indexNodeTemp.next;
++ size;
}
++ i;
temp = temp.next;
}
indexList.add(indexNode);
temp = indexNode.next;
//當(dāng)生成的索引鏈表僅1時(shí)不需要再繼續(xù)生成
if (size == 1) {
break;
}
}
level = indexList.size();
}從跳表中查找元素:從最頂層索引鏈表開始查找,找到第一個(gè)大于當(dāng)前節(jié)點(diǎn)的元素,則需要查找的元素在當(dāng)前節(jié)點(diǎn)與之前節(jié)點(diǎn)之間,則從當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)prev往下nodevalue繼續(xù)進(jìn)行查找,直到當(dāng)前節(jié)點(diǎn)值與查找值相等,則直接返回當(dāng)前節(jié)點(diǎn),返回的節(jié)點(diǎn)可能是索引節(jié)點(diǎn),也可能是原始數(shù)據(jù)節(jié)點(diǎn),如果需要找到原始數(shù)據(jù)節(jié)點(diǎn),則通過nodeValue繼續(xù)往下找。
/**
* 是否存在num
* @param num
* @return
*/
public boolean hasNum(int num) {
Node result = this.findNum(num);
return null != result;
}
/**
* 查找num(返回的可能是索引,也可能是原始數(shù)據(jù),根據(jù)nodeValue可以判斷,也可以找到原始數(shù)據(jù))
* @param num
*/
public Node findNum(int num) {
//跳表結(jié)構(gòu)indexList是數(shù)據(jù)-》第一層索引-》第二層索引-》。。。。
//1.直接匹配到
//2.找到第一個(gè)大于當(dāng)前元素的數(shù),找前一個(gè)
Node node = indexList.get(indexList.size() - 1).next;
Node last = null;
while (null != node) {
if (node.value == num) {
//已經(jīng)找到元素
return node;
}
if (node.value > num) {
if (null == last) {
//比最小值還小
return null;
}
//找到了第一個(gè)大于num的索引node
//到下一層去繼續(xù)找
node = last.nodeValue;
last = null;
continue;
}
last = node;
node = null != node.next ? node.next : node.nodeValue;
}
return null;
}刪除節(jié)點(diǎn):首先通過上面的查找方法找到目標(biāo)節(jié)點(diǎn),如果目標(biāo)節(jié)點(diǎn)是索引值,則需要從當(dāng)前索引層,層層往下刪除包括原始數(shù)據(jù)鏈表,如果是原始數(shù)據(jù)值,則直接刪除,暫不調(diào)整。
/**
* 構(gòu)建索引時(shí):自底向上逐層構(gòu)建,如果索引需要?jiǎng)h除(當(dāng)兩個(gè)索引之間沒有任何數(shù)據(jù)時(shí)候,刪除)
* @param num
* @return
*/
public boolean remove(int num) {
Node node = this.findNum(num);
if (null == node) {
//不需要移除
return false;
}
if (null == node.nodeValue) {
//數(shù)據(jù)鏈表,可以直接移除
//是否最后一個(gè)節(jié)點(diǎn)
if (null == node.next) {
node.prev.next = null;
return true;
}
node.next.prev = node.prev;
node.prev.next = node.next;
return true;
}
//當(dāng)前在索引上,自上而下刪除索引及數(shù)據(jù)
while (null != node) {
Node cur = node.nodeValue;
if (null == node.next) {
node.prev.next = null;
} else {
node.next.prev = node.prev;
node.prev.next = node.next;
}
node = cur;
}
return true;
}新增節(jié)點(diǎn):新增節(jié)點(diǎn)時(shí)候,如果不對(duì)索引進(jìn)行調(diào)整,極端情況下,每次新增的節(jié)點(diǎn)都在之前第一層兩個(gè)節(jié)點(diǎn)之間,當(dāng)這之間的鏈表越變越長,時(shí)間復(fù)雜度直接退化為O(n),所以需要同時(shí)新增索引,維持跳表的高效性。但是我們?nèi)绾涡略?,有一個(gè)方法就是,在新增節(jié)點(diǎn)時(shí),隨機(jī)選擇k,即第k級(jí)索引,從1~k新增索引。
/**
* 首先需要查找插入位置,如果比最小的還小,直接在前面插入
* 否則需要從最頂級(jí)一直查找到數(shù)據(jù)鏈表,找到插入位置,插入,在查找的過程中,就可以開始插入索引節(jié)點(diǎn),
* 從上往下進(jìn)行插入
* @param num
*/
public void add(int num) {
int k = this.generatorLevelK();
//尋找插入點(diǎn)的過程和查找過程基本一致
//頂級(jí)索引鏈表
Node node = indexList.get(indexList.size() - 1).next;
int index = 1;
while (null != node) {
//找到第一個(gè)node.value >= num的元素,在前面插入
if (node.value >= num) {
//已經(jīng)找到,前插
if (index >= k) {
Node newNode = new Node(num);
Node temp = node.prev;
newNode.next = temp.next;
temp.next.prev = newNode;
newNode.prev = temp;
temp.next = newNode;
}
//找的時(shí)候往后面找的,但是當(dāng)前已經(jīng)先于num了,下一次再往后面找,就出現(xiàn)問題
if (null == node.prev.prev) {
//第一個(gè)節(jié)點(diǎn)就符合條件
node = node.nodeValue;
continue;
}
node = node.prev.nodeValue;
++ index;
continue;
}
//沒有找到,但是當(dāng)前已經(jīng)是鏈表最后一個(gè)元素了
if (null == node.next) {
if (index >= k) {
Node newNode = new Node(num);
newNode.prev = node;
node.next = newNode;
}
if (null == node.prev.prev) {
//第一個(gè)節(jié)點(diǎn)就符合條件
node = node.nodeValue;
continue;
}
node = node.prev.nodeValue;
++ index;
continue;
}
node = node.next;
}
}
private int generatorLevelK() {
Random random = new Random();
return random.nextInt(level);
}至此,我們實(shí)現(xiàn)了一個(gè)跳表的定義,初始化,查找,節(jié)點(diǎn)新增與刪除。
到此這篇關(guān)于Java實(shí)現(xiàn)跳躍表的示例詳解的文章就介紹到這了,更多相關(guān)Java跳躍表內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決mybatis where-if中if不能識(shí)別大寫AND,OR的問題
這篇文章主要介紹了解決mybatis where-if中if不能識(shí)別大寫AND,OR的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-02-02
Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式
這篇文章主要給大家介紹了關(guān)于Java中l(wèi)ist根據(jù)id獲取對(duì)象的幾種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
SpringBoot在生產(chǎn)快速禁用Swagger2的方法步驟
這篇文章主要介紹了SpringBoot在生產(chǎn)快速禁用Swagger2的方法步驟,使用注解關(guān)閉Swagger2,避免接口重復(fù)暴露,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-12-12
MyBatis CodeHelperPro激活方法詳細(xì)教程
MyBatisCodeHelper-Pro是IDEA下的一個(gè)插件,功能類似mybatis plugin,今天小編給大家分享MyBatis CodeHelperPro激活方法,需要的朋友跟隨小編一起看看吧2021-07-07

