【数据结构与算法】查找

一、静态查找表

只允许做查询和检索操作的查找表。

#define MAX 100 
// 顺序表
typedef struct
{
	int key;
}ElemType;

typedef struct
{
	ElemType elem[MAX];
	int length;
}SqList;

1. 顺序查找

查找表组织成线性表(数组 / 链表),从表的一端顺序找到表的另一端,元素顺序无特殊要求。

// 说明:返回0说明没找到,若找到,返回该元素的“下标”。
int search(SqList L, int x)
{
	int i;
	for (i = 0; i < L.length; i++)
		if (L.elem[i].key == x)
			return i + 1;
	return 0;
}

为提高查找速度,设置下标为0的元素作为哨兵项,数据元素从下标为1的元素开始存放。

// 说明:待查找的元素放在哨兵项处,从表尾开始比较。
int search(SqList L, int x)
{
	int k = L.length;
	L.elem[0].key = x;
	while (x != L.elem[k].key)
		k--;
	return k;
}

2. 二分查找

查找表组织成有序线性表(递增 / 递减),采用顺序存储结构,链表无法实现二分查找。为保持表的有序性,插入和删除都必须移动大量的数据元素。因此,二分查找适用于一经建立就很少改动,又经常需要查找的线性表。

// 说明:返回0说明没找到;若找到,返回该元素的“下标”。
int binaryS(SqList L, int x)
{
	int low = 0, high = L.length - 1, mid;
	while (low <= high)
	{
		mid = (low + high) / 2;	// 向下取整
		if (L.elem[mid].key == x)
			return mid + 1;
		if (L.elem[mid].key > x)
			high = mid - 1;
		else
			low = mid + 1;
	}
	return 0;
}

查找成功示例:查找91
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
查找失败示例:查找26
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3. 索引顺序表查找

将线性表分成若干块,每一块中的键值存储顺序是任意的,块与块之间按键值排序,即后一块中的所有记录的关键字的值均大于前一块中最大键值。
在这里插入图片描述
首先对索引表采用二分查找或顺序查找的方法查找,以确定待查记录所在的块,然后在相应的块中进行顺序查找。在线性表查找k,若其在线性表中存在,且位于第i块,则第i-1块的最大键值 < k ≤ 第i块的最大键值

二、动态查找表

除查询和检索操作外,还允许进行插入和删除操作的查找表。

1. 二叉排序树

二叉排序树定义:若它的左子树不空,则左子树上所有结点的值均小于根结点的值。若它的右子树不空,则右子树上所有结点的值均大于根结点的值。它的左、右子树也都分别是二叉排序树。
在这里插入图片描述
判断一棵二叉树是否为二叉排序树:中序遍历结果是否递增有序。

typedef struct Node
{
	int key;
	struct Node* lc, * rc;
}BiNode, * BiTree;

(1)查找

查找时间复杂度O(logn),最坏情况是单枝树,时间复杂度为O(n)

// 函数返回查找结果,没找到返回空指针。
BiTree Search(BiNode* T, int x)
{
	BiTree p = T;
	while (p != NULL)
	{
		if (x == p->key)
			return p;
		if (x < p->key)
			p = p->lc;
		else
			p = p->rc;
	}
	return p;
}

在这里插入图片描述

(2)插入

同一组数据,若输入的顺序不同,所建的二叉排序树不同。
在这里插入图片描述

按照二分查找的过程建立的二叉排序树为最佳二叉排序树,平均查找性能最高。
在这里插入图片描述

先在二叉排序树中查找,若查找成功,按二叉排序树定义,待插入数据已存在,不用插入。若查找不成功,新插入结点一定是作为叶子结点添加上去。建立一棵二叉排序树则是逐个插入结点的过程。

int Insert(BiTree& T, int x)
{
	BiTree q, p, s;
	// p:正在查看的节点
	p = T;
	// q:p的双亲节点
	q = NULL;
	while (p != NULL)
	{
		// 查找成功,退出
		if (x == p->key)
			return 0;
		q = p;
		if (x < p->key)
			p = p->lc;
		else
			p = p->rc;
	}
	// 查找失败,插入
	s = (BiTree)malloc(sizeof(BiNode));
	s->key = x;
	s->lc = NULL;
	s->rc = NULL;
	// 若原先的二叉树是空树
	if (q == NULL)
		T = s;
	else if (x < q->key)
		q->lc = s;
	else
		q->rc = s;
	return 1;
}

