常用查詢資料結構及演算法(Python實現)
目錄
一、基本概念
二、無序表查詢
三、有序表查詢
3.1 二分查詢(Binary Search)
3.2 插值查詢
3.3 斐波那契查詢
四、線性索引查詢
4.1 稠密索引
4.2 分塊索引
4.3 倒排索引
五、二叉排序樹
六、 平衡二叉樹
七、多路查詢樹(B樹)
7.1 2-3樹
7.2 2-3-4樹
7.3 B樹
7.4 B+樹
八、散列表(雜湊表)
8.1 雜湊函式的構造方法
8.2 處理雜湊衝突
8.3 散列表查詢實現
8.4 散列表查詢效能分析
參考書目《大話資料結構》
一、基本概念
查詢(Searching)就是根據給定的某個值,在查詢表中確定一個其關鍵字等於給定值的資料元素(或記錄)。
查詢表(Search Table):由同一型別的資料元素(或記錄)構成的集合
關鍵字(Key):資料元素中某個資料項的值,又稱為鍵值。
主鍵(Primary Key):可唯一地標識某個資料元素或記錄的關鍵字。
查詢表按照操作方式可分為:
- 靜態查詢表(Static Search Table):只做查詢操作的查詢表。它的主要操作是:
- 查詢某個“特定的”資料元素是否在表中
- 檢索某個“特定的”資料元素和各種屬性
- 動態查詢表(Dynamic Search Table):在查詢中同時進行插入或刪除等操作:
- 查詢時插入資料
- 查詢時刪除資料
二、無序表查詢
也就是資料不排序的線性查詢,遍歷資料元素。
演算法分析:最好情況是在第一個位置就找到了,此為O(1);最壞情況在最後一個位置才找到,此為O(n);所以平均查詢次數為(n+1)/2。最終時間複雜度為O(n)
12345678910111213141516 | # 最基礎的遍歷無序列表的查詢演算法# 時間複雜度O(n)def sequential_search(lis,key):length=len(lis)foriinrange(length):iflis[i]==key:returnielse:returnFalseif__name__=='__main__':LIST=[1,5,8,123,22,54,7,99,300,222]result=sequential_search(LIST,123)print(result) |
三、有序表查詢
查詢表中的資料必須按某個主鍵進行某種排序!
1. 二分查詢(Binary Search)
演算法核心:在查詢表中不斷取中間元素與查詢值進行比較,以二分之一的倍率進行表範圍的縮小。
Python12345678910111213141516171819202122232425 | # 針對有序查詢表的二分查詢演算法# 時間複雜度O(log(n))defbinary_search(lis,key):low=0high=len(lis)-1time=0whilelow<high:time+=1mid=int((low+high)/2)ifkey<lis[mid]:high=mid-1elifkey>lis[mid]:low=mid+1else:# 列印折半的次數print("times: %s"%time)returnmidprint("times: %s"%time)returnFalseif__name__=='__main__':LIST=[1,5,7,8,22,54,99,123,200,222,444]result=binary_search(LIST,99)print(result) |
2. 插值查詢
二分查詢法雖然已經很不錯了,但還有可以優化的地方。
有的時候,對半過濾還不夠狠,要是每次都排除十分之九的資料豈不是更好?選擇這個值就是關鍵問題,插值的意義就是:以更快的速度進行縮減。
插值的核心就是使用公式:
value = (key – list[low])/(list[high] – list[low])
用這個value來代替二分查詢中的1/2。
上面的程式碼可以直接使用,只需要改一句。
123456789101112131415161718192021222324252627 | # 插值查詢演算法# 時間複雜度O(log(n))defbinary_search(lis,key):low=0high=len(lis)-1time=0whilelow<high:time+=1# 計算mid值是插值演算法的核心程式碼mid=low+int((high-low)*(key-lis[low])/(lis[high]-lis[low]))print("mid=%s, low=%s, high=%s"%(mid,low,high))ifkey<lis[mid]:high=mid-1elifkey>lis[mid]:low=mid+1else:# 列印查詢的次數print("times: %s"%time)returnmidprint("times: %s"%time)returnFalseif__name__=='__main__':LIST=[1,5,7,8,22,54,99,123,200,222,444]result=binary_search(LIST,444)print(result) |
插值演算法的總體時間複雜度仍然屬於O(log(n))級別的。其優點是,對於表內資料量較大,且關鍵字分佈比較均勻的查詢表,使用插值演算法的平均效能比二分查詢要好得多。反之,對於分佈極端不均勻的資料,則不適合使用插值演算法。
3. 斐波那契查詢
由插值演算法帶來的啟發,發明了斐波那契演算法。其核心也是如何優化那個縮減速率,使得查詢次數儘量降低。
使用這種演算法,前提是已經有一個包含斐波那契資料的列表
F = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,…]
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556 | # 斐波那契查詢演算法# 時間複雜度O(log(n))deffibonacci_search(lis,key):# 需要一個現成的斐波那契列表。其最大元素的值必須超過查詢表中元素個數的數值。F=[1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368]low=0high=len(lis)-1# 為了使得查詢表滿足斐波那契特性,在表的最後新增幾個同樣的值# 這個值是原查詢表的最後那個元素的值# 新增的個數由F[k]-1-high決定k=0whilehigh>F[k]-1:k+=1print(k)i=highwhileF[k]-1>i:lis.append(lis[high])i+=1print(lis)# 演算法主邏輯。time用於展示迴圈的次數。time=0whilelow<=high:time+=1# 為了防止F列表下標溢位,設定if和elseifk<2:mid=lowelse:mid=low+F[k-1]-1print("low=%s, mid=%s, high=%s"%(low,mid,high))ifkey<lis[mid]:high=mid-1k-=1elifkey>lis[mid]:low=mid+1k-=2else:ifmid<=high:# 列印查詢的次數print("times: %s"%time)returnmidelse:print("times: %s"%time)returnhighprint("times: %s"%time)returnFalseif__name__=='__main__':LIST=[1,5,7,8,22,54,99,123,200,222,444]result=fibonacci_search(LIST,444)print(result) |
演算法分析:斐波那契查詢的整體時間複雜度也為O(log(n))。但就平均效能,要優於二分查詢。但是在最壞情況下,比如這裡如果key為1,則始終處於左側半區查詢,此時其效率要低於二分查詢。
總結:二分查詢的mid運算是加法與除法,插值查詢則是複雜的四則運算,而斐波那契查詢只是最簡單的加減運算。在海量資料的查詢中,這種細微的差別可能會影響最終的查詢效率。因此,三種有序表的查詢方法本質上是分割點的選擇不同,各有優劣,應根據實際情況進行選擇。
四、線性索引查詢
對於海量的無序資料,為了提高查詢速度,一般會為其構造索引表。
索引就是把一個關鍵字與它相對應的記錄進行關聯的過程。
一個索引由若干個索引項構成,每個索引項至少包含關鍵字和其對應的記錄在儲存器中的位置等資訊。
索引按照結構可以分為:線性索引、樹形索引和多級索引。
線性索引:將索引項的集合通過線性結構來組織,也叫索引表。
線性索引可分為:稠密索引、分塊索引和倒排索引
- 稠密索引
稠密索引指的是線上性索引中,為資料集合中的每個記錄都建立一個索引項。
這其實就相當於給無序的集合,建立了一張有序的線性表。其索引項一定是按照關鍵碼進行有序的排列。
這也相當於把查詢過程中需要的排序工作給提前做了。
- 分塊索引
給大量的無序資料集合進行分塊處理,使得塊內無序,塊與塊之間有序。
這其實是有序查詢和無序查詢的一種中間狀態或者說妥協狀態。因為資料量過大,建立完整的稠密索引耗時耗力,佔用資源過多;但如果不做任何排序或者索引,那麼遍歷的查詢也無法接受,只能折中,做一定程度的排序或索引。
分塊索引的效率比遍歷查詢的O(n)要高一些,但與二分查詢的O(logn)還是要差不少。
- 倒排索引
不是由記錄來確定屬性值,而是由屬性值來確定記錄的位置,這種被稱為倒排索引。其中記錄號表儲存具有相同次關鍵字的所有記錄的地址或引用(可以是指向記錄的指標或該記錄的主關鍵字)。
倒排索引是最基礎的搜尋引擎索引技術。
五、二叉排序樹
二叉排序樹又稱為二叉查詢樹。它或者是一顆空樹,或者是具有下列性質的二叉樹:
- 若它的左子樹不為空,則左子樹上所有節點的值均小於它的根結構的值;
- 若它的右子樹不為空,則右子樹上所有節點的值均大於它的根結構的值;
- 它的左、右子樹也分別為二叉排序樹。
構造一顆二叉排序樹的目的,往往不是為了排序,而是為了提高查詢和插入刪除關鍵字的速度。
二叉排序樹的操作:
- 查詢:對比節點的值和關鍵字,相等則表明找到了;小了則往節點的左子樹去找,大了則往右子樹去找,這麼遞迴下去,最後返回布林值或找到的節點。
- 插入:從根節點開始逐個與關鍵字進行對比,小了去左邊,大了去右邊,碰到子樹為空的情況就將新的節點連結。
- 刪除:如果要刪除的節點是葉子,直接刪;如果只有左子樹或只有右子樹,則刪除節點後,將子樹連結到父節點即可;如果同時有左右子樹,則可以將二叉排序樹進行中序遍歷,取將要被刪除的節點的前驅或者後繼節點替代這個被刪除的節點的位置。
Python12345678910111213141516 相關推薦
常用查詢資料結構及演算法(Python實現)
目錄 一、基本概念 二、無序表查詢 三、有序表查詢 3.1 二分查詢(Binary Search) 3.2 插值查詢 3.3 斐波那契查詢 四、線性索引查詢 4.1 稠密索引 4.2 分塊索引 4.3 倒排索引 五、二叉排序樹 六、 平衡二叉樹 七、多路查詢樹(B樹) 7.1 2-3樹 7.2 2-3-4樹
資料結構與演算法(JavaScript實現)
1.如何在陣列中間位置新增陣列function avaerageAdd(){ var nums = [1,2,3,4,5,6,7,8]; var newElements = [233,666]; nums.splice.apply(nums, [Math.floo
資料結構-迴圈佇列(Python實現)
今天我們來到了迴圈佇列這一節,之前的文章中,我介紹過了用python自帶的列表來實現佇列,這是最簡單的實現方法。 但是,我們都知道,在列表中刪除第一個元素和刪除最後一個元素花費的時間代價是不一樣的,刪除列表的第一個元素,那麼在它之後的所有元素都要進行移動。所以當列表特別長的時候,這個代價就比較明顯了。我們本文
為什麼我要放棄javaScript資料結構與演算法(第一章)—— JavaScript簡介
資料結構與演算法一直是我算比較薄弱的地方,希望通過閱讀《javaScript資料結構與演算法》可以有所改變,我相信接下來的記錄不單單對於我自己有幫助,也可以幫助到一些這方面的小白,接下來讓我們一起學習。 第一章 JavaScript簡介 眾所周知,JavaScript是一門非常強大的程式語言,不僅可以用於
為什麼我要放棄javaScript資料結構與演算法(第二章)—— 陣列
第二章 陣列 幾乎所有的程式語言都原生支援陣列型別,因為陣列是最簡單的記憶體資料結構。JavaScript裡也有陣列型別,雖然它的第一個版本並沒有支援陣列。本章將深入學習陣列資料結構和它的能力。 為什麼用陣列 需求:儲存所在城市每個月的平均溫度,可以這麼實現 var averageTemp1 = 43.3;
資料結構與演算法(C語言) | 二叉排序樹
二叉排序樹的定義—— 二叉排序樹 ( Binary Sort Tree) 或者為空;或者是具有如下特性的二叉樹: (1)若根的左子樹不空,則左子樹上所有結點的關鍵字均小於根結點的關鍵字; (2)若
資料結構與演算法(十二)並查集(Union Find)
本文主要包括以下內容: 並查集的概念 並查集的操作 並查集的實現和優化 Quick Find Quick Union 基於size的優化 基於rank的優化 路徑壓縮優化 並查集的時間複雜度 並查集的概念 在電腦科學中,並查集 是一種樹形的資料結
資料結構與演算法(Java描述)-20、圖、圖的鄰接矩陣、有向圖的廣度優先遍歷與深度優先遍歷
一、圖的基本概念圖:是由結點集合及結點間的關係集合組成的一種資料結構。結點和邊:圖中的頂點稱作結點,圖中的第i個結點記做vi。有向圖: 在有向圖中,結點對<x ,y>是有序的,結點對<x,y>稱為從結點x到結點y的一條有向邊,因此,<x,y>與<y,x>是兩條不同的邊。有向圖
資料結構與演算法(java版)
轉自:http://blog.csdn.net/column/details/datastructureinjava.html 目錄 (1)資料結構與演算法概念解析 (2)資料結構之陣列 (3)資料結構之棧 (4)資料結構之佇列 (5)資
[資料結構]貪婪演算法(Dijkstra Algorithm)
下週要期末考了,好虛啊=_= 複習一波資料結構發現最後一章居然還有兩個演算法沒看 所以順道把貪婪和最小生成樹看一下~ 這篇就只講貪婪演算法啦,一會再開一篇。 一、貪婪演算法簡介 貪心演算法(又稱貪婪演算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不
資料結構與演算法(Java描述)-15、稀疏矩陣以及稀疏矩陣的三元組實現
一、稀疏矩陣 對一個m×n的矩陣,設s為矩陣元素個數的總和,有s=m*n,設t為矩陣中非零元素個數的總和,滿足t<<s的矩陣稱作稀疏矩陣。符號“<<”讀作小於小於。簡單說,稀疏矩陣就是非零元素個數遠遠小於元素個數的矩陣。相對於稀疏矩陣來說,一個不稀疏的矩陣也稱作稠密矩陣。
走進資料結構和演算法(c++版)(3)——線性表的鏈式儲存結構
線性表的鏈式儲存結構 我們知道線性表的順序儲存結構在插入和刪除操作時需要移動大量的資料,他們的時間複雜度為O(n)O(n)。當我們需要經常插入和刪除資料時,順序儲存結構就不適用了,這時我們就需要用到線性表的鏈式儲存結構。 線性表的鏈式儲存結構的特點是
常用資料預處理技術(python實現)
一、特徵規範化方法1. 均值移除( Mean removal)把每個特徵的平均值移除,以保證特徵均值為0(即標準化處理)。這樣做可以消除特徵彼此間的偏差(bias),變為均值為0方差為1的資料集。(x-mean)/標準差#均值移除from sklearn import pre
資料結構與演算法(C語言) | 線性表(順序儲存、鏈式儲存)
線性表是最常用最簡單的線性結構 線性結構具有以下基本特徵: 線性結構是一個數據元素的有序(次序)集(處理元素有限)。若該集合非空,則 1)必存在唯一的一個“第一元素”; 2)必存在唯一的一個“最後元素”; 3)除第一元素之外,其餘每個元素均有唯一的前
資料結構與演算法(十一)Trie字典樹
本文主要包括以下內容: Trie字典樹的基本概念 Trie字典樹的基本操作 插入 查詢 字首查詢 刪除 基於連結串列的Trie字典樹 基於Trie的Set效能對比 LeetCode相關線段樹的問題 LeetCode第208號問題 LeetCode第211
資料結構-連結串列(java實現)
/** * 連結串列節點定義 * */ private class Node { private Object data; private Node next; public Node getNext() { return next;
排序演算法(python實現)
排序演算法一共有2類: 演算法時間fuzh複雜讀與nlogn比較,大為非線性類,小為線性類; 非線性類比較類排序有:交換排序(冒泡,快速),插入排序(簡單插入,shell),歸併排序(2路歸併,多路歸併),選擇排序(簡單選擇,堆排序);
基於畫素清晰度的影象融合演算法(Python實現)
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt import cv2 from math import log from PIL import Image import d
C4.5決策樹演算法(Python實現)
C4.5演算法使用資訊增益率來代替ID3的資訊增益進行特徵的選擇,克服了資訊增益選擇特徵時偏向於特徵值個數較多的不足。資訊增益率的定義如下: # -*- coding: utf-8 -*- from numpy import * import ma
資料結構--連結串列(C實現)
連結串列是學習資料結構的基礎,何為資料結構,簡單來說就是研究資料的儲存問題,演算法是對資料的操作問題,儲存主要是個人的儲存和個人與個人的關係的儲存,研究如何將我們現實生活中各種事物及其關係的儲存。 連結串列的優缺點(相比於同是線性結構的陣列): 1.空間沒有限制