数据结构——查找
创始人
2025-06-01 09:16:08

查找概论

        查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素。

顺序表查找

顺序表查找算法 

/* 顺序查找,a为数组,n为要查找的数组个数,key为要查找的关键字 */
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;
}

顺序表查找优化

/* 有哨兵顺序查找 */
int Sequential_Search2(int *a,int n,int key)
{int i;a[0]=key;			/* 设置a[0]为关键字值,我们称之为“哨兵”*/i=n;				/* 循环从数组尾部开始 */while(a[i]!=key){i--;}return i;			/* 返回0则说明查找失败 */
}

有序表查找 

折半查找

/* 折半查找 */
int Binary_Search(int *a,int n,int key)
{int low,high,mid;low=1;					/* 定义最低下标为记录首位 */high=n;					/* 定义最高下标为记录末位 */while(low<=high){mid=(low+high)/2;	/* 折半 */if (keya[mid])/* 若查找值比中值大 */low=mid+1;		/* 最低下标调整到中位下标大一位 */else{return mid;		/* 若相等则说明mid即为查找到的位置 */}}return 0;
}

插值查找

/* 插值查找 */
int Interpolation_Search(int *a,int n,int key)
{int low,high,mid;low=1;	/* 定义最低下标为记录首位 */high=n;	/* 定义最高下标为记录末位 */while(low<=high){mid=low+ (high-low)*(key-a[low])/(a[high]-a[low]); /* 插值 */if (keya[mid])/* 若查找值比插值大 */low=mid+1;		/* 最低下标调整到插值下标大一位 */elsereturn mid;		/* 若相等则说明mid即为查找到的位置 */}return 0;
}

斐波那契查找

 

int Fibonacci_Search(int *a,int n,int key) /* 斐波那契查找 */
{int low,high,mid,i,k;low=1;						/* 定义最低下标为记录首位 */high=n;						/* 定义最高下标为记录末位 */k=0;while(n>F[k]-1)				/* 计算n位于斐波那契数列的位置 */k++;for (i=n;ia[mid])	/* 若查找记录大于当前分隔记录 */{low=mid+1;			/* 最低下标调整到分隔下标mid+1处 */k=k-2;				/* 斐波那契数列下标减两位 */}else{if (mid<=n)return mid;		/* 若相等则说明mid即为查找到的位置 */else return n;		/* 若mid>n说明是补全数值,返回n */}}return 0;
}

线性索引查找

稠密索引

        稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项,索引项按关键码有序排列。

分块索引 

         分块有序,是把数据集的记录分成了若干块,并且这些块之间满足一下两个条件:

        块内无序,即每一块内的记录不要求有序。 

倒排索引

二叉排序树(左小右大)

如图是一棵二叉排序树的例子。

二叉排序树的查找操作

/* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode					/* 结点结构 */
{int data;							/* 结点数据 */struct BiTNode *lchild, *rchild;	/* 左右孩子指针 */
} BiTNode, *BiTree;
Status SearchBST(BiTree T, int key, BiTree f, BiTree *p) 
{ /* 递归查找二叉排序树T中是否存在key, */ if (!T)		/* 若查找不成功,指针p指向查找路径上访问的最后一个结点并返回FALSE */{ *p = f;  return FALSE; }else if (key==T->data) /* 若查找成功,则指针p指向该数据元素结点,并返回TRUE */{ *p = T;  return TRUE; } else if (keydata) return SearchBST(T->lchild, key, T, p);  	/* 在左子树中继续查找 */else  return SearchBST(T->rchild, key, T, p);  	/* 在右子树中继续查找 */
}

        二叉树f指向T的双亲,当T指向根结点时,f的初值就为NULL,它在递归时有用,最后的参数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;						/*  插入s为新的根结点 */else if (keydata) p->lchild = s;				/*  插入s为左孩子 */else p->rchild = s;  			/*  插入s为右孩子 */return TRUE;} else return FALSE;  					/*  树中已有关键字相同的结点,不再插入 */
}

二叉排序树的删除操作

①若被删除结点z是叶结点,则直接删除,不会破坏二叉排序树的特性

