数据结构——查找
创始人
2025-06-01 11:08:45
0

查找概论

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

顺序表查找

顺序表查找算法 

/* 顺序查找,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;
}

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

散列表查找的性能分析

 

相关内容

热门资讯

朋友喜欢编故事,编的一波三折,... 朋友喜欢编故事,编的一波三折,装无辜扮可怜,别人还深信不疑,这样的叫什么?很明显,你这个朋友平时虚情...
实在找不到女朋友,有点想用各种... 实在找不到女朋友,有点想用各种方法骗女孩子骗到我身边谈恋爱,比如:编假故事 瞒得了一时,瞒不了一...
各年龄段的孩子适合看什么绘本 各年龄段的孩子适合看什么绘本  每个年龄段适合孩子看的绘本  0~1岁宝宝  选书要点:这个年龄...
一不小心爱上你还有续集么 一不小心爱上你还有续集么我感觉结局不大好 如果 能像 一起来看流星雨 一样有个 续集 这才完美但是...
《开学了》小品剧本台词 《开学了》小品剧本台词在日常生活和工作中,需要使用台词的情况越来越多,台词的写作与安排是剧作技巧的重...
寒门贵子最后两句文言文 寒门贵子最后两句文言文有志者事竟成,破釜沉舟,百二秦关终属楚;苦心人天不负,卧薪尝胆,三千越甲可吞吴...
厦门的大学有哪些 厦门的大学有...   漆黑的白鹭岛灯光垂落在白城沙滩做成的屏风上,放映着印着椰树和海浪的电影,带起芙蓉湖里凤凰花的颜色...
一年级看图写话小猴子和小熊过河 一年级看图写话小猴子和小熊过河一年级看图写话小猴子和小熊过河星期六,小猫给小猴打电话说:“邀请上你的...
深圳创业补贴2020,深圳创业...   【最高补贴500万元!城市金融助力大学生创业创新!】关注即将迎来毕业季。为推动杭州市大学生创业创...
保障参展人员和物资便利通关 转自:云南日报昆明海关出台16条措施保障参展人员和物资便利通关6月5日,昆明海关发布《第9届中国—南...
属鼠的人2021年全年运势 属...   00-1010如果你想咨询我,请通过以下方式搜索:      #星座女王傅子琪,      #塔...
二手衣服回收,闲置物品收购公司...   最近身边很多朋友不是辞职就是创业失败,教育培训行业最近一年的日子也不好过。每个人都失去了工作和事...
上证指数股票,持有沪市股票可以...   投资增长;投资策略-中长期;适合用户——有一定投资经验,抗风险能力强的用户。      内容仅供...
废品回收创业案例,先有钱后创业...   这个月入六千买废品的老头。在建立他的数据库的过程中,他穿着干净整洁的工作服出现在社区里。    ...
上海应届生人才补贴政策 优秀人...   市民政局表示,2022年元旦、春节帮扶困难群众送温暖计划已经确定,预计覆盖107.58万人。请参...
投资机构如何评估创业团队,什么...   每个公司的业务不一样,必须制度化的工作内容也会不一样,但有些工作内容是共通的。比如每天上下班打卡...
小学周记格式怎么写 小学周记格式怎么写周记和日记可以说差不多,只不过是日记日记天天记,周记却是一星期写一次,就跟写日记一...
己欲立而立人,己欲达而达人的大... 己欲立而立人,己欲达而达人的大概意思"己欲立而立人,己欲达而达人",意思是通过正己而正他人,自己通达...
阿里巴巴创业团队案例 阿里巴巴...   1、【在线教育行业】Timing App的Serverless实践案例      在用户和流量...
吴川海滨公路,吴川市海滨街道海...   近日,具有文化特色的吴川滨海小镇奠基仪式在王村港镇“梦幻海岸摄影基地”景区举行。目前,这个小镇旅...