欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python數(shù)據(jù)結(jié)構(gòu)與算法之跳表詳解

 更新時(shí)間:2022年02月10日 09:52:43   作者:盼小輝丶  
跳表是帶有附加指針的鏈表,使用這些附加指針可以跳過(guò)一些中間結(jié)點(diǎn),用以快速完成查找、插入和刪除等操作。本節(jié)將詳細(xì)介紹跳表的相關(guān)概念及其具體實(shí)現(xiàn),需要的可以參考一下

0. 學(xué)習(xí)目標(biāo)

在諸如單鏈表、雙線鏈表等普通鏈表中,查找、插入和刪除操作由于必須從頭結(jié)點(diǎn)遍歷鏈表才能找到相關(guān)鏈表,因此時(shí)間復(fù)雜度均為O(n)。跳表是帶有附加指針的鏈表,使用這些附加指針可以跳過(guò)一些中間結(jié)點(diǎn),用以快速完成查找、插入和刪除等操作。本節(jié)將介紹跳表的相關(guān)概念及其具體實(shí)現(xiàn)。

通過(guò)本節(jié)學(xué)習(xí),應(yīng)掌握以下內(nèi)容:

  • 跳表的基本概念及其與普通鏈表的區(qū)別
  • 跳表及其操作的實(shí)現(xiàn)

1. 跳表的基本概念

跳表 (Skip List) 是一種可以快速進(jìn)行查找、插入和刪除等操作的有序鏈表(鏈表中的數(shù)據(jù)項(xiàng)按照某種順序進(jìn)行排列的鏈表稱為有序鏈表)。跳表的核心思想是以更大的跨度遍歷鏈表,以便可以跳過(guò)中間結(jié)點(diǎn)。

1.1 跳表介紹

首先,讓我們回想一下如何在以下單鏈表中查找數(shù)據(jù)元素,即使鏈表中的數(shù)據(jù)已經(jīng)排好序,我們也需要從頭結(jié)點(diǎn)開始遍歷鏈表。

例如要在以上單鏈表中查找 15,查找過(guò)程為: 0-->3-->5 -->7-->10-->15。

為了提高查找的效率,我們可以抽取鏈表中的一部分結(jié)點(diǎn)建立一個(gè)起“索引”作用的鏈,稱為“索引層”或簡(jiǎn)稱“索引”:

此時(shí)我們查找 15,首先在索引層中遍歷,當(dāng)我們掃描到值為 10 的結(jié)點(diǎn)時(shí),由于下一結(jié)點(diǎn)值為 27,因此我們知道 15 應(yīng)當(dāng)在這兩個(gè)結(jié)點(diǎn)之間,然后回到原鏈表中遍歷即可找到值為 15 的結(jié)點(diǎn),遍歷過(guò)程如下圖綠色箭頭所示:

可以看到通過(guò)建立索引鏈,需要遍歷的結(jié)點(diǎn)數(shù)得以減少,為了進(jìn)一步減少所需遍歷的結(jié)點(diǎn)數(shù),可以通過(guò)對(duì)索引鏈添加索引,跳表是對(duì)鏈表疊加多級(jí)索引的數(shù)據(jù)結(jié)構(gòu)。

1.2 跳表的性能

1.3 跳表與普通鏈表的異同

我們可以將跳表理解為排序后的鏈表,但與普通鏈表相比,有以下不同點(diǎn):

  • 普通鏈表中的結(jié)點(diǎn)只有一個(gè) next 指針,跳表中的結(jié)點(diǎn)有多個(gè) next 指針;
  • 給定結(jié)點(diǎn)的 next 指針的數(shù)量是隨機(jī)確定的。

跳表中結(jié)點(diǎn)的每個(gè) next 指針?lè)Q為一層,結(jié)點(diǎn)中的層數(shù)稱為結(jié)點(diǎn)的大小,其決定了結(jié)點(diǎn)在鏈表中的層級(jí);在普通的有序鏈表中,插入、刪除和查找操作需要對(duì)鏈表依次遍歷,因此每個(gè)操作的時(shí)間復(fù)雜度均為O(n),跳表允許在遍歷時(shí)跳過(guò)一些中間節(jié)點(diǎn),每個(gè)操作的時(shí)間復(fù)雜度為O(㏒n)。

2. 跳表的實(shí)現(xiàn)