插入值为22的结点过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

(3)删除

从二叉排序树中删除一个结点后,仍要保持二叉排序树的特性。

  • 被删结点为叶结点,由于删去叶结点后不影响整棵树的特性,只需将被删结点的双亲结点相应指针域改为空指针。
  • 若被删结点只有右子树pr或只有左子树pl,只需用pr或pl替换被删结点。
    在这里插入图片描述
    在这里插入图片描述
  • 若被删结点既有左子树pl又有右子树pr。
    在这里插入图片描述

2. 平衡二叉树

我们希望所建的二叉排序树均为平衡二叉树。平衡二叉树定义:树中每个结点的左、右子树高度之差(平衡因子)的绝对值不大于1。
在这里插入图片描述
如果在一棵平衡二叉树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡,我们称调整平衡过程为平衡旋转。目标根结点为离新插结点最近的失去平衡的结点

(1)LL型

新插入的结点在目标根节点的左孩子的左子树上。具体做法:A的左孩子B右旋代替A成为新的根结点,A右旋成为B的右孩子,B的原右子树成为A的左孩子。
在这里插入图片描述

(2)RR型

新插入的结点在目标根节点的右孩子的右子树上。具体做法:A的右孩子B左旋代替A成为新的根结点,A左旋成为B的左孩子,B的原左子树成为A的右孩子。
在这里插入图片描述

(3)LR型

新插入的结点在目标根节点的左孩子的右子树上。具体做法:①以B的右孩子C为子树根,把A的左子树左旋,C的原左子树成为B的右孩子。②C右旋代替A成为新的根结点。
在这里插入图片描述
在这里插入图片描述

(4)RL型

新插入的结点在目标根节点的右孩子的左子树上。具体做法:①以B的左孩子C为子树根,把A的右子树右旋,C的原右子树成为B的左孩子。②C右旋代替A成为新的根结点。
在这里插入图片描述
在这里插入图片描述

3. B-树

m阶B-树满足下列特性:树中每个结点最多m棵子树。若根结点不是叶结点,则至少有2棵子树。除根结点之外所有非叶结点至少有m/2(向上取整)棵子树。所有叶子结点位于同一层上。所有的非叶结点包含下列信息数据:
在这里插入图片描述
一棵3阶的B-树(2-3树),每个非叶结点最多有3棵子树,最少有2棵子树。
在这里插入图片描述

#define MAX 10 
typedef struct BTNode
{
	int keynum;					// 关键字个数
	int key[MAX];				// 存放关键字K1, K2, ..., Kn 
	struct BTNode* ptr[MAX];	// 存放n棵子树的地址A0, A1, A2, …, An 
}BTNode, * BTree;

(1)查找

// 返回的结果
typedef struct
{
	BTNode* pt;
	int i;
	int tag;
}Result;

Result SearchBTree(BTree T, int k)
{
	BTNode* p = T, * q = NULL;
	int i = -1;
	while (p != NULL)
	{
		i = p->keynum;
		// 哨兵项
		p->key[0] = k;
		while (k < p->key[i])
		{
			i--;
		}
		// 找到
		if (k == p->key[i] && i > 0)
		{
			return { p, i, 1 };
		}
		// 没找到
		else
		{
			q = p;
			p = p->ptr[i];
		}
	}
	return { q, i, 0 };
}

查找值为53的结点过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)插入

首先在B-树上进行查找,确定插入位置。插入不是在叶结点上进行的,而是在最底层的某个非叶结点中添加一个关键字,若插入后该结点上关键字个数不超过m-1个,则直接插入即可,否则该结点上关键字个数达到m个,该结点的子树超过了m棵,需要进行分裂调整。

插入值为7的结点过程:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

(3)删除

所有删除操作最终都是要删除最底层非叶结点中关键字。

