1. 程式人生 > >【資料結構】跳躍連結串列(Skip list)

【資料結構】跳躍連結串列(Skip list)

普通的連結串列存在一個嚴重的缺陷:

需要順序掃描才能找到所需要的元素。而且查詢從連結串列的開頭開始,只有找到了所需要的元素,或者直到連結串列的末尾都沒有找到這個元素時才會停下來。將量表進行排序可以加速查詢的過程,但是仍然需要順序查詢。因此,很容易想到,連結串列最好可以跳過某些節點,以避免順序處理。

從而,引出了跳躍連結串列,跳躍連結串列是有序連結串列的一個有趣的變種,可以進行非順序查詢。

在有n個節點的跳躍連結串列中,對於每個滿足1≦k≦└ lgn ┘ 和 1≦└ n/2^(k-1)┘-1 的k和i,位於2^(k-1)·i的結點指向位於2^(k-1)·(i+1)的節點。這意味著第二個節點指向前面距離兩個單位的節點,第四個節點指向前面距離4個單位的節點,以此類推。所以,要在連結串列的結點中包含不同樹木的指標:半數節點只有一個指標,1/4的節點有兩個指標,1/8節點有3個指標,一次列退。可以看出,指標數表明了每個節點的級,而級的數量為maxLevel = └ lg n ┘+1

為了查詢元素e1,應該從最高層上的指標開始,找到該元素就成功地結束查詢。如果到達該連結串列的末尾,或者遇到大雨元素e1的某個元素key,就包含key的那個節點的前一個節點重新開始查詢,但是這次查詢是從比前面低一級的指標開始。知道找到e1,或者沿著第一級的指標到達了連結串列的末尾,或者找到一個大於e1的元素,查詢才會停止。下面給出為程式碼:

search(元素e1)
	p = 最高層i上的非空連結串列;
	while 沒有找到 e1 且 i ≥ 0
		if p-> key < e1
			p = 從 --i級上p的前驅開始的子連結串列;
		else if p->key > e1
			if p是i級上的最後一個節點
				p = 在 <i的最高階上從p開始的非空子連結串列
				i = 新的級數
		else 
			p = p-> next

下面是跳錶的具體實現:
//
// Created by 大幕 on 2017/9/13.
//

#ifndef SKIPLIST_SKIPLIST_H
#define SKIPLIST_SKIPLIST_H

#include <iostream>
const int DefaultSize = 100;
template <typename E,typename K>
struct SkipNode{
    E data;         //資料域
    SkipNode<E,K> **link;   //指標陣列域
    SkipNode(int size = DefaultSize){   //建構函式
        link = new SkipNode<E,K> *[size];
        if(link = nullptr)
        {
            std::cerr <<"儲存分配失敗!"<<std::endl;
            exit(1);
        }
    }
    ~SkipNode()
    {
        delete []link;
    }
};

/**
 * 跳錶類定義
 * 其中large是一個比字典中任何一個元素的值都大的值
 * 放在尾結點中。0級鏈上的元素值從左向右按照升序進行排列
 * 但是不包括附加頭結點
 */

template <typename E,typename K>
class SkipList{
public:
    SkipList(K large,int maxLev = DefaultSize); //建構函式
    ~SkipList();
    bool Search(const K k1,E& e1)const;     //搜尋函式
    E& getData(SkipNode<E,K> *current)
    {
        if(current != nullptr)
            return current->data;
        else
            return nullptr;
    }
    bool Insert(const K k1,E &e1);      //插入函式
    bool Remove(const K k1,E &e1);      //刪除函式
private:
    int maxLevel;       //所允許的最大級數
    int Levels;
    K TailKey;
    SkipNode<E,K> *head;    //附加頭結點
    SkipNode<E,K> *tail;    //附加尾結點
    SkipNode<E,K> **last;   //指標陣列
    int level();
    SkipNode<E,K> *SaveSearch(const K k1);
};

/***跳錶的建構函式初始化Level(當前出現的最大級別)、maxLevel、TailKey(所有元素值均小於這個值)
 * 還為附加頭結點和尾結點分配空間。在插入和刪除之前進行搜尋時,
 * 所遇到的每條鏈上的最後一個元素均被放在陣列last中。附加頭結點中有maxLevel+1個用於指向
 * 各級鏈的指標被初始化為指向尾結點(空連結串列).
 * 解構函式釋放連結串列中用到的所有空間
 */

/**
 * 跳錶的建構函式和解構函式
 */