跳表中的每個(gè)數(shù)據(jù)元素由一個(gè)結(jié)點(diǎn)表示,結(jié)點(diǎn)的層級(jí)在結(jié)點(diǎn)插入時(shí)隨機(jī)選擇,而不考慮數(shù)據(jù)結(jié)構(gòu)中數(shù)據(jù)元素的數(shù)量。第i級(jí)結(jié)點(diǎn)有i個(gè)next 指針(在結(jié)點(diǎn)實(shí)現(xiàn)時(shí)使用大小為i的數(shù)組 next 存儲(chǔ)i個(gè) next 指針),因此我們不需要在結(jié)點(diǎn)中存儲(chǔ)結(jié)點(diǎn)的層級(jí),使用最大層數(shù) max_level 來(lái)限制層級(jí)上限。跳表的頭結(jié)點(diǎn)具有從 1 級(jí)到 max_level 級(jí)的 next 指針。

2.1 跳表結(jié)點(diǎn)類

跳表的結(jié)點(diǎn)示意圖如下所示,每個(gè)結(jié)點(diǎn)都有一組用于指向其它層級(jí)結(jié)點(diǎn)的指針域列表 next:

class Node:
    def __init__(self, data, level=0):
        self.data = data
        self.next = [None] * level
    
    def __str__(self):
        return "Node({}, {})".format(self.data, len(self.next))

next 數(shù)組用于保存指向不同層級(jí)結(jié)點(diǎn)的指針,其中每個(gè)元素就是一個(gè) next 指針,而 data 變量用于存儲(chǔ)數(shù)據(jù),重載 __str__ 方法用于便于打印結(jié)點(diǎn)對(duì)象,len(self.next) 可以用于表示結(jié)點(diǎn)中的層數(shù)。

2.2 跳表的初始化

跳表的初始化是建立一個(gè)空的帶頭結(jié)點(diǎn)的跳表,其表長(zhǎng) length 初始化為 0,此時(shí)鏈表中沒(méi)有元素結(jié)點(diǎn),只有一個(gè)頭結(jié)點(diǎn):

class SkipList:
    def __init__(self, max_level=8):
        self.max_level = max_level
        node = Node(Node, max_level)
        self.head = node
        self.length = 0

創(chuàng)建跳表 SkipList 對(duì)象的時(shí)間復(fù)雜度為O(1)。

2.3 獲取跳表長(zhǎng)度

求取跳表長(zhǎng)度只需要重載 __len__ 從對(duì)象返回 length 的值,因此時(shí)間復(fù)雜度為O(1):

    def __len__(self):
        return self.length

2.4 讀取指定位置元素

由于跳表中的每個(gè)結(jié)點(diǎn)的層級(jí)是在結(jié)點(diǎn)插入時(shí)隨機(jī)選擇的,因此讀取指定位置的數(shù)據(jù)元素和單鏈表類似,需要順序遍歷第0層結(jié)點(diǎn),操作的復(fù)雜度為O(n),如果索引不在可接受的索引范圍內(nèi),將引發(fā) IndexError 異常::

    def __getitem__(self, index):
        if index > self.length - 1 or index < 0:
            raise IndexError("UnrolledLinkedList assignment index out of range")
        else:
            count = 0
            node = self.head.next[0]
            while count < index:
                node = node.next[0]
                count += 1
            return node.data

2.5 查找指定元素

跳表的查找操作從最上層開始,如果待查元素 value 小于當(dāng)前層的后繼結(jié)點(diǎn)的 data,則平移至當(dāng)前層的后繼結(jié)點(diǎn);否則下移進(jìn)入下一層,繼續(xù)比較,直至第一層。

    def locate(self, value):
        node = self.head
        # 由于索引從0開始,因此第i層級(jí)的索引為i-1
        level = self.max_level - 1
        while level >= 0:
            while node.next[level] != None and node.next[level].data < value:
                # 平移到當(dāng)前層的后繼結(jié)點(diǎn)
                node = node.next[level]
            # 下移進(jìn)入下一層
            level -= 1
        if node.next[0].data == value:
            return node.next[0]
        else:
            raise ValueError("{} is not in skip list".format(value))

2.6 在跳表中插入新元素

與普通鏈表不同,在跳表中插入新元素不能指定位置(需要保證有序性),需要通過(guò)查找確定新數(shù)據(jù)元素的插入位置。

