查找表是由同一类型的数据元素构成的集合。例如电话号码簿和字典均可以看做是一张查找表。
在查找表中只作查找操做,而不改动表中数据元素,称此类查找表为静态查找表;反之,在查找表中作查找操做的同时进行插入数据或者删除数据的操做,称此类表为动态查找表。算法
顺序查找的查找过程为:从表中的最后一个数据元素开始,逐个同记录的关键字作比较,若是匹配成功,则查找成功;反之,若是直到表中第一个关键字查找完也没有成功匹配,则查找失败
同时,在程序中初始化建立查找表时,因为是顺序存储,因此将全部的数据元素存储在数组中,可是把第一个位置留给了用户用于查找的关键字。例如,在顺序表{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); }
有任何问题,都可经过公告中的二维码联系我