數(shù)據(jù)結(jié)構(gòu)之查找
查找概論
查找表
定義
查找表(Search Table)是同一類型的數(shù)據(jù)元素(或記錄)的集合哗魂。
查找表分類
靜態(tài)查找表
靜態(tài)查找表(Static Search Table):只做查找操作的查找表。
它的主要操作有:
- 查找某個“特定的”數(shù)據(jù)元素是否存在于查找表中疏之。
- 檢索某個“特定的”數(shù)據(jù)元素和各種屬性。
動態(tài)查找表
動態(tài)查找表(Dynamic Search Table):在查找過程中同時插入不存在的數(shù)據(jù)元素速挑,
或在查找過程中刪除已經(jīng)存在的數(shù)據(jù)元素谤牡。
它的主要操作有:
- 查找時插入數(shù)據(jù)元素。
- 查找時刪除數(shù)據(jù)元素姥宝。
關(guān)鍵字
關(guān)鍵字(Key)是數(shù)據(jù)元素中某個數(shù)據(jù)項的值翅萤,又稱為鍵值。(難理解)
若此關(guān)鍵字可以唯一地標識一個記錄腊满,則稱此關(guān)鍵字為主關(guān)鍵字(Primary key)套么。
可以識別多個數(shù)據(jù)元素(或記錄)的關(guān)鍵字,是次關(guān)鍵字(Secondary Key)碳蛋。
意思是根據(jù)次關(guān)鍵字可以查詢到多條數(shù)據(jù)元素嗎胚泌?
查找定義
查找(Searching)是根據(jù)給定的某個值,在查找表中確定一個其關(guān)鍵字等于給定
值的數(shù)據(jù)元素(或記錄)疮蹦。
若查找到了數(shù)據(jù)元素诸迟,則查找成功;否則愕乎,查找失敗阵苇。
查找結(jié)構(gòu)
面向查找操作的數(shù)據(jù)結(jié)構(gòu)叫做查找結(jié)構(gòu)。
順序表查找
概念
順序表查找(Sequential Search)又叫線性查找感论,是最基本的查找技術(shù)绅项。它的查找過程是:
從表中第一個(或最后一個)記錄開始,逐個比較記錄的關(guān)鍵字和給定值比肄。
若某個記錄的關(guān)鍵字和給定值相等快耿,則查找成功。
若一直查找到最后一個(或第一個)記錄芳绩,其關(guān)鍵字都不等于給定值掀亥,則查找失敗。
代碼
int Sequential_search(int *a, int n, int key)
{
int i;
for(i = 1; i < n; i++){
if(a[i] == key){
return i;
}
}
return 0;
}
順序表查找代碼優(yōu)化
int Sequential_search(int *a, int n, int key)
{
int i;
a[0] = key;
i = 1;
while(a[i] != key){
i--;
}
return i; // 當i等于0時查找失敗
}
或
int Sequential_search(int *a, int n, int key)
{
int i;
a[n-1] = key;
while(a[i] != key){
i++;
}
return i; // 當i等于n-1時查找失敗
}
時間復雜度
$O(n)$
有序表查找
折半查找
摘抄
一看就懂的知識點妥色,一般不再打字記錄筆記搪花,直接摘抄書本。
代碼
int Binary_search(int *a, int n, int key)
{
int low, high, mid;
low = 1;
high = n;
while(low <= high){
/*1*/ mid = (low + high) / 2;
if(key > a[mid]){
low = mid + 1;
}else if(key < a[mid]){
high = mid - 1;
}else{
return mid;
}
}
/*2*/ return 0;
}
第1行嘹害,取值相當于PHP中的 floor
函數(shù)撮竿。
第2行,查找結(jié)果不是 mid
就是查找失敗笔呀。這說明幢踏,若查找表中存在要
查找的關(guān)鍵字,那么該關(guān)鍵字的位置一定是某個中間位置许师。不能理解這點房蝉。
差值查找
差值計算公式
$key-a[low]\over a[high]-a[low]$
這個公式是如何得到的僚匆?
代碼
int Binary_search(int *a, int n, int key)
{
int low, high, mid;
low = 1;
high = n;
while(low <= high){
mid = (key - a[low]) / (a[high] - a[low]);
if(key > a[mid]){
low = mid + 1;
}else if(key < a[mid]){
high = mid - 1;
}else{
return mid;
}
}
/*2*/ return 0;
}
時間復雜度
$O(logn)$
斐波那契查找
代碼
int Fibonacci_Search(int *a, int n, int key)
{
int low, high, mid, i, k;
low = 1;
high = n;
k = 0;
/*1*/ while(n>F[k]-1){ // 計算n位于斐波那契數(shù)列的位置
k++;
}
for(i = n; i < F[k]-1; i++){ // 將不滿的數(shù)值補全
a[i] = a[n];
}
while(low <= high){
mid = low + F[k-1] - 1; // 計算當前分隔的下標
if(key < a[mid]){
high = mid - 1;
/*2*/ k = k -1; // 斐波那契數(shù)列下標減一位
}else if(key > a[mid]){
low = mid + 1;
/*3*/ k = k - 2; // 斐波那契數(shù)列下標減二位
}else{
if(mid <= n){
return mid; // 若相等則說明mid即為查找到的位置
}else{
return n; // 若mid>n說明是補全數(shù)值,返回n
}
}
}
return 0;
}
理解不了第1行搭幻、第2行白热、第3行。
摘抄
不理解上圖中的范圍個數(shù)是怎么得到的粗卜。
這個圖好像有助于理解某些東西。
線性索引查找
摘抄
概念
稠密索引
概念
對于稠密索引的的索引表纳击,索引項一定是按照關(guān)鍵碼有序的排列琐驴。
分塊索引
概念
時間復雜度(不懂)
倒排索引
概念
存儲技巧
二叉排序樹
二叉排序樹查找操作代碼
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p)
{
if(!T){
*p = f;
return FALSE;
}else if(key == T->data){
*p = T;
return TRUE;
}else if(key > T->data){
return SearchBST(T->rchild, key, T, p);
}else{
return SearchBST(T->lchild, key, T, p);
}
}
二叉排序樹插入操作代碼
二叉排序樹插入操作代碼
Status InsertBST(BiTree *T, int key)
{
BiTree p, s;
if(!SearchBST(*T, key, NULL, &p)){
s = (BiTree)malloc(sizeof(BiTNode));
s->data = key;
s->lchild = s->rchild = NULL;
if(!p){
*T = s;
}else if(key > p->data){
p->rchild = s;
}else{
p->lchild = s;
}
return TRUE;
}else{
return FALSE;
}
}
如果查找到的是葉子結(jié)點昙读,插入新的結(jié)點很容易。
如果查找到的不是葉子結(jié)點,假如此結(jié)點剛好有左右孩子結(jié)點枫绅,如何插入新的結(jié)點?
查找停止的情況有兩種:找到了和關(guān)鍵字匹配的記錄港庄;查找失敗甸祭。第二種情況,必然
是遍歷到了葉子結(jié)點善已。好不能透徹地理解這點灼捂,只能大概不確定地得出這樣的結(jié)論。
創(chuàng)建二叉排序樹代碼
int CreateBST(void)
{
int i;
int a[10] = {62, 88, 58, 47, 35, 73, 51, 99, 37, 93};
BiTree T = NULL;
for(i = 0; i < 10; i++){
InsertBST(T, a[i]);
}
return 0;
}
二叉排序樹刪除操作代碼
代碼
Status Delete(BiTree *p)
{
BiTree q, s;
if((*p)->rchild == NULL){
q = *p;
*p = (*p)->lchild;
free(q);
}else if((*p)->lchild == NULL){
q = *p;
*p = (*p)->rchild;
free(q);
}else{
q = *p;
s = (*p)->lchild;
while(s->rchild){
q = s;
s = s->rchild;
}
(*p)->data = s->data;
if(q != *p){
q->rchild = s->lchild;
}else{
q->lchild = s->lchild;
}
free(s);
}
}
二叉排序樹刪除結(jié)點代碼
Status DeleteBST(BiTree *T, int key)
{
if(!*T){
return FALSE;
}else{
if(key == (*T)->data){
return Delete(T);
}else if(key < (*T)->data){
return DeleteBST(&(*T)->lchild, key);
}else{
return DeleteBST(&(*T)->rchild, key);
}
}
}
刪除操作有四種情況:
- 要刪除的結(jié)點是葉子結(jié)點换团。
- 要刪除的結(jié)點有左孩子悉稠。
- 要刪除的結(jié)點有右孩子。
- 要刪除的結(jié)點有左右孩子艘包。理解不了這種情況的代碼的猛。
代碼解讀
摘抄
概念
平衡二叉樹(AVL樹)
概念
平衡二叉樹
平衡二叉樹(Self-Balancing Binary Search Tree 或 Height-Balanced Binary Search Tree),
是一種二叉排序樹想虎,其中每一個結(jié)點的左子樹和右子樹的高度差至多等于1卦尊。
平衡因子
平衡二叉樹上的結(jié)點的左子樹減去右子樹的得到的差值,叫做平衡因子(Balance Factor)舌厨。
平衡因子可能的值是 -1岂却,0 和 1。
書中認為圖3中邓线,結(jié)點58的左子樹的高度是2淌友,我認為是3。
最小不平衡子樹
距離插入結(jié)點最近的骇陈、平衡因子的絕對值大于 1 的結(jié)點為根的子樹震庭,叫做最小不平衡子樹。
什么叫插入結(jié)點你雌?是指插入新結(jié)點的那個位置嗎器联?
結(jié)點58的左子樹的高度二汛,我認為是3。
平衡二叉樹實現(xiàn)算法代碼
旋轉(zhuǎn)操作
樹的結(jié)點結(jié)構(gòu)
typedef struct BiTNode
{
int data;
int bf; // 結(jié)點的平衡因子
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
右旋轉(zhuǎn)操作代碼
void R_Rotate(BiTree *p)
{
BiTree L;
L = (*p)->lchild; // L指向p的左子樹根結(jié)點
(*p)->lchild = L->rchild; // L的右子樹掛結(jié)為p的左子樹
L->rchild = (*p);
*p = L; // P指向新的根結(jié)點
}
左旋操作代碼
void L_Rotate(BiTree *p)
{
BiTree R;
R = (*p)->rchild; // R指向p的右子樹根結(jié)點
(*p)->rchild = R->lchild; // R的左子樹掛接為p的右子樹
R->lchild = (*p);
*p = R; // p指向新的根結(jié)點
}
理解不了拨拓。按照我的思路肴颊,p
是 R->lchild
的左子樹,可是這和左旋轉(zhuǎn)
后的結(jié)果不吻合渣磷。
重新畫了幾次婿着,根據(jù)代碼,可以得到預期效果圖了醋界【顾危可是,L
的右子樹為何
會變成 p
的左子樹形纺?
旋轉(zhuǎn)操作的關(guān)鍵在于:第一步的時候丘侠,原樹拆成了兩棵樹。旋轉(zhuǎn)過程見紙質(zhì)筆記逐样。
左平衡旋轉(zhuǎn)處理函數(shù)代碼
代碼
#define LH +1; // 左高
#define EH 0; // 等高
#define RH -1; // 右高
void LeftBalance(BiTree *T)
{
BiTree L, Lr;
L = (*T)->lchild; // L指向T的左子樹根結(jié)點
switch(L->bf){
case LH:
/*1*/ (*T)->bf = L->bf = EH;
R_Rotate(T);
break;
case RH:
Lr = L->rchild; // Lr指向T的左孩子的右子樹的根
switch(Lr->bf){ // 修改T及其左孩子的平衡因子
case LH:
(*T)->bf = RH;
L->bf = EH;
break;
case EH:
(*T)->bf = L->bf = EH;
break;
case RH:
(*T)->bf = EH;
L->bf = LH;
break;
}
Lr->bf = EH;
L_Rotate(&(*T)->lchild); // 對T的左子樹作左旋平衡處理
R_Rotate(T); // 對T做右旋平衡處理
}
}
代碼解讀
主函數(shù)代碼
代碼
Status InsertAVL(BiTree *T, int e, Status *taller)
{
if(!T){
*T = (BiTree)malloc(sizeof(BiTNode));
(*T)->data = e;
(*T)->lchild = (*T)->rchild = NULL;
(*T)->bf = EH;
*taller = TRUE;
}else{
if(e = (*T)->data){
*taller = FALSE;
return FALSE;
}
if(e <(*T)->data){
if(!InsertAVL(&(*T)->lchild, e, taller)){
return FALSE;
}
if(taller){
switch((*T)->bf){
case LH:
LeftBalance(T);
*taller = FALSE;
break;
case EH:
(*T)->bf = LH;
*taller = TRUE;
break;
case RH:
(*T)->bf = EH;
*taller = FALSE;
break;
}
}
}else{
if(!InsertAVL(&(*T)->rchild, e, taller)){
return FALSE;
}
if(*taller){
switch((*T)->bf){
case LH:
(*T)->bf = EH;
*taller = FALSE;
break;
case EH:
(*T)->bf = RH;
*taller = TRUE;
break;
case RH:
RightBalance(T);
*taller = FALSE;
break;
}
}
}
}
return TRUE;
}
代碼解讀
《大話數(shù)據(jù)結(jié)構(gòu)》中的代碼蜗字,好像有很多錯誤,只可以當作逼真的偽代碼去看待脂新。
多路查找樹(B樹)
摘抄
必要性
要操作的數(shù)據(jù)非常大挪捕,大到無法在內(nèi)存中處理。
定義
多路查找樹(multi-way search tree)的每一個結(jié)點的孩子數(shù)可以多于兩個戏羽,且每一個結(jié)點
處可以存儲多個元素担神。元素之間存在某種特定的排序關(guān)系。
2-3樹
概念
2-3樹的插入實現(xiàn)
2-3樹的刪除實現(xiàn)
可以理解這些方法能夠保證刪除的元素在被刪除后始花,新樹仍然是2-3樹妄讯,但不明白這么做的規(guī)律性,
更無能力用代碼實現(xiàn)刪除操作酷宵。
2-3-4樹
概念
2-3-4樹的插入和刪除
B樹
概念與性質(zhì)
減少內(nèi)存與外存交換數(shù)據(jù)的頻率的原因
B+樹
產(chǎn)生原因--優(yōu)化B樹
![數(shù)據(jù)結(jié)構(gòu)_查找_多路查找樹_B加樹_產(chǎn)生原因](http://7xt25g.com1.z0.glb.clouddn.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84_%E6%9F%A5%E6%89%BE_%E5%A4%9A%E8%B7%AF%E6%9F%A5%E6%89%BE%E6%A0%91_B%E6%A0%91_%E4%BA%A7%E7%94%9F%E5%8E%9F%E5%9B%A0.png)
概念
與B樹的對比
散列查找表概述
摘抄
定義
散列表查找步驟
1.存儲時亥贸,通過散列函數(shù)計算記錄的散列地址,并按該存儲地址存儲這條記錄浇垦。
2.查找時炕置,通過相同的散列函數(shù)計算記錄的散列地址,并按該散列地址讀取記錄男韧。
散列表適用場景
1.最適合的求解問題是查找與給定關(guān)鍵字等值的記錄朴摊。
2.同樣的關(guān)鍵字對應很多記錄的問題,不適合用散列表查找此虑。
3.范圍查找甚纲,不適合用散列表查找。
沖突
散列函數(shù)的構(gòu)造方法
設計好的散列函數(shù)的原則
1.計算簡單朦前。
2.散列地址分布均勻介杆。這和“使用連續(xù)的存儲空間存儲記錄”有關(guān)嗎鹃操?
直接定址法
概念
![數(shù)據(jù)結(jié)構(gòu)_查找_散列查找表概述_摘抄_直接定址法_概念](http://7xt25g.com1.z0.glb.clouddn.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84_%E6%9F%A5%E6%89%BE_%E6%95%A3%E5%88%97%E6%9F%A5%E6%89%BE%E8%A1%A8%E6%A6%82%E8%BF%B0_%E7%9B%B4%E6%8E%A5%E5%AE%9A%E5%9D%80%E6%B3%95_%E6%A6%82%E5%BF%B5.png)
優(yōu)缺點
![數(shù)據(jù)結(jié)構(gòu)_查找_散列查找表概述_摘抄_直接定址法_優(yōu)缺點](http://7xt25g.com1.z0.glb.clouddn.com/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84_%E6%9F%A5%E6%89%BE_%E6%95%A3%E5%88%97%E6%9F%A5%E6%89%BE%E8%A1%A8%E6%A6%82%E8%BF%B0_%E7%9B%B4%E6%8E%A5%E5%AE%9A%E5%9D%80%E6%B3%95_%E4%BC%98%E7%BC%BA%E7%82%B9.png)
數(shù)字分析法
只強調(diào)了抽取,對散列函數(shù)并無固定要求春哨。
平方取中法
折疊法
存儲標簽id時荆隘,我常用的方法是,存儲“1,2,3,4”這樣的字段赴背。有人提出椰拒,
計算這4個標簽ID的“位運算”,存儲“位運算”的結(jié)果凰荚。具體應用方法已經(jīng)
忘記耸三。這也是折疊法。它們都減少了占用的存儲空間浇揩。
除留余數(shù)法
隨機數(shù)法
處理散列沖突的方法
摘抄
開放定址法
再散列函數(shù)法
存儲的時候,是否應該記錄解決沖突使用的散列函數(shù)憨颠?若不記錄胳徽,讀取
數(shù)據(jù)的時候,如何計算存儲時候的地址爽彤?
鏈接地址法
公共溢出法
散列表查找實現(xiàn)
代碼
散列表結(jié)構(gòu)
#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12
#define NULLKEY -32768
typedef struct
{
int *elem; // 數(shù)據(jù)元素存儲基址
int count; // 當前數(shù)據(jù)元素個數(shù)
}HashTable;
int m = 0; // 散列表表長养盗,全局變量
散列表初始化
Status InitHashTable(HashTable *H)
{
int i;
m = HASHSIZE;
H->count = m;
H->elem = (int *)malloc(m*sizeof(int));
for(i = 0; i < m; i++){
H->elem[i] = NULLKEY;
}
return OK;
}
散列函數(shù)
int Hash(int key)
{
return key % m;
}
插入操作
void InsertHash(HashTable *H, int key)
{
int addr = Hash(key); // 求散列地址
while(H->elem[addr] != NULLKEY)
addr = (addr + 1) % m;
H->elem[addr] = key;
}
查詢操作
void SearchHash(HashTable *H, int key)
{
int addr = Hash(key);
while(H->elem[addr] != key){
addr = (addr + 1) % m;
if(H->elem[addr] != key || addr == Hash(key))
{
return UNSUCCESS;
}
}
return SUCCESS;
}
if(H->elem[addr] != key || addr == Hash(key))
中的 addr == Hash(key)
說明
循環(huán)回到原點,我不理解這點适篙。
if(H->elem[addr] != key || addr == Hash(key))
{
return UNSUCCESS;
}
這塊代碼是否有問題往核?
當 H->elem[addr] != key
成立時,應該繼續(xù)計算哈希地址嚷节。
散列表查找性能分析
喜歡我的文章請關(guān)注公眾號
ganggangwudao
或掃描個人主頁的微信二維碼聂儒,謝謝!
我篤信“寫作可以促進思考”硫痰,會以自己面臨的問題為思考素材衩婚,寫成文字,堅持每天推文效斑。
如果可以和大家共同探討非春,就更開心了。