說實話查找平時用的的確不是很多,不過也有可能是對自己放鬆了要求,最近確實過的太安逸了,甚至考試也令我無動於衷,的卻該收收心,專心投入學習了,再這樣下去就成爲廢人了。。。
一,概述
這章主要還是講查找的幾個算法,比較優劣,拓寬思路,其實就是增加難度。主要介紹三種表:
1,線性表 適用靜態查找,不涉及頻繁插入和刪除操作。
2,數表 動態查找,涉及插入刪除操作。
3,散列表 是一種先計算再比較的查找算法。
1,查找性能問題
引入平均查找長度ASL,將查找算法進行的關鍵碼的比較次數的數學期望值定義爲平均查找長度
1~n求和p*c
n:問題規模,查找集合中的記錄個數;
pi:查找第i個記錄的概率;
ci:查找第i個記錄所需的關鍵碼的比較次數。
二,線性表的查找技術
1,帶監視哨的順序查找
將所找元素放於下標0位置,免去了在查找過程中每次比較都要判斷是否越界,優化了性能
int SeqSearch(int k)
{
int i = length; //從數組高端開始比較
data[0] = k; //設置哨兵
while (data[i] != k) //不用判斷下標i是否越界
i--;
return i;
}
2,折半查找
折半查找又叫二分法,相信已經非常熟悉了,是順序存儲中最常用的方法但還是有很多要注意的地方
非遞歸:data數組封裝,獨立運用需帶參,注意變換邊界+1 -1 的位置
int BinSearch1(int k){
int mid, low = 1, high = length; //初始查找區間是[1, n]
while (low <= high) {//當區間存在時
mid = (low + high) / 2;
if (k < data[mid])
high = mid - 1;
else if (k > data[mid])
low = mid + 1;
else
return mid; //查找成功,返回元素序號
}
return 0; //查找失敗,返回0
}
遞歸:
int BinSearch2(int low, int high, int k){
if (low > high)
return 0; //遞歸的邊界條件
else {
int mid = (low + high) / 2;
if (k < data[mid])
return BinSearch2(low, mid-1, k);
else if (k > data[mid])
return BinSearch2(mid+1, high, k);
else
return mid; //查找成功,返回序號
}
}
3,折半查找判定樹
把折半查找用二叉樹表示,常考其性能分析,以11個節點的二叉樹分析:
查找成功:在表中查找任一記錄的過程,即是折半查找判定樹中從根結點到該記錄結點的路徑,和給定值的比較次數等於該記錄結點在樹中的層數。
如:給定一棵樹,該樹的ASLsucc=(1+2*2+3*4+4*4)/11=33/11=3
查找失敗的過程就是走了一條從根結點到外部結點的路徑,
和給定值進行的關鍵碼的比較次數等於該不通路徑上內部結點的個數(失敗情況下的平均查找長度等於樹的高度)。
查找不成功時的ASLusucc:= ( 3*4+4*8) /12
三,樹表的查找技術
1,二叉排序樹,左子樹小於根節點,右子樹大於根節點,仍採用樹(二叉鏈表)的方式:
樹的構建由插入完成,二叉樹不空新插入的節點必爲一個新的葉子節點,遞歸尋找位置:
#include <iostream>
using namespace std;
template <class DataType>
struct BiNode{ DataType data; BiNode *lchild, *rchild; };
class BiSortTree {
public:
BiSortTree(int a[ ], int n); //建立查找集合a[n]的二叉排序樹
~ BiSortTree( ){ Release(root); } //析構函數,同二叉鏈表的析構函數
void InOrder( ){InOrder(root);} //中序遍歷二叉樹
BiNode *InsertBST(int x) {return InsertBST(root, x);} //插入記錄x
BiNode *SearchBST(int k) {return SearchBST(root, k);} //查找值爲k的結點
void DeleteBST(BiNode *p, BiNode *f ); //刪除f的左孩子p
private:
void Release(BiNode *bt);
BiNode *InsertBST(BiNode *bt , int x);
BiNode *SearchBST(BiNode *bt, int k);
void InOrder(BiNode *bt); //中序遍歷函數調用
BiNode *root; //二叉排序樹的根指針
};
BiNode *BiSortTree::InsertBST(BiNode *bt, int x)
{
if (bt == NULL) { //找到插入位置
BiNode *s = new BiNode;
s->data = x;
s->lchild = NULL;
s->rchild = NULL;
bt = s;
return bt;
}
else if (bt->data > x)
bt->lchild = InsertBST(bt->lchild, x);
else
bt->rchild = InsertBST(bt->rchild, x);
}
BiSortTree::BiSortTree(int a[ ], int n)
{
root = NULL;
for (int i = 0; i < n; i++)
root = InsertBST(root, a[i]);
}
此外二叉排序樹的刪除也是個麻煩事,分爲三種情況:
二叉排序樹的查找性能取決於二叉排序樹的形狀,在O(log2n)和O(n)之間。
2,平衡二叉樹(首先是一棵二叉排序樹)
根結點的左子樹和右子樹的深度最多相差1,根結點的左子樹和右子樹也都是平衡二叉樹。
平衡因子:結點的平衡因子是該結點的左子樹的深度與右子樹的深度之差。 平衡因子小於等於1.
注意最小不平衡二叉樹:在平衡二叉樹的構造過程中,以距離插入結點最近的、且平衡因子的絕對值大於1的結點爲根的子樹。
構造平衡二叉樹
每插入一個節點都要判斷是否破壞平衡,從底向上找到不平衡點,根據前兩步走法分類:
設結點A爲最小不平衡子樹的根結點,對該子樹進行平衡調整歸納起來有以下四種情況:
1. LL型
2. RR型
3. LR型
4. RL型
LL,RR都是中間節點做新根,RL,LR末節點做新根,插入新節點包括三步:
(1) 查找應插位置, 同時記錄離插入位置最近的可能失衡結點A(A的平衡因子不等於0)。
(2) 插入新結點S, 並修改從A到S路徑上各結點的平衡因子。
(3) 根據A、 B的平衡因子, 判斷是否失衡以及失衡類型, 並做相應處理。
3,B_樹(平衡有序)
m階B-樹:是滿足下列特性的樹:
1, 樹中每個結點至多有m棵子樹;(求m 空指針算上找最大m)
2, 若根結點不是終端結點,則至少有兩棵子樹;
3,除根結點外,其他非終端結點至少有(m/2)向上取整 棵子樹;
4,所有葉子結點都在同一層上,B樹是高平衡的。
四,散列表(hash)的查找技術
特殊存儲,特殊查找。在存儲位置和關鍵碼之間建立聯繫,是一種計算式的查找。
散列函數:將關鍵碼映射爲散列表中適當存儲位置的函數。
散列地址:由散列函數所得的存儲位置址,相當於數組下標
衝突:對於兩個不同關鍵碼ki≠kj,有H(ki)=H(kj),即兩個不同的記錄需要存放在同一個存儲位置,ki和kj相對於H稱做同義詞。
*關鍵
1,散列函數的設計,廣泛採用除留餘數法,選p爲小於或等於表長(最好接近表長)的最小素數
2,衝突的處理:
2.1,開散列法(鏈地址法,拉鍊法)
將所有散列地址相同的記錄,即所有同義詞的記錄存儲在一個單鏈表中(稱爲同義詞子表),在散列表中存儲的是所有同義詞子表的頭指針。
2.2,閉散列法(開放定址發)一維數組解決
線性探測法
從衝突的下一個位置起依次尋找空地, Hi=(H(key)+di) % m 堆積:在處理衝突的過程中出現的非同義詞之間對同一個散列地址爭奪的現象。成功探測經過的鍵值不一定都是同義詞,可能是相應值被擠入不同房間。
二次探測法
Hi=(H(key)+di)% m
從衝突開始先向下一個找下一個沒空間找上一個,依次往復
2.3 建立公共溢出區
基本表+溢出表