数据结构Chap 7/查找

一、基本概念

1、常用概念

(1).查找表(查找结构):用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成。

(2).关键字:数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,其查找结果应该是唯一的【id,学号,qq号等】

(3).对查找表的常见操作

a.查找符合条件的数据元素【无改动->静态查找表->仅关注查找速度】

b.插入、删除某个数据元素【有改动->动态查找表->还要关注增删的难度】

(4).查找算法的效率评价->平均查找长度ASL->通常考虑成功、失败两种情况下的ASL

二、查找办法

1.顺序查找

(1)两种实现办法【常用线性表、动态数组】:

typedef struct
{
	int elem[3];		//动态数组
	int TableLen;		//数组长度
}SSTable;
/*1.不带哨兵的顺序查找【从前往后即可】*/
int Search_Seq(SSTable ST, int key)		//key为需要寻找的那个值
{
	int i;
	for (i = 0; i < ST.TableLen && ST.elem[i] != key; ++i)
		;		//注意这里有个分号,如果下标还小,值又不是想要的,就什么也不干,只是i++
		//两种跳出循环,跳入下一行:一是i超过数组长度,二是某一元素值恰好为key
		return i==ST.TableLen? -1:i;	//判断是第一类跳出还是第二类
}
/*2.带哨兵的顺序查找【从后往前】*/
int Search_Seq_with_guard(SSTable ST, int key)
{
	ST.elem[0] = key;					//数组0号位置存哨兵,具体数据从1号开始存
	int i;
	for (i = ST.TableLen; ST.elem[i] != key; --i)
		;
		return i;						//查找成功时,返回数组下标,失败时返回哨兵下标0
}
int main()
{
	SSTable T = { T.elem[3] = {0,2}, T.TableLen = 5 };
	int target;
	int result;
	//for (int i = 0; i < T.TableLen; ++i)
	//	cin>>T.elem[i];
	cout << "which num do you want to find?" << endl<<"target= ";
	cin >> target;
	result = Search_Seq(T, target);
	cout<<"The num you find located in :"<<result;
	return 0;
}

注:哨兵优点:无需判断是否越界,效率更高。【尚未实现,报错】

查找成功的ASL:\frac{\left ( 1+2+...+n \right )}{n}=\frac{n+1}{2}=O\left ( n \right )

查找失败的ASL:O(n+1)

(2)顺序查找的优化【使用查找判定树】:

a.【顺序表有序】

 

 b.【顺序表被差概率不等】

 

如果很容易查找成功且概率不等->用第二种办法

如果很容易失败->用第一种办法

 2.二分查找/折半查找

(1)适用范围:

        有序的顺序表,而无法用链表->需要有随机访问的特性,而链表没有

(2)实现方法:

typedef struct
{
	int *elem;			//动态数组
	int TableLen;		//数组长度
}SSTable;
/*折半查找*/
int Binary_Search(SSTable L, int key)
{
	int low = 0, high = L.TableLen - 1, mid;
	while (low <= high)
	{
		mid = (low + high) / 2;
		if (key== L.elem[mid])
			return mid;
		else if (key<L.elem[mid])
			high = mid - 1;
		else if (key>L.elem[mid])
			low = mid + 1;
	}
	return -1;			//查找失败,返回-1
}

         如果是向下取整,则右子树比左子树的结点相等or多一个

        折半查找的判定树中,只有最下一层是不满的,因此,元素个数为n时树高h=log2(n+1)取上界

        所以时间复杂度:O(log_{2}n),一般情况下比顺序查找更优秀,但是也不能绝对地说,因为特殊情况下顺序查找更快,如 7 ,8 , 9, 10,11 中找“ 7 ”

3.分块查找

(1)定义:

        也称索引顺序查找,算法过程如下:
        a.在索引表中确定待查记录所属分块(可顺序,可折半)

        b.折半查找时,需要在low的块内顺序查找

(2)查找效率分析(ASL)

        运用查找的总次数/元素个数

4.二叉排序树的查找

(1)定义:

        左子树<根节点<右子树  (的值)【左小右大】

(2)实现方法:

typedef struct BSTNode
{
	int key;			//结点值
	struct BSTNode *lchild, * rchild;
}BSTNdode,*BSTree;
/*1.二叉排序树查找值为key的结点*/
BSTNode* BST_Search(BSTree T, int key)
{
	while (T != NULL && key != T->key)
	{
		if (key < T->key)
			T = T->lchild;
		else T = T->rchild;
	}
	return T;
}
/*2.二叉排序树查找值为key的结点【递归式】*/
BSTNode* BST_Search(BSTree T, int key)
{
	if (T == NULL)
		return NULL;
	if (key == T->key)
		return T;
	else if (key < T->key)
		return BST_Search(T->lchild, key);
	else if (key > T->key)
		return BST_Search(T->rchild, key);
}

第一种的空间复杂度O(1),第二种是O(h),与高度有关。

(3)二叉排序树的插入操作

typedef struct BSTNode
{
	int key;			//结点值
	struct BSTNode *lchild, * rchild;
}BSTNdode,*BSTree;
int BST_Insert(BSTree& T, int k)
{
	if (T == NULL)
	{
		T = new BSTNode;
		T->key = k;
		T->lchild = T->rchild = NULL;
		return 1;
	}
	else if (k == T->key)
		return 0;
	else if (k < T->key)
		return BST_Insert(T->lchild, k);
	else if (k > T->key)
		return BST_Insert(T->rchild, k);
}