②若结点z只有一棵左子树或右子树,则让z的子树成为z父结点的子树,替代z的位置

③若结点z有左、右两棵子树,则令z的直接后继(或直接前驱)替代z,然后从二叉排序树中删去这个直接后继(或直接前驱),这样就转换成了第一或第二种情况。

Status Delete(BiTree *p)
{/* 从二叉排序树中删除结点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; /* s指向被删结点直接前驱(将被删结点前驱的值取代被删结点的值) */if(q!=*p)q->rchild=s->lchild;/*  重接q的右子树 */ elseq->lchild=s->lchild;/*  重接q的左子树 */free(s);}return TRUE;
}Status DeleteBST(BiTree *T,int key)
{/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据结点 */if(!*T) 					/* 不存在关键字等于key的数据元素 */ return FALSE;else{if (key==(*T)->data) 	/* 找到关键字等于key的数据元素 */ return Delete(T);else if (key<(*T)->data)return DeleteBST(&(*T)->lchild,key);elsereturn DeleteBST(&(*T)->rchild,key);}
}

平衡二叉树

        平衡二叉树是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1。我们将二叉树上结点的左子树高度减去右子树高度的值称为平衡因子BF,平衡二叉树上所有结点的平衡因子只可能是-1、0和1。 

平衡二叉树的实现原理

平衡二叉树的实现算法 

/* 二叉树的二叉链表结点结构定义 */
typedef  struct BiTNode					/* 结点结构 */
{int data;							/* 结点数据 */int bf; 							/*  结点的平衡因子 */ struct BiTNode *lchild, *rchild;	/* 左右孩子指针 */
} BiTNode, *BiTree;

右旋操作如下 

/* 对以p为根的二叉排序树作右旋处理, */
/* 处理之后p指向新的树根结点,即旋转处理之前的左子树的根结点 */
void R_Rotate(BiTree *P)
{ BiTree L;L=(*P)->lchild; 		/*  L指向P的左子树根结点 */ (*P)->lchild=L->rchild; /*  L的右子树挂接为P的左子树 */ L->rchild=(*P);*P=L; 					/*  P指向新的根结点 */ 
}

左旋操作代码如下

/* 对以P为根的二叉排序树作左旋处理, */
/* 处理之后P指向新的树根结点,即旋转处理之前的右子树的根结点0  */
void L_Rotate(BiTree *P)
{ BiTree R;R=(*P)->rchild; 		/*  R指向P的右子树根结点 */ (*P)->rchild=R->lchild; /* R的左子树挂接为P的右子树 */ R->lchild=(*P);*P=R; 					/*  P指向新的根结点 */ 
}

        左平衡旋转处理的函数代码,该函数被调用时,已经确认当前子树是不平衡状态,且左子树的高度大于右子树的高度,即T的根节点应该是平衡因子BF的值大于1的数。

#define LH +1 /*  左高 */ 
#define EH 0  /*  等高 */ 
#define RH -1 /*  右高 */ void LeftBalance(BiTree *T)  //T为需要调整平衡性的子树
{ BiTree L,Lr;L=(*T)->lchild; 					/* L指向T的左子树根结点 */ switch(L->bf)		/* 检查T的左子树的平衡度,并作相应平衡处理 */ { case LH:     	/* 新结点插入在T的左孩子的左子树上,要作单右旋处理 */ (*T)->bf=L->bf=EH;R_Rotate(T);break;case RH:     	/* 新结点插入在T的左孩子的右子树上,要作双旋处理 */ 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作右旋平衡处理 */ }
}

右平衡旋转处理的函数代码如下