template <typename E,typename K>
SkipList<E,K>::SkipList(K large, int maxLev) {
    //建構函式,建立空的多級鏈
    maxLevel = maxLev;          //最大級鏈數目
    TailKey = large;            //控制掃描的最大關鍵碼
    Levels = 0;
    head = new SkipNode<E,K>(maxLevel+1);   //附加頭結點,有maxLevel+1個指標
    tail = new SkipNode<E,K>(0);            //附加尾結點,有0個指標
    last = new SkipNode<E,K> *[maxLevel+!]; //跳錶的多級鏈的頭指標
    tail -> data = large;
    for(int i =0;i<maxLevel;i++)
    {
        head->link[i] = tail;
    }
}

template<typename E,typename K>
SkipList<E,K>::~SkipList() {
    /**
     * 解構函式,釋放連結串列上的所有元素節點
     */
    SkipNode<E,K> *next;
    while(head != tail)
    {
        next = head->link[0];
        delete head;
        head = next;
    }
    delete tail;
    delete []last;
}

/**
 * 跳錶的搜尋。插入和刪除
 */

/**
 * 1.跳錶的搜尋:
 * 跳錶有兩個搜尋函式,當需要搜尋一個值為k1的元素時,可用公共成員函式Search
 * 如果找到想要搜尋的元素,則將這個元素返回到e1中,並返回true,否則返回false.
 * Search從最飛機鏈(Level級,僅含有一個元素)的表頭開始搜尋,順著指標向右搜尋
 * 逐步逼近要搜尋的元素,一直走到0級鏈。
 * 當從for迴圈退出時,正好處於要尋找的元素的左邊。與0級鏈的下一個元素進行比較,
 * 就能知道要找的元素是否在跳錶中
 *
 * 第二個搜尋函式是私有函式SaveSearch(),作用是由插入和刪除進行呼叫,
 * SaveSearch不僅僅包含了Search的全部功能,而且可以把每一級中遇到的最後一個結點存放到陣列last中FFFFFF
 */

template <typename E,typename K>
bool SkipList<E,K>::Search(const K k1, E &e1) const {
    if(k1 > TailKey)
        return false;
    SkipNode<E,K> *p = head;
    for(int i = Levels;i >= 0;i--)      //逐級向下搜尋
    {
        while(p->link[i]->data < k1)    //過載,元素關鍵碼判小於
            p = p->link[i];
    }
    e1 = p->link[0]->data;
    return e1 == k1;     //過載,元素關鍵碼判等於
}

template <typename E,typename K>
SkipNode<E,K>* SkipList<E,K>::SaveSearch(const K k1) {
    if(k1 > TailKey)
        return nullptr;
    SkipNode<E,K> *p = head;
    for(int i = Levels; i >= 0; i--) {
        while (p->link[i]->data < k1)
            p = p->link[i];
        last[i] = p;        //記下最後比較的及誒單
    }
    return p->link[0];
}

#endif //SKIPLIST_SKIPLIST_H

相關推薦

資料結構跳躍連結串列(Skip list)

普通的連結串列存在一個嚴重的缺陷: 需要順序掃描才能找到所需要的元素。而且查詢從連結串列的開頭開始,只有找到了所需要的元素,或者直到連結串列的末尾都沒有找到這個元素時才會停下來。將量表進行排序可以加速查詢的過程,但是仍然需要順序查詢。因此,很容易想到,連結串列最好可以跳過某

資料結構環形連結串列

