数据结构中基本查找算法总结
原文地址:https://www.cnblogs.com/xuzhp/p/4638937.html
基本查找算法?
?
一、查找的基本概念
查找,也可稱檢索,是在大量的數(shù)據(jù)元素中找到某個特定的數(shù)據(jù)元素而進行的工作。查找是一種操作。
?
二、順序查找
針對無序序列的一種最簡單的查找方式。
時間復雜度為O(n)。
?
三、折半查找
針對已排序序列的一種查找方式。并且只適用于順序存儲結構的序列。要求序列中的元素基本不變,在需要做刪除和插入操作的時候,會影響檢索效率。
時間復雜度為O(logN)。
?
四、B樹
B樹又稱二叉排序樹(Binary Sort Tree)。
1、概念:
? 它或者是一棵空樹;或者是具有下列性質的二叉樹:
(1)若左子樹不空,則左子樹上所有結點的值均小于左子樹所在樹的根結點的值;
(2)若右子樹不空,則右子樹上所有結點的值均大于右子樹所在樹的根結點的值;
(3)左、右子樹也分別為二叉排序樹;
2、B樹的查找:
時間復雜度與樹的深度的有關。
步驟:若根結點的關鍵字值等于查找的關鍵字,成功。
否則:若小于根結點的關鍵字值,遞歸查左子樹。
若大于根結點的關鍵字值,遞歸查右子樹。
若子樹為空,查找不成功。
3、B樹的插入:
首先執(zhí)行查找算法,找出被插結點的父親結點。
判斷被插結點是其父親結點的左兒子還是右兒子。將被插結點作為葉子結點插入。
若二叉樹為空。則首先單獨生成根結點。
注意:新插入的結點總是葉子結點,所以算法復雜度是O(h)。
4、B樹的刪除:
如果刪除的結點沒有孩子,則刪除后算法結束;
如果刪除的結點只有一個孩子,則刪除后該孩子取代被刪除結點的位置;
如果刪除的結點有兩個孩子,則選擇該結點的后繼結點(該結點右孩子為根的樹中的左子樹中的值最小的點)作為新的根,同時在該后繼結點開始,執(zhí)行前兩種刪除算法,刪除算法結束。
?
5、B+樹
一棵m階的B+樹滿足下列條件:
(1)每個結點最多m個孩子。
(2)除根結點和葉子結點外,其它每個結點至少有ém/2ù個孩子。
(3)根結點至少有兩個孩子。
(4)所有的葉子結點在同一層,且包含了所有關鍵字信息。
(5)有k個孩子的分支結點包含k個關鍵字。
例如:
?
五、散列(hash)表
關鍵字:哈希函數(shù)、裝填因子、沖突、同義詞;
?
關鍵字和和存儲的地址建立一個對應的關系:
Add = Hash(key);
?
解決沖突方法:
開放定址法 – 探測方式:線性探測、二次探測。
分離鏈接法 – 利用鏈表的方式。
?
查找找效率不依賴于數(shù)據(jù)長度n,查找效率非常快,很多能達到O(1),查找的效率是a(裝填因子)的函數(shù),而不是n的函數(shù)。因此不管n多大都可以找到一個合適的裝填因子以便將平均查找長度限定在一個范圍內(nèi)。
?
一 定義
二叉查找樹(Binary Search Tree),也稱有序二叉樹(ordered binary tree),排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹:
1. 若任意節(jié)點的左子樹不空,則左子樹上所有結點的值均小于它的根結點的值;
2. 若任意節(jié)點的右子樹不空,則右子樹上所有結點的值均大于它的根結點的值;
3. 任意節(jié)點的左、右子樹也分別為二叉查找樹。
4. 沒有鍵值相等的節(jié)點(no duplicate nodes)。
如下圖,這個是普通的二叉樹:
在此基礎上,加上節(jié)點之間的大小關系,就是二叉查找樹:
二 實現(xiàn)
在實現(xiàn)中,我們需要定義一個內(nèi)部類Node,它包含兩個分別指向左右節(jié)點的Node,一個用于排序的Key,以及該節(jié)點包含的值Value,還有一個記錄該節(jié)點及所有子節(jié)點個數(shù)的值Number。
public class BinarySearchTreeSymbolTable<TKey, TValue> : SymbolTables<TKey, TValue> where TKey : IComparable<TKey>, IEquatable<TValue> {private Node root;private class Node{public Node Left { get; set; }public Node Right { get; set; }public int Number { get; set; }public TKey Key { get; set; }public TValue Value { get; set; } public Node(TKey key, TValue value, int number){this.Key = key;this.Value = value;this.Number = number;} }…
}
查找
查找操作和二分查找類似,將key和節(jié)點的key比較,如果小于,那么就在Left Node節(jié)點查找,如果大于,則在Right Node節(jié)點查找,如果相等,直接返回Value。
?
該方法實現(xiàn)有迭代和遞歸兩種。
遞歸的方式實現(xiàn)如下:
public override TValue Get(TKey key) {TValue result = default(TValue);Node node = root;while (node != null){ if (key.CompareTo(node.Key) > 0){node = node.Right;}else if (key.CompareTo(node.Key) < 0){node = node.Left;}else{result = node.Value;break;} } return result;}
迭代的如下:
public TValue Get(TKey key) {return GetValue(root, key); }private TValue GetValue(Node root, TKey key)
{
if (root == null) return default(TValue);
int cmp = key.CompareTo(root.Key);
if (cmp > 0) return GetValue(root.Right, key);
else if (cmp < 0) return GetValue(root.Left, key);
else return root.Value;
}
插入
插入和查找類似,首先查找有沒有和key相同的,如果有,更新;如果沒有找到,那么創(chuàng)建新的節(jié)點。并更新每個節(jié)點的Number值,代碼實現(xiàn)如下:
public override void Put(TKey key, TValue value) {root = Put(root, key, value); }private Node Put(Node x, TKey key, TValue value)
{
//如果節(jié)點為空,則創(chuàng)建新的節(jié)點,并返回
//否則比較根據(jù)大小判斷是左節(jié)點還是右節(jié)點,然后繼續(xù)查找左子樹還是右子樹
//同時更新節(jié)點的Number的值
if (x == null) return new Node(key, value, 1);
int cmp = key.CompareTo(x.Key);
if (cmp < 0) x.Left = Put(x.Left, key, value);
else if (cmp > 0) x.Right = Put(x.Right, key, value);
else x.Value = value;
x.Number = Size(x.Left) + Size(x.Right) + 1;
return x;
}
private int Size(Node node)
{
if (node == null) return 0;
else return node.Number;
}
? 插入操作圖示如下:
下面是插入動畫效果:
隨機插入形成樹的動畫如下,可以看到,插入的時候樹還是能夠保持近似平衡狀態(tài):
最大最小值
如下圖可以看出,二叉查找樹的最大最小值是有規(guī)律的:
從圖中可以看出,二叉查找樹中,最左和最右節(jié)點即為最小值和最大值,所以我們只需迭代調用即可。
public override TKey GetMax() {TKey maxItem = default(TKey);Node s = root;while (s.Right != null){s = s.Right;}maxItem = s.Key;return maxItem; }public override TKey GetMin()
{
TKey minItem = default(TKey);
Node s = root;
while (s.Left != null)
{
s = s.Left;
}
minItem = s.Key;
return minItem;
}
以下是遞歸的版本:
public TKey GetMaxRecursive() {return GetMaxRecursive(root); }private TKey GetMaxRecursive(Node root)
{
if (root.Right == null) return root.Key;
return GetMaxRecursive(root.Right);
}
public TKey GetMinRecursive()
{
return GetMinRecursive(root);
}
private TKey GetMinRecursive(Node root)
{
if (root.Left == null) return root.Key;
return GetMinRecursive(root.Left);
}
Floor和Ceiling
查找Floor(key)的值就是所有<=key的最大值,相反查找Ceiling的值就是所有>=key的最小值,下圖是Floor函數(shù)的查找示意圖:
以查找Floor為例,我們首先將key和root元素比較,如果key比root的key小,則floor值一定在左子樹上;如果比root的key大,則有可能在右子樹上,當且僅當其右子樹有一個節(jié)點的key值要小于等于該key;如果和root的key相等,則floor值就是key。根據(jù)以上分析,Floor方法的代碼如下,Ceiling方法的代碼類似,只需要把符號換一下即可:
public TKey Floor(TKey key) {Node x = Floor(root, key);if (x != null) return x.Key;else return default(TKey); }private Node Floor(Node x, TKey key)
{
if (x == null) return null;
int cmp = key.CompareTo(x.Key);
if (cmp == 0) return x;
if (cmp < 0) return Floor(x.Left, key);
else
{
Node right = Floor(x.Right, key);
if (right == null) return x;
else return right;
}
}
刪除
刪除元素操作在二叉樹的操作中應該是比較復雜的。首先來看下比較簡單的刪除最大最小值得方法。
以刪除最小值為例,我們首先找到最小值,及最左邊左子樹為空的節(jié)點,然后返回其右子樹作為新的左子樹。操作示意圖如下:
代碼實現(xiàn)如下:
public void DelMin() {root = DelMin(root); }private Node DelMin(Node root)
{
if (root.Left == null) return root.Right;
root.Left = DelMin(root.Left);
root.Number = Size(root.Left) + Size(root.Right) + 1;
return root;
}
刪除最大值也是類似。
現(xiàn)在來分析一般情況,假定我們要刪除指定key的某一個節(jié)點。這個問題的難點在于:刪除最大最小值的操作,刪除的節(jié)點只有1個子節(jié)點或者沒有子節(jié)點,這樣比較簡單。但是如果刪除任意節(jié)點,就有可能出現(xiàn)刪除的節(jié)點有0個,1 個,2個子節(jié)點的情況,現(xiàn)在來逐一分析。
當刪除的節(jié)點沒有子節(jié)點時,直接將該父節(jié)點指向該節(jié)點的link設置為null。
?
當刪除的節(jié)點只有1個子節(jié)點時,將該自己點替換為要刪除的節(jié)點即可。
當刪除的節(jié)點有2個子節(jié)點時,問題就變復雜了。
假設我們刪除的節(jié)點t具有兩個子節(jié)點。因為t具有右子節(jié)點,所以我們需要找到其右子節(jié)點中的最小節(jié)點,替換t節(jié)點的位置。這里有四個步驟:
1. 保存帶刪除的節(jié)點到臨時變量t
2. 將t的右節(jié)點的最小節(jié)點min(t.right)保存到臨時節(jié)點x
3. 將x的右節(jié)點設置為deleteMin(t.right),該右節(jié)點是刪除后,所有比x.key最大的節(jié)點。
4. 將x的做節(jié)點設置為t的左節(jié)點。
整個過程如下圖:
對應代碼如下:
public void Delete(TKey key) {root =Delete(root, key);}
private Node Delete(Node x, TKey key)
{
int cmp = key.CompareTo(x.Key);
if (cmp > 0) x.Right = Delete(x.Right, key);
else if (cmp < 0) x.Left = Delete(x.Left, key);
else
{
if (x.Left == null) return x.Right;
else if (x.Right == null) return x.Left;
else
{
Node t = x;
x = GetMinNode(t.Right);
x.Right = DelMin(t.Right);
x.Left = t.Left;
}
}
x.Number = Size(x.Left) + Size(x.Right) + 1;
return x;
}
private Node GetMinNode(Node x)
{
if (x.Left == null) return x;
else return GetMinNode(x.Left);
}
以上二叉查找樹的刪除節(jié)點的算法不是完美的,因為隨著刪除的進行,二叉樹會變得不太平衡,下面是動畫演示。
三 分析
二叉查找樹的運行時間和樹的形狀有關,樹的形狀又和插入元素的順序有關。在最好的情況下,節(jié)點完全平衡,從根節(jié)點到最底層葉子節(jié)點只有l(wèi)gN個節(jié)點。在最差的情況下,根節(jié)點到最底層葉子節(jié)點會有N各節(jié)點。在一般情況下,樹的形狀和最好的情況接近。
在分析二叉查找樹的時候,我們通常會假設插入的元素順序是隨機的。對BST的分析類似與快速排序中的查找:
BST中位于頂部的元素就是快速排序中的第一個劃分的元素,該元素左邊的元素全部小于該元素,右邊的元素均大于該元素。
對于N個不同元素,隨機插入的二叉查找樹來說,其平均查找/插入的時間復雜度大約為2lnN,這個和快速排序的分析一樣,具體的證明方法不再贅述,參照快速排序。
?
四 總結
有了前篇文章?二分查找的分析,對二叉查找樹的理解應該比較容易。下面是二叉查找樹的時間復雜度:
它和二分查找一樣,插入和查找的時間復雜度均為lgN,但是在最壞的情況下仍然會有N的時間復雜度。原因在于插入和刪除元素的時候,樹沒有保持平衡。我們追求的是在最壞的情況下仍然有較好的時間復雜度,這就是后面要講的平衡查找樹的內(nèi)容了。
分類: 數(shù)據(jù)結構 標簽: 數(shù)據(jù)結構, 查找算法, 二叉樹 上一篇:數(shù)據(jù)結構中的基本排序算法總結? 下一篇:[軟件架構]模塊化編程思想及(C++)實踐
總結
以上是生活随笔為你收集整理的数据结构中基本查找算法总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 知识图谱-数据集
- 下一篇: 以太网和路由设置,内网和外网同时上