/*  对以指针T所指结点为根的二叉树作右平衡旋转处理, */ 
/*  本算法结束时,指针T指向新的根结点 */ 
void RightBalance(BiTree *T)
{ BiTree R,Rl;R=(*T)->rchild; 					/* R指向T的右子树根结点 */ switch(R->bf){ /* 检查T的右子树的平衡度,并作相应平衡处理 */ case RH: /* 新结点插入在T的右孩子的右子树上,要作单左旋处理 */ (*T)->bf=R->bf=EH;L_Rotate(T);break;case LH: /* 新结点插入在T的右孩子的左子树上,要作双旋处理 */ Rl=R->lchild; 			/* Rl指向T的右孩子的左子树根 */ switch(Rl->bf)			/* 修改T及其右孩子的平衡因子 */ { 						case RH: (*T)->bf=LH;R->bf=EH;break;case EH: (*T)->bf=R->bf=EH;break;case LH: (*T)->bf=EH;R->bf=RH;break;}Rl->bf=EH;R_Rotate(&(*T)->rchild); 	/* 对T的右子树作右旋平衡处理 */ L_Rotate(T); 				/* 对T作左旋平衡处理 */ }
}
Status InsertAVL(BiTree *T,int e,Status *taller)
{  if(!*T)						/* 插入新结点,树“长高”,置taller为TRUE */ { *T=(BiTree)malloc(sizeof(BiTNode));(*T)->data=e; (*T)->lchild=(*T)->rchild=NULL; (*T)->bf=EH;*taller=TRUE;}else{if (e==(*T)->data)		/* 树中已存在和e有相同关键字的结点则不再插入 */{  *taller=FALSE; return FALSE;}if (e<(*T)->data)		/* 应继续在T的左子树中进行搜索 */{  if(!InsertAVL(&(*T)->lchild,e,taller)) /*  未插入 */ return FALSE;if(*taller) 		/* 已插入到T的左子树中且左子树“长高” */ {switch((*T)->bf)/* 检查T的平衡度 */ {case LH: 	/* 原本左子树比右子树高,需要作左平衡处理 */ LeftBalance(T);	*taller=FALSE; break;case EH: 	/* 原本左、右子树等高,现因左子树增高而使树增高 */ (*T)->bf=LH; *taller=TRUE; break;case RH: 	/* 原本右子树比左子树高,现左、右子树等高 */  (*T)->bf=EH; *taller=FALSE; break;}}}else					/*  应继续在T的右子树中进行搜索 */ { if(!InsertAVL(&(*T)->rchild,e,taller)) /* 未插入 */ return FALSE;if(*taller) 		/* 已插入到T的右子树且右子树“长高” */ {switch((*T)->bf)/* 检查T的平衡度 */ {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;
}

多路查找树(B树)

        一旦涉及外部存储设备,时间复杂度的计算就会发生变化,访问集合元素的时间已经不仅仅是寻找该元素所需比较次数的函数,必须考虑对硬盘等外部存储设备的访问时间以及将会对该设备做出多少次单独访问。

        多路查找树,其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。由于它是查找树,所有元素之间存在某种特定的排序关系。 

2-3树 

2-3树的插入实现

2-3树的插入可以分为以下三种情况:

(1) 对于空树,插入一个2结点即可

(2)插入结点到一个2结点的叶子上。将该结点变成一个3结点即可。

(3)要往3结点中插入一个新元素,因为3结点本身已经是2-3树的结点最大容量,因此就需要将其拆分,且将树中两元素或插入元素的三者中选择其一向上移动一层。

2-3树的删除实现

2-3-4树

        2-3-4树其实就是2-3树的概念扩展,包括了4结点的使用。一个4结点包含小中大3个元素和4个孩子(或没有孩子)。如果某个4结点有孩子的话,左子树包含小于元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素。

B树

B+树

散列表查找(哈希表)概述

散列函数的构造方法

什么才算是好的散列函数呢?

1.计算简单

直接定址法 

数字分析法

平方取中法

折叠法

 除留余数法

随机数法

 处理散列冲突的方法

开放定址法

 

再散列函数法

 链地址法

公共溢出区法

散列表查找的实现

散列表查找的算法实现

#define SUCCESS 1
#define UNSUCCESS 0
#define HASHSIZE 12 	/* 定义散列表长为数组的长度 */
#define NULLKEY -32768 typedef struct
{int *elem; 			/* 数据元素存储基址,动态分配数组 */int count; 			/*  当前数据元素个数 */
}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;ielem[i]=NULLKEY; return OK;
}/* 散列函数 */
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; 				/* 直到有空位后插入关键字 */
}/* 散列表查找关键字 */
Status SearchHash(HashTable H,int key,int *addr)
{*addr = Hash(key);  									/* 求散列地址 */while(H.elem[*addr] != key) 							/* 如果不为空,则冲突 */{*addr = (*addr+1) % m; 								/* 开放定址法的线性探测 */if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */return UNSUCCESS;								/* 则说明关键字不存在 */}return SUCCESS;
}

        查找的代码与插入的代码非常类似,只需做一个不存在关键字的判断而已。

散列表查找的性能分析

 

相关内容

热门资讯

【实验报告】实验一 图像的... 实验目的熟悉Matlab图像运算的基础——矩阵运算;熟悉图像矩阵的显示方法࿰...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
大模型落地比趋势更重要,NLP... 全球很多人都开始相信,以ChatGPT为代表的大模型,将带来一场NLP领...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
kuernetes 资源对象分... 文章目录1. pod 状态1.1 容器启动错误类型1.2 ImagePullBackOff 错误1....
STM32实战项目-数码管 程序实现功能: 1、上电后,数码管间隔50ms计数; 2、...
TM1638和TM1639差异... TM1638和TM1639差异说明 ✨本文不涉及具体的单片机代码驱动内容,值针对芯...
Qt+MySql开发笔记:Qt... 若该文为原创文章,转载请注明原文出处 本文章博客地址:https://h...
Java内存模型中的happe... 第29讲 | Java内存模型中的happen-before是什么? Java 语言...
《扬帆优配》算力概念股大爆发,... 3月22日,9股封单金额超亿元,工业富联、鸿博股份、鹏鼎控股分别为3.0...
CF1763D Valid B... CF1763D Valid Bitonic Permutations 题目大意 拱形排列࿰...
SQL语法 DDL、DML、D... 文章目录1 SQL通用语法2 SQL分类3 DDL 数据定义语言3.1 数据库操作3.2 表操作3....
文心一言 VS ChatGPT... 3月16号,百度正式发布了『文心一言』,这是国内公司第一次发布类Chat...
CentOS8提高篇5:磁盘分...        首先需要在虚拟机中模拟添加一块新的硬盘设备,然后进行分区、格式化、挂载等...
Linux防火墙——SNAT、... 目录 NAT 一、SNAT策略及作用 1、概述 SNAT应用环境 SNAT原理 SNAT转换前提条...
部署+使用集群的算力跑CPU密... 我先在开头做一个总结,表达我最终要做的事情和最终环境是如何的,然后我会一...
Uploadifive 批量文... Uploadifive 批量文件上传_uploadifive 多个上传按钮_asing1elife的...
C++入门语法基础 文章目录:1. 什么是C++2. 命名空间2.1 域的概念2.2 命名...
2023年全国DAMA-CDG... DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义...
php实现助记词转TRX,ET... TRX助记词转地址网上都是Java,js或其他语言开发的示例,一个简单的...
【分割数据集操作集锦】毕设记录 1. 按要求将CSV文件转成json文件 有时候一些网络模型的源码会有data.json这样的文件里...
Postman接口测试之断言 如果你看文字部分还是不太理解的话,可以看看这个视频,详细介绍postma...
前端学习第三阶段-第4章 jQ... 4-1 jQuery介绍及常用API导读 01-jQuery入门导读 02-JavaScri...
4、linux初级——Linu... 目录 一、用CRT连接开发板 1、安装CRT调试工具 2、连接开发板 3、开机后ctrl+c...
Urban Radiance ... Urban Radiance Fields:城市辐射场 摘要:这项工作的目标是根据扫描...
天干地支(Java) 题目描述 古代中国使用天干地支来记录当前的年份。 天干一共有十个,分别为:...
SpringBoot雪花ID长... Long类型精度丢失 最近项目中使用雪花ID作为主键,雪花ID是19位Long类型数...
对JSP文件的理解 JSP是java程序。(JSP本质还是一个Servlet) JSP是&#...
【03173】2021年4月高... 一、单向填空题1、大量应用软件开发工具,开始于A、20世纪70年代B、20世纪 80年...
LeetCode5.最长回文子... 目录题目链接题目分析解题思路暴力中心向两边拓展搜索 题目链接 链接 题目分析 简单来说࿰...