(4)二叉排序树的构造

void Create_BST(BSTree& T, int str[], int n)
{
	T = NULL;                    //一开始是空树
	for (int i = 0; i < n; i++)
		BST_Insert(T, str[i]);
}

        不同的关键字序列可能得到不同的二叉排序树,也可能相同。【先左后右和先右后左相同】

(5)二叉排序树的删除:

        a.叶子结点直接删除

        b.只有左子树/右子树,让其来补位即可

        c.删除一个既有左子树又有右子树的根节点,需要用直接前驱/直接后继来补位。

【二叉排序树经过中序遍历,一定可以得到递增序列,若删除叶子结点:可以用前驱/后继来补位】【找前驱:即最右下结点,后用左子树代替该位置;找后继:即最左下结点,后用右子树代替该位置】

 (6)查找效率分析

查找长度——对比关键字的次数,反映了操作的时间复杂度

        所以要左右子树深度相差至多为1是最好情况,时间复杂度最低。【即平衡二叉树

5.平衡二叉树(AVL)

(1)定义:

        每个结点的   |平衡因子|<=1二叉排序树。

        平衡因子:左子树高右子树高的值。

 (2)平衡二叉树的插入

举例:【注意代码,调整时自下而上】

 (3)查找效率分析

 (4)平衡二叉树的删除

 6.红黑树(RBT)

(1)创建红黑树的原因:

        平衡二叉树插入删除时很容易破坏“平衡特性”,需要频繁调整树的形态。而红黑树很多时候不会破坏红黑特性,无需调整/可以在常数级时间内调整。

所以:

平衡二叉树:适用于以查为主,很少插入删除的场景。

红黑树:适用于频繁插入、删除的场景,实用性更强。

(2)定义:

除了是二叉排序树以外,还需要:【左根右,根叶黑,不红红,黑路同】

 

思考:要让内部结点少,肯定全为黑最少,因为带红肯定可以,但是这样就多了。并且此时必须全满,比如bh=2,只有左结点的话,就不满足“黑路同”的特性了。

性质1:【最长:红黑相间;最短:全是黑结点】

性质2:最少的话,全是黑结点!【注意黑高是不算本身结点以后,带上“外部结点”时,下面的层数,但是内部结点是包括根节点的】 

 (3)红黑树的插入

7.B树 (Balance Tree)

(1)定义【逐字读】

注:1.根结点是唯一可以只有一个关键字的,但是也可以多个关键字,但是不能超过m-1.

2.B树上强制平衡,所以所有子树的高度必须相同。

(2)B树的插入

a.首先确定关键词数目,阶数为m的B树,除了根节点的结点,其关键字数目n满足:

        \left \lceil m/2 \right \rceil-1\leqslant n\leqslant m-1

如5阶B树的结点关键词个数:  2\leqslant n\leqslant 4

b.先插入,如果某结点满了,就采用“中间起,两头分”的办法。\left \lceil m/2 \right \rceil变为父节点。

(3)B树的删除 

a.删除根节点找前驱或者后继结点来补位。

b.如果删除的是叶子结点,则看兄弟够不够借【挪过来后会不会导致兄弟的关键字也不够】

         如果不会导致这意外,则让后继及后继的后继【前驱及其前驱】换位。

         如果会,则说明此时可以将兄弟俩带着父节点一起合并。

 (4)与B+树的区别

8.散列查找

(1)哈希排列【处理同义词的冲突】

        散列法,又称为hash法或者关键字地址计算法。时间复杂度为0(理想情况下),是一种key-value的存储方法。核心就是由hash函数决定关键字值和散列地址之间的关系,通过这种关系来组织存储并进行查找等操作。

散列法面临的问题:会发生地址冲突。
(1)如何恰当的构造hash函数,使得结点分布均匀,尽量少的减少冲突。
(2)冲突无可避免,怎样处理冲突?

 装填因子a=表中记录数/散列表长度【直接影响查找效率】【装得越多,a越大,空间利用率越大但是查找效率越低】

(2)散列/哈希函数

 (3)处理冲突的办法【让数据分布更合理,不冲突】

 注意哈希函数是除以数据表的表长(下例为13),而开放定值法师除以存储表的表长(下例为16)。

 线性探测法的缺点:容易造成同义词、非同义词的“聚集/堆积”现象,严重影响查找效率。【原因:冲突后再探测一定是放在某个连续的位置】

采用二次探测法/平方探测法:虽然都是开放定址法,但是当选择的增量序列(di=0,1,-1,4,-4,9,-9...)不同时,其位置有不同之处。缓解了堆积,提高了效率!

表长要求:散列表长度m必须是一个可以表示成4j+3的素数,才能探测到所有位置。(数论知识)

(4)查找操作

        不仅查找同义词,也要查找非同义词。遇到数值为空的再停止【注意不是逻辑为空!】

 缺点:线性探测法很容易造成

(5)删除操作

  不能简单置空,而是做一个删除标记,进行逻辑删除,否则会影响到后续数据的查找。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值