① 删除最底层非叶结点中关键字,若删除操作前,结点中关键字个数不小于m/2(向上取整),则直接删去,否则调整合并。

  • 与“兄弟”借
    在这里插入图片描述
    在这里插入图片描述
  • 与“双亲”借
    在这里插入图片描述
    在这里插入图片描述
    ② 删除非最底层非叶结点中的关键字Ki,则以指针Ai所指子树上的最小关键字Y代替Ki,然后删Y。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

4. B+树

m阶B+树的特点:有n棵子树的结点中含有n个关键字。所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点按照关键字从小到大的顺序链接。所有的非叶子结点可以看成是索引部分,结点中仅含有其子树根结点中最大关键字。

(1)查找

在进行缩小范围的查找时,不管成功与否,都必须查到叶子结点才能结束。若在结点内查找时,给定值≤Ki,则应继续在Ai所指子树中进行查找。

  • 缩小范围地查找
    在这里插入图片描述
  • 顺序查找
    在这里插入图片描述

(2)插入

插入值为20的结点过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:插入值为100的结点过程
在这里插入图片描述在这里插入图片描述

(3)删除

删除值为51的结点过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
删除值为59的结点的过程:
在这里插入图片描述
在这里插入图片描述

三、哈希表

我们希望不进行关键字的比较,直接根据关键字的值确定其存储位置。哈希函数:关键字的值和存储位置的对应关系的函数。在一般情况下,很容易产生“冲突”现象:key1≠key2,而h(key1) = h(key2)。

1. 构造哈希函数的方法

(1)直接定址法
哈希函数为关键字的线性函数。此法仅适合于地址集合大小等于关键字集合大小的情况。

(2)数字分析法
预先估计出全体关键字的每一位上各种数字出现的频度,并从中提取分布均匀的若干位作为地址。
在这里插入图片描述
(3)平方取中法
关键字中的每一位都有某些数字重复出现频度很高的现象时,以关键字的平方值的中间几位作为存储地址。
在这里插入图片描述
(4)折叠法
此方法适合于关键字的数字位数相对于地址位数特别多的情况。将关键字自左到右分成位数相等的几部分,最后一部分位数可以短些,然后将这几部分叠加求和作为哈希地址。加法分为进位相加、不进位相加。叠加方法分为移位法(将各部分的最后一位对齐相加)、间界叠加法(从一端向另一端沿各部分分界来回折叠后,最后一位对齐相加)。
在这里插入图片描述
(5)除留余数法
设定哈希函数:h(key) = key MOD p,p应为不大于哈希表表长m的素数或是不含20以下的质因子。

(6)随机数法
设定哈希函数:h(key) = Random(key),此方法用于对长度不等的关键字构造哈希函数。

2. 处理冲突的方法

为产生冲突的地址寻找下一个哈希地址。
(1)开放定址法
设定一个地址探测序列h1, h2, …, hk(1≤k≤m-1),按照这个探测序列顺序为发生冲突的数据key寻找存放位置。其中:hi = (h(key) + di) MOD m,i=1, 2, …, k
① 线性探测再散列:di = i
二次聚集:哈希地址不同的元素争夺同一个哈希地址,比如:23与68
在这里插入图片描述
② 平方(二次)探测再散列:di = 1^2, -1^2, 2^2, -2^2, 3^2, -3^2 , …
在这里插入图片描述
③ 随机探测再散列:di是一组伪随机数列
(2)再哈希法
为产生冲突的地址再次通过另一个哈希函数求得地址。
(3)链地址法
将所有哈希地址相同的数据都链接在同一链表中。
在这里插入图片描述
(4)建立一个公共溢出区
将发生冲突的数据元素顺序存放于一个公共的溢出区。

3. 哈希表查找分析

决定哈希表查找的ASL的因素:选用的哈希函数;选用的处理冲突的方法;哈希表饱和的程度,装载因子α=n/m值的大小(n—数据数,m—表的长度)。一般情况下,认为选用的哈希函数是“均匀” 的,则在讨论ASL时不考虑它。哈希表的ASL是处理冲突方法和装载因子的函数。
在这里插入图片描述

4.哈希表的删除操作

以开放定址法为例:加删除标志。插入操作遇到有删除标记的位置,当成空闲的空间,直接放入插入的数据;查找操作遇到加删除标记的位置,当成非空闲空间,但不做比较操作。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值