給定一個連結串列,判斷連結串列中是否有環。 思路分析: 判斷連結串列是否帶環,實際上歸屬於快慢指標問題,快指標先進環,慢指標後進環,然後快指標和慢指標最終會在環裡面相遇,如果不會相遇,則表示不是迴圈連結串列,也就是說到頭了,即不帶環。 具體程式碼如下: /**

資料結構雙向連結串列的實現

文章目錄 LinkList.h LinkLish.c LinkList.h #ifndef __LINKLIST_H__ #define __LINKLIST_H__ #include <stdio.h>

資料結構複雜連結串列的複製

複雜連結串列的結構體 typedef struct ComplexNode { int data; struct ComplexNode *next; struct ComplexNode *r

資料結構--1.連結串列的基本操作和雜湊表定義

C實現連結串列的基本操作 初始化 插入 刪除  雜湊表的定義  //連結串列的基本操作 初始化 插入 刪除 雜湊表的定義 #include<iostream> using namespace std; typedef struct Node { int

資料結構靜態連結串列

資料結構之靜態連結串列實現 前言 靜態連結串列,是一種巧妙的資料結構實現方式。 靜態連結串列:         每個節點有一個資料域(data),用來存放有用的資料資訊;         還有一個下標域(cur),用來指示下一個元素的下標位置。 我們都知道鏈式線性表的

資料結構迴圈連結串列和非迴圈單鏈表的區別

注意:這裡的迴圈連結串列是以尾指標為起始。非迴圈單鏈表判斷結束的標誌為指標為空。而迴圈連結串列判斷結束的標誌是指標不是頭節點。在插入操作中,非迴圈單鏈表判斷迴圈結束是指標為空。若迴圈結束後,發現指標變為空,說明要求插入的位置不合理:位置大於Length+1。bool ins

資料結構連結串列實現多項式運算

一元多項式的運算包括加法減法和乘法,而多項式的加法和乘法都可以依靠多項式的加法來實現,所以本文僅僅講解如何用連結串列實現一元多項式的加法。 數學上的一元多項式的表示是p(x) = p0 + p1 * x + p2 * x^2 + p3 * x^3 + … +

C/C++資料結構雙向連結串列操作

目錄 標頭檔案定義 測試檔案 雙向連結串列操作 像雙向連結串列的求長,判空,遍歷,查詢,檢索,之類的操作都和單鏈表一樣的。不過我還是在了文中。 標頭檔案定義 #ifndef _DOUBLELINKLIST_H_ #def

php實現資料結構單向連結串列

什麼是單向連結串列 連結串列是以鏈式儲存資料的結構,其不需要連續的儲存空間,連結串列中的資料以節點來表示,每個節點由元素(儲存資料)和指標(指向後繼節點)組成。 單向連結串列(也叫單鏈表)是連結串列中最簡單的一種形式,每個節點只包含一個元素和一個指標。它有一個表頭,並且除了最後一個節點外,所有節點都有其後

資料結構跳躍連結串列

#include<stdio.h>   #include<stdlib.h>       #define MAX_LEVEL 10 //最大層數       //節點  typedef  struct nodeStructure   {       int key;       in

資料結構多項式連結串列實現相加

#include<bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; const int maxn = 1006; struct node { double coef; int exp; struct n

資料結構合併兩個有序連結串列

#include<stdio.h> #include<string.h> #include<stdlib.h> const int maxn = 1e5 + 5; struct node { int num; struct node *next; }; s

資料結構線性表的鏈式儲存(二)迴圈連結串列

線性錶鏈式儲存的迴圈單鏈表 迴圈連結串列從任意一點出發,可以訪問全部節點。 一般為了便於操作,將連結串列的頭指標變為尾指標,指向尾節點,連結串列的頭節點則為尾指標的next。 程式碼收穫 用尾指標進行操作雖然省下迴圈,但是插入刪除等操作都需要移動尾指標導致

資料結構線性表的鏈式儲存連結串列的初始化、插入元素、刪除元素操作(三)

雙向連結串列的初始化插入與刪除 程式碼收穫 雙向連結串列刪除結點需要注意要刪除最後一個結點和不是最後一個結點分類討論。 插入和刪除時注意修改上一個結點裡指向下一個結點的指標與下一個結點裡指向上一個結點的指標。 #include <stdio.h>

資料結構帶有尾指標的連結串列連結串列實現佇列

前面寫了用動態陣列實現佇列,現在我將用帶有尾指標的連結串列實現佇列。 我們知道佇列需要在兩端進行操作,如果是以普通連結串列進行底層實現的佇列,在入隊時需要找到最後一個節點才能進行新增元素,這樣相當於遍歷一遍連結串列,從而造成巨大的時間浪費。 為了解決這個問題,我們引入了尾

資料結構單鏈表(無頭單向非迴圈連結串列)各個介面的實現

順序表存在的問題: 中間/頭部的插入刪除,時間複雜度為O(N) 增容需要申請新空間,拷貝資料,釋放舊空間。會有不小的消耗 增容一般是呈2倍的增長,勢必會有一定的空間浪費。 例如當前容量為100,滿了以後增容到200,如果再繼續插入了5個數據,後面沒有資料插入了,

資料結構連結串列相關練習題:反轉連結串列

反轉一個單鏈表。 示例: 輸入: 1->2->3->4->5->NULL 輸出: 5->4->3->2->1->NULL 這道題有兩種思路 ①傳統的使用三個指標:兩個指標用來交換,另一個指標用來向前走  ,

資料結構連結串列相關練習題:連結串列的中間節點

給定一個帶有頭結點 head 的非空單鏈表,返回連結串列的中間結點。 如果有兩個中間結點,則返回第二個中間結點。 示例 1: 輸入:[1,2,3,4,5] 輸出:此列表中的結點 3 (序列化形式:[3,4,5]) 返回的結點值為 3 。 (測評系統對該結點序列化表述是

資料結構連結串列相關練習題:連結串列中倒數第k個結點

題目描述:輸入一個連結串列,輸出該連結串列中倒數第k個結點。 分析:這個題同樣是引入快慢指標,為了遍歷一次就找到倒數第k個節點,可以定義兩個指標:fast指標從連結串列的頭指標開始遍歷向前走k-1(k)步,slow指標保持不動;從第k步開始,slow指標開從連結串列的頭指標