插入元素前,首先需要確定該元素所占據(jù)的層數(shù) level k,這是通過(guò)算法隨機(jī)生成的,這樣可以減少算法實(shí)現(xiàn)的復(fù)雜度,同時(shí)也可以保證跳表性能。

    def random_level(self, max_level):
        num = random.randint(1, 2**max_level -1)
        lognum = math.log(num, 2)
        level = int(math.floor(lognum))
        return max_level - level

然后需要在 level 1, level 2, ..., level k 各層的鏈表都插入新元素,為了達(dá)到此目的,我們需要在查找插入位置時(shí)維護(hù)一個(gè)列表 update,update 中的每個(gè)元素 update[i] 指向 level i 中小于插入元素 data 的最右邊結(jié)點(diǎn),即插入位置的左側(cè),如下圖所示,插入元素 data = 9,隨機(jī)層數(shù) k=3:

    def update_list(self, data):
        update = [None] * self.max_level
        node = self.head
        level = self.max_level - 1
        while level >= 0:
            while node.next[level] != None and node.next[level].data < data:
                node = node.next[level]
            update[level] = node
            level -= 1
        return update

可以看到維護(hù) update 列表的過(guò)程與查找元素類似,根據(jù) update 列表可以判斷跳表中是否已存在元素 data,如果不存在將其插入跳表中:

    def insert_node(self, data):
        # 產(chǎn)生隨機(jī) level,并構(gòu)造結(jié)點(diǎn)
        level = self.random_level(self.max_level)
        node = Node(data, level)
        # 獲取 update 列表
        update = self.update_list(data)
        # 插入結(jié)點(diǎn)
        if len(update) == 0 or update[0].next[0] == None or update[0].next[0].data != data:
            self.length += 1
            for i in range(level):
                node.next[i] = update[i].next[i]
                update[i].next[i] = node

2.7 刪除跳表中指定元素

刪除指定元素與插入操作類似:

    def delete_node(self, data):
        # 獲取 update 列表
        update = self.update_list(data)
        if len(update) > 0:
            node = update[0].next[0]
            # 刪除指定元素結(jié)點(diǎn)
            if node != None and node.data == data:
                self.length -= 1
                for i in range(len(node.next)):
                    update[i].next[i] = node.next[i]
                del node

2.8 其它一些有用的操作

1.跳表指定層元素輸出

將跳表指定層轉(zhuǎn)換為字符串以便進(jìn)行打印,編寫 print_level 方法打印指定層中數(shù)據(jù)元素:   

def get_level(self, level):
? ? ? ? """輔助函數(shù),用于根據(jù)給定層構(gòu)造結(jié)果字符串"""
? ? ? ? node = self.head.next[level]
? ? ? ? result = ''
? ? ? ? while node:
? ? ? ? ? ? result = result + str(node.data) + '-->'
? ? ? ? ? ? node = node.next[level]
? ? ? ? result += 'None'
? ? ? ? return result

? ? def print_level(self, level):
? ? ? ? print('level {}'.format(level))
? ? ? ? result = self.get_level(level)
? ? ? ? print(result)

2.跳表各層元素輸出

可以借助上述輔助函數(shù) get_level,使用 str 函數(shù)調(diào)用對(duì)象上的 __str__ 方法可以創(chuàng)建適合打印的字符串表示:

    def __str__(self):
        result = ''
        for i in list(range(self.max_level))[self.max_level-1:0:-1]:
            result = result + self.get_level(i) + '\n'
        result += self.get_level(0)
        return result

3. 跳表應(yīng)用

接下來(lái),我們將測(cè)試上述實(shí)現(xiàn)的鏈表,以驗(yàn)證操作的有效性。

3.1 跳表應(yīng)用示例

首先初始化一個(gè)跳表 sl,并在其中插入若干元素:

sl = SkipList(4)
for i in range(0, 30, 3):
    sl.insert_node(i)

我們可以直接打印跳表中的數(shù)據(jù)元素、鏈表長(zhǎng)度等信息:

print('跳表 sl 各層元素為:')
print(sl)
print('跳表 sl 長(zhǎng)度為:', len(sl))
print('跳表 sl 第 2 個(gè)元素為:', sl[2])

以上代碼輸出如下:

