查找表是由同一類型的數據元素構成的集合。例如電話號碼簿和字典均可以看做是一張查找表。
在查找表中只作查找操做,而不改動表中數據元素,稱此類查找表爲靜態查找表;反之,在查找表中作查找操做的同時進行插入數據或者刪除數據的操做,稱此類表爲動態查找表。算法
順序查找的查找過程爲:從表中的最後一個數據元素開始,逐個同記錄的關鍵字作比較,若是匹配成功,則查找成功;反之,若是直到表中第一個關鍵字查找完也沒有成功匹配,則查找失敗
同時,在程序中初始化建立查找表時,因爲是順序存儲,因此將全部的數據元素存儲在數組中,可是把第一個位置留給了用戶用於查找的關鍵字。例如,在順序表{1,2,3,4,5,6}中查找數據元素值爲 7 的元素,則添加後的順序表爲:
圖1
順序表的一端添加用戶用於搜索的關鍵字,稱做「監視哨」。
圖 1 中監視哨的位置也可放在數據元素 6 的後面(這種狀況下,整個查找的順序應有逆向查找改成順序查找)。
放置好監視哨以後,順序表遍歷從沒有監視哨的一端依次進行,若是查找表中有用戶須要的數據,則程序輸出該位置;反之,程序會運行至監視哨,此時匹配成功,程序中止運行,可是結果是查找失敗。
代碼實現:數組
/* * @Description: 順序查找算法 * @Version: V1.0 * @Autor: Carlos * @Date: 2020-05-22 15:52:11 * @LastEditors: Carlos * @LastEditTime: 2020-06-03 16:56:06 */ #include <stdio.h> #include <stdlib.h> #define keyType int typedef struct { //查找表中每一個數據元素的值 keyType key; //若是須要,還能夠添加其餘屬性 }ElemType; typedef struct{ //存放查找表中數據元素的數組 ElemType *elem; //記錄查找表中數據的總數量 int length; }SSTable; /** * @Description: 建立查找表 * @Param: SSTable **st 指向結構體指針的指針,即指針變量的指針,int length 建立的二叉樹的長度 * @Return: 無 * @Author: Carlos */ void Create(SSTable **st,int length){ (*st)=(SSTable*)malloc(sizeof(SSTable)); (*st)->length=length; //結構體指針分配空間 (*st)->elem =(ElemType*)malloc((length+1)*sizeof(ElemType)); printf("輸入表中的數據元素:\n"); //根據查找表中數據元素的總長度,在存儲時,從數組下標爲 1 的空間開始存儲數據 for (int i=1; i<=length; i++) { scanf("%d",&((*st)->elem[i].key)); } } /** * @Description: 查找表查找的功能函數,其中key爲關鍵字 * @Param: SSTable *st指向結構體變量的指針,keyType key 要查找的元素 * @Return: key在查找表中的位置 * @Author: Carlos */ int Search_seq(SSTable *st,keyType key){ //將關鍵字做爲一個數據元素存放到查找表的第一個位置,起監視哨的做用 st->elem[0].key=key; int i=st->length; //從查找表的最後一個數據元素依次遍歷,一直遍歷到數組下標爲0 while (st->elem[i].key!=key) { i--; } //若是 i=0,說明查找失敗;反之,返回的是含有關鍵字key的數據元素在查找表中的位置 return i; } int main(int argc, const char * argv[]) { SSTable *st; Create(&st, 6); getchar(); printf("請輸入查找數據的關鍵字:\n"); int key; scanf("%d",&key); int location=Search_seq(st, key); if (location==0) { printf("查找失敗"); }else{ printf("數據在查找表中的位置爲:%d",location); } return 0; }
折半查找,也稱二分查找,在某些狀況下相比於順序查找,使用折半查找算法的效率更高。可是該算法的使用的前提是靜態查找表中的數據必須是有序的。
例如,在{5,21,13,19,37,75,56,64,88 ,80,92}這個查找表使用折半查找算法查找數據以前,須要首先對該表中的數據按照所查的關鍵字進行排序:{5,13,19,21,37,56,64,75,80,88,92}。
在折半查找以前對查找表按照所查的關鍵字進行排序的意思是:若查找表中存儲的數據元素含有多個關鍵字時,使用哪一種關鍵字作折半查找,就須要提早以該關鍵字對全部數據進行排序。函數
對靜態查找表{5,13,19,21,37,56,64,75,80,88,92}採用折半查找算法查找關鍵字爲 21 的過程爲:
圖2
後一個關鍵字,指針 mid 指向處於 low 和 high 指針中間位置的關鍵字。在查找的過程當中每次都同 mid 指向的關鍵字進行比較,因爲整個表中的數據是有序的,所以在比較以後就能夠知道要查找的關鍵字的大體位置。
例如在查找關鍵字 21 時,首先同 56 做比較,因爲21 < 56,並且這個查找表是按照升序進行排序的,因此能夠斷定若是靜態查找表中有 21 這個關鍵字,就必定存在於 low 和 mid 指向的區域中間。
所以,再次遍歷時須要更新 high 指針和 mid 指針的位置,令 high 指針移動到 mid 指針的左側一個位置上,同時令 mid 從新指向 low 指針和 high 指針的中間位置。如圖3所示:
圖3
一樣,用 21 同 mid 指針指向的 19 做比較,19 < 21,因此能夠斷定 21 若是存在,確定處於 mid 和 high 指向的區域中。因此令 low 指向 mid 右側一個位置上,同時更新 mid 的位置。
圖4
當第三次作判斷時,發現 mid 就是關鍵字 21 ,查找結束。
注意:在作查找的過程當中,若是 low 指針和 high 指針的中間位置在計算時位於兩個關鍵字中間,即求得 mid 的位置不是整數,須要統一作取整操做。
折半查找的實現代碼:3d
/* * @Description: 折半查找.前提是靜態查找表中的數據必須是有序的。 * @Version: V1.0 * @Autor: Carlos * @Date: 2020-05-22 16:09:01 * @LastEditors: Carlos * @LastEditTime: 2020-06-03 16:58:14 */ #include <stdio.h> #include <stdlib.h> #define keyType int typedef struct { //查找表中每一個數據元素的值 keyType key; //若是須要,還能夠添加其餘屬性 }ElemType; typedef struct{ //存放查找表中數據元素的數組 ElemType *elem; //記錄查找表中數據的總數量 int length; }SSTable; /** * @Description: 建立查找表 * @Param: SSTable **st 指向結構體指針的指針,即指針變量的指針,int length 建立的二叉樹的長度 * @Return: 無 * @Author: Carlos */ void Create(SSTable **st,int length){ (*st)=(SSTable*)malloc(sizeof(SSTable)); (*st)->length=length; (*st)->elem = (ElemType*)malloc((length+1)*sizeof(ElemType)); printf("輸入表中的數據元素:\n"); //根據查找表中數據元素的總長度,在存儲時,從數組下標爲 1 的空間開始存儲數據 for (int i=1; i<=length; i++) { scanf("%d",&((*st)->elem[i].key)); } } //折半查找算法 /** * @Description: 折半查找算法 * @Param: SSTable *ST 指向結構體的指針,keyType key 要插入的元素 * @Return: 成功的返回key在查找表中的位置,不然返回0 * @Author: Carlos */ int Search_Bin(SSTable *ST,keyType key){ //初始狀態 low 指針指向第一個關鍵字 int low=1; //high 指向最後一個關鍵字 int high=ST->length; int mid; while (low<=high) { //int 自己爲整形,因此,mid 每次爲取整的整數 mid=(low+high)/2; //若是 mid 指向的同要查找的相等,返回 mid 所指向的位置 if (ST->elem[mid].key==key) { return mid; } //若是mid指向的關鍵字較大,則更新 high 指針的位置 else if(ST->elem[mid].key>key) { high=mid-1; } //反之,則更新 low 指針的位置 else{ low=mid+1; } } return 0; } int main(int argc, const char * argv[]) { SSTable *st; Create(&st, 11); getchar(); printf("請輸入查找數據的關鍵字:\n"); int key; scanf("%d",&key); int location=Search_Bin(st, key); //若是返回值爲 0,則證實查找表中未查到 key 值, if (location==0) { printf("查找表中無該元素"); }else{ printf("數據在查找表中的位置爲:%d",location); } return 0; }
動態查找表中作查找操做時,若查找成功能夠對其進行刪除;若是查找失敗,即表中無該關鍵字,能夠將該關鍵字插入到表中。
動態查找表的表示方式有多種,本節介紹一種使用樹結構表示動態查找表的實現方法——二叉排序樹(又稱爲「二叉查找樹」)。指針
二叉排序樹要麼是空二叉樹,要麼具備以下特色:code
例如,圖 5 就是一個二叉排序樹:
圖5blog
二叉排序樹中查找某關鍵字時,查找過程相似於次優二叉樹,在二叉排序樹不爲空樹的前提下,首先將被查找值同樹的根結點進行比較,會有 3 種不一樣的結果:排序
/** * @Description: 二叉排序樹查找算法 * @Param: BiTree T KeyType key BiTree f BiTree *p * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p) { //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息 if (!T) { *p = f; return FALSE; } //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息 else if (key == T->data) { *p = T; return TRUE; } //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹 else if (key < T->data) { return SearchBST(T->lchild, key, T, p); } else { return SearchBST(T->rchild, key, T, p); } }
二叉排序樹自己是動態查找表的一種表示形式,有時會在查找過程當中插入或者刪除表中元素,當由於查找失敗而須要插入數據元素時,該數據元素的插入位置必定位於二叉排序樹的葉子結點,而且必定是查找失敗時訪問的最後一個結點的左孩子或者右孩子。
例如,在圖 1 的二叉排序樹中作查找關鍵字 1 的操做,當查找到關鍵字 3 所在的葉子結點時,判斷出表中沒有該關鍵字,此時關鍵字 1 的插入位置爲關鍵字 3 的左孩子。
因此,二叉排序樹表示動態查找表作插入操做,只須要稍微更改一下上面的代碼就能夠實現,具體實現代碼爲:遞歸
/** * @Description: 二叉排序樹查找算法 * @Param: BiTree T KeyType key BiTree f BiTree *p * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p) { //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息 if (!T) { *p = f; return FALSE; } //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息 else if (key == T->data) { *p = T; return TRUE; } //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹 else if (key < T->data) { return SearchBST(T->lchild, key, T, p); } else { return SearchBST(T->rchild, key, T, p); } } /** * @Description: 二叉樹插入函數 * @Param: BiTree *T 二叉樹結構體指針的指針 ElemType e 要插入的元素 * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int InsertBST(BiTree *T, ElemType e) { BiTree p = NULL; //若是查找不成功,需作插入操做 if (!SearchBST((*T), e, NULL, &p)) { //初始化插入結點 BiTree s = (BiTree)malloc(sizeof(BiTree)); s->data = e; s->lchild = s->rchild = NULL; //若是 p 爲NULL,說明該二叉排序樹爲空樹,此時插入的結點爲整棵樹的根結點 if (!p) { *T = s; } //若是 p 不爲 NULL,則 p 指向的爲查找失敗的最後一個葉子結點,只須要經過比較 p 和 e 的值肯定 s 究竟是 p 的左孩子仍是右孩子 else if (e < p->data) { p->lchild = s; } else { p->rchild = s; } return TRUE; } //若是查找成功,不須要作插入操做,插入失敗 return FALSE; }
經過使用二叉排序樹對動態查找表作查找和插入的操做,同時在中序遍歷二叉排序樹時,能夠獲得有關全部關鍵字的一個有序的序列。
例如,假設原二叉排序樹爲空樹,在對動態查找表 {3,5,7,2,1} 作查找以及插入操做時,能夠構建出一個含有表中全部關鍵字的二叉排序樹,過程如圖6 所示:
圖6
經過不斷的查找和插入操做,最終構建的二叉排序樹如圖 6(5) 所示。當使用中序遍歷算法遍歷二叉排序樹時,獲得的序列爲:1 2 3 5 7 ,爲有序序列。
一個無序序列能夠經過構建一棵二叉排序樹,從而變成一個有序序列。圖片
在查找過程當中,若是在使用二叉排序樹表示的動態查找表中刪除某個數據元素時,須要在成功刪除該結點的同時,依舊使這棵樹爲二叉排序樹。
假設要刪除的爲結點 p,則對於二叉排序樹來講,須要根據結點 p 所在不一樣的位置做不一樣的操做,有如下 3 種可能:
/* * @Description: 二叉查找樹 * @Version: V1.0 * @Autor: Carlos * @Date: 2020-06-02 15:50:31 * @LastEditors: Carlos * @LastEditTime: 2020-06-03 16:49:46 */ #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 #define ElemType int #define KeyType int /* 二叉排序樹的節點結構定義 */ typedef struct BiTNode { int data; struct BiTNode *lchild, *rchild; } BiTNode, *BiTree; /** * @Description: 二叉排序樹查找算法 * @Param: BiTree T KeyType key BiTree f BiTree *p * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int SearchBST(BiTree T, KeyType key, BiTree f, BiTree *p) { //若是 T 指針爲空,說明查找失敗,令 p 指針指向查找過程當中最後一個葉子結點,並返回查找失敗的信息 if (!T) { *p = f; return FALSE; } //若是相等,令 p 指針指向該關鍵字,並返回查找成功信息 else if (key == T->data) { *p = T; return TRUE; } //若是 key 值比 T 根結點的值小,則查找其左子樹;反之,查找其右子樹 else if (key < T->data) { return SearchBST(T->lchild, key, T, p); } else { return SearchBST(T->rchild, key, T, p); } } /** * @Description: 二叉樹插入函數 * @Param: BiTree *T 二叉樹結構體指針的指針 ElemType e 要插入的元素 * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int InsertBST(BiTree *T, ElemType e) { BiTree p = NULL; //若是查找不成功,需作插入操做 if (!SearchBST((*T), e, NULL, &p)) { //初始化插入結點 BiTree s = (BiTree)malloc(sizeof(BiTree)); s->data = e; s->lchild = s->rchild = NULL; //若是 p 爲NULL,說明該二叉排序樹爲空樹,此時插入的結點爲整棵樹的根結點 if (!p) { *T = s; } //若是 p 不爲 NULL,則 p 指向的爲查找失敗的最後一個葉子結點,只須要經過比較 p 和 e 的值肯定 s 究竟是 p 的左孩子仍是右孩子 else if (e < p->data) { p->lchild = s; } else { p->rchild = s; } return TRUE; } //若是查找成功,不須要作插入操做,插入失敗 return FALSE; } /** * @Description: 刪除節點的函數 * @Param: BiTree *p 指向結構體指針的指針 * @Return: 刪除成功 TRUE * @Author: Carlos */ int Delete(BiTree *p) { BiTree q, s; //狀況 1,結點 p 自己爲葉子結點,直接刪除便可 if (!(*p)->lchild && !(*p)->rchild) { *p = NULL; } //左子樹爲空,只需用結點 p 的右子樹根結點代替結點 p 便可; else if (!(*p)->lchild) { q = *p; *p = (*p)->rchild; free(q); q = NULL; } //右子樹爲空,只需用結點 p 的左子樹根結點代替結點 p 便可; else if (!(*p)->rchild) { q = *p; //這裏不是指針 *p 指向左子樹,而是將左子樹存儲的結點的地址賦值給指針變量 p *p = (*p)->lchild; free(q); q = NULL; } //左右子樹均不爲空,採用第 2 種方式 else { q = *p; s = (*p)->lchild; //遍歷,找到結點 p 的直接前驅 while (s->rchild) { //指向p節點左子樹最右邊節點的前一個。保留下來 q = s; s = s->rchild; } //直接改變結點 p 的值 (*p)->data = s->data; //判斷結點 p 的左子樹 s 是否有右子樹,分爲兩種狀況討論 若是有右子樹,s必定會指向右子樹的葉子節點。q 此時指向的是葉子節點的父節點。 q != *p兩者不等說明有右子樹 if (q != *p) { //如有,則在刪除直接前驅結點的同時,令前驅的左孩子結點改成 q 指向結點的孩子結點 q->rchild = s->lchild; } else //q == *p ==NULL 說明沒有右子樹 { //不然,直接將左子樹上移便可 q->lchild = s->lchild; } free(s); s = NULL; } return TRUE; } /** * @Description: 刪除二叉樹中的元素 * @Param: BiTree *T 指向二叉樹結構體的指針 int key 要刪除的元素 * @Return: 刪除成功 TRUE 刪除失敗 FALSE * @Author: Carlos */ int DeleteBST(BiTree *T, int key) { if (!(*T)) { //不存在關鍵字等於key的數據元素 return FALSE; } else { if (key == (*T)->data) { Delete(T); return TRUE; } else if (key < (*T)->data) { //使用遞歸的方式 return DeleteBST(&(*T)->lchild, key); } else { return DeleteBST(&(*T)->rchild, key); } } } /** * @Description: 中序遍歷輸出二叉樹 * @Param: BiTree t 結構體變量 * @Return: 無 * @Author: Carlos */ void order(BiTree t) { if (t == NULL) { return; } order(t->lchild); printf("%d ", t->data); order(t->rchild); } int main() { int i; int a[5] = {3, 4, 2, 5, 9}; BiTree T = NULL; for (i = 0; i < 5; i++) { InsertBST(&T, a[i]); } printf("中序遍歷二叉排序樹:\n"); order(T); printf("\n"); printf("刪除3後,中序遍歷二叉排序樹:\n"); DeleteBST(&T, 3); order(T); }
有任何問題,都可經過公告中的二維碼聯繫我