跳表 sl 各層元素為:
9-->None
0-->9-->18-->None
0-->9-->18-->27-->None
0-->3-->6-->9-->12-->15-->18-->21-->24-->27-->None
跳表 sl 長(zhǎng)度為: 10
跳表 sl 第 2 個(gè)元素為: 6

接下來(lái),我們將演示在添加/刪除元素、以及如何查找指定元素等:

# 插入元素
sl.insert_node(14)
print('在跳表 sl 中插入數(shù)據(jù) 14 后:')
print(sl)
# 刪除元素
sl.delete_node(14)
print('刪除跳表 sl 中數(shù)據(jù) 14 后:')
print(sl)
# 查找數(shù)據(jù)元素 15
print('查找跳表 sl 中數(shù)據(jù)元素 15:', sl.locate(15))

以上代碼輸出如下:

在跳表 sl 中插入數(shù)據(jù) 14 后:
9-->None
0-->9-->18-->None
0-->9-->14-->18-->27-->None
0-->3-->6-->9-->12-->14-->15-->18-->21-->24-->27-->None
刪除跳表 sl 中數(shù)據(jù) 14 后:
9-->None
0-->9-->18-->None
0-->9-->18-->27-->None
0-->3-->6-->9-->12-->15-->18-->21-->24-->27-->None
查找跳表 sl 中數(shù)據(jù)元素 15: Node(15, 1)

以上就是Python數(shù)據(jù)結(jié)構(gòu)與算法之跳表詳解的詳細(xì)內(nèi)容,更多關(guān)于Python跳表的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • python用Tkinter做自己的中文代碼編輯器

    python用Tkinter做自己的中文代碼編輯器

    這篇文章主要介紹了python用Tkinter做自己的中文代碼編輯器,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Python Pandas list列表數(shù)據(jù)列拆分成多行的方法實(shí)現(xiàn)

    Python Pandas list列表數(shù)據(jù)列拆分成多行的方法實(shí)現(xiàn)

    這篇文章主要介紹了Python Pandas list(列表)數(shù)據(jù)列拆分成多行的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • python學(xué)習(xí)開發(fā)mock接口

    python學(xué)習(xí)開發(fā)mock接口

    這篇文章主要為大家詳細(xì)介紹了python學(xué)習(xí)開發(fā)mock接口的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-04-04
  • Django環(huán)境下使用Ajax的操作代碼

    Django環(huán)境下使用Ajax的操作代碼

    AJAX 的主要目標(biāo)是在不刷新整個(gè)頁(yè)面的情況下,通過(guò)后臺(tái)與服務(wù)器進(jìn)行數(shù)據(jù)交換和更新頁(yè)面內(nèi)容,通過(guò) AJAX,您可以向服務(wù)器發(fā)送請(qǐng)求并接收響應(yīng),然后使用 JavaScript 動(dòng)態(tài)地更新頁(yè)面的部分內(nèi)容,這篇文章主要介紹了Django環(huán)境下使用Ajax,需要的朋友可以參考下
    2024-03-03
  • python 獲取url中的參數(shù)列表實(shí)例

    python 獲取url中的參數(shù)列表實(shí)例

    今天小編就為大家分享一篇python 獲取url中的參數(shù)列表實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • Python模擬登錄requests.Session應(yīng)用詳解

    Python模擬登錄requests.Session應(yīng)用詳解

    這篇文章主要介紹了Python模擬登錄requests.Session應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-11-11
  • python Tkinter是什么

    python Tkinter是什么

    大家好,本篇文章主要講的是 python Tkinter是什么,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下
    2022-01-01
  • python實(shí)現(xiàn)機(jī)器學(xué)習(xí)之元線性回歸

    python實(shí)現(xiàn)機(jī)器學(xué)習(xí)之元線性回歸

    這篇文章主要為大家詳細(xì)介紹了python實(shí)現(xiàn)機(jī)器學(xué)習(xí)之元線性回歸,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • python適合人工智能的理由和優(yōu)勢(shì)

    python適合人工智能的理由和優(yōu)勢(shì)

    在本篇文章里小編給大家分享了關(guān)于python適合人工智能的理由和優(yōu)勢(shì)以及相關(guān)知識(shí)點(diǎn),需要的朋友們學(xué)習(xí)下。
    2019-06-06
  • python圖書管理系統(tǒng)

    python圖書管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了python圖書管理系統(tǒng)的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03

最新評(píng)論