数据结构——查找

查找:根据给定的某个值,在表中确定一个关键字等于给定值的记录或数据元素, 若表中存在这样的记录,则称查找成功,查找结果为该记录在查找表中的位置; 否则称为查找失败,查找结果为 0 或 NULL。

评价查找:ASL(Average Search Length) :为确定记录在表中的位置,需要与给定值进行比较的关键字的个数的期望值。\sum_{i=1}^{n}{P_{i}C_{i}},Pi为查找第 i 个记录的概率,Ci为查找第 i 个记录所需的比较次数。

记录:由若干数据项构成的数据元素。

关键字:能标识一个数据元素(或记录)的数据项。

主关键字:能唯一地标识一个记录的关键字。

次关键字:用以识别若干记录的关键字。

一、静态查找表

在指定的表中查找某一个“特定”的数据元素是否存在,检索某一个“特定”数据元素的各种属性。

1.顺序表的查找:顺序查找

顺序表/线性链表

int Search_Seq ( SSTable ST, KeyType key )
{ //在顺序表ST中顺序查找关键字等于key的元素。若找到,则函数值为该元素在表中的位置,否则为0。
  ST.elem[0].key = key;   // “哨兵”
  for ( i=ST.length; !EQ(key, ST.elem[i].Key; --i );
  return i;  // 不存在待查元素,  i=0
} //Search_Seq

对顺序表而言,Ci =  n-i+1 在等概率查找的情况下,顺序表查找的平均查找长度为:

特点     

  • 无排序要求;     
  • 存储结构:顺序、链式;     
  • 平均查找长度 ASLSS = (n+1)/2; 

2.有序表查找:折半查找

int Search_Bin(SSTable ST, KeyType key)
{ //在有序表ST中折半查找法查找其关键字等于key的数据元素。若找到,则返回该元素在表中的位置,否则为0。
	low = 1;   high = ST.length;
	while (low <= high) {
		mid = (low + high) / 2;
		if EQ(key, ST.elem[mid].key)  return mid;
		else
			if  LT(key, ST.elem[mid].key) high = mid - 1;
			else  low = mid + 1;
	}
	return 0;    //表中不存在待查元素
}//Search_Bin

查找过程可描述为一棵二叉树(判定树)

一般情况,表长为 n 的折半查找的判定树的深度与含有 n 个结点的完全二叉树的深度接近

 折半查找的特点     

  • 要求元素按关键字有序。     
  • 存储结构:顺序。     
  • 平均查找长度 ASLbs= log2(n+1)-1

 3.索引顺序表的查找:分块查找

块间有序 块内无序

索引顺序查找的平均查找长度 = 查找“索引”的平均查找长度  +   查找“顺序表”的平均查找长度

将长度为n的查找表均匀地分成b块,每块有s个记录,在等概率的情况下,若在块内和索引表中均采用顺序查找,ASL=\frac{b+1}{2}+\frac{s+1}{2}

s=\sqrt{n},则平均查找长度取最小值\sqrt{n}+1

 分块查找特点:

  • 存储结构:顺序表,线性链表
  • 块间有序 块内无序
  • ASL介于顺序查找和折半查找之间

二、动态查找表

在查找的过程中同时插入表中不存在的数据元素,或者从查找表中删除已存在的某个数据元素。

1.二叉排序树

左子树上结点的值均小于根结点上的值,右子树上结点的值均大于根结点的值,递归定义。

typedef struct BiTNode {
	TElemType data;
	struct BiTNode * lchild, *rchild;
} BiTNode, *BTree;
BiTree SearchBST(BiTree T, KeyType key)
{ /*在T所指二叉排序树中查找关键字key,若成功,则返回指向该记录结点的指针,否则,返回NULL*/
	p = T;
	while (p && !EQ(key, p->data.key))
	{
		if (LT(key, T->data.key))  p = p->lchild;
		else  p = p->rchild;
	}
	return  p;
} //SearchBST

对二叉排序树进行中序遍历可得到有序序列

1)插入结点

①若二叉树为空,则首先单独生成根结点。

② 执行查找算法 SearchBST,找出被插入结点的双亲结点 p;

③ 判断被插入结点是其双亲结点的左孩子结点还是右孩子结点,将被插入结点作为叶结点插入。

Status InsertBST(BiTree  &T, TElemType e)
{
	if (!SearchBST(T, e.key, NULL, p)) // 调算法9.5b
	{  //查找不成功。p指向访问路径上最后一个结点
		s = (BiTree)malloc(sizeof(BiTNode));
		s->data = e;   s->lchild = s->rchild = NULL;
		if (!p) T = s;        // T为空,被插结点为根结点
		else if (LT(e.key, p->data.key))  p->lchild = s;
		else   p->rchild = s;
		return TRUE;       // 插入成功
	}
	else return FALSE; // 树中已有e,不需要再插入
} // InsertBST             

2)删除结点

要删除二叉排序树中的p结点,分3种情况:

① p为叶子结点,只需修改 p 双亲的指针

② p只有左子树或右子树 p只有左子树,用p的左孩子或右孩子代替p

③ p左、右子树均非空 沿 p 左子树的根 C 找到最右结点 S,将 S 的左子树成为 S 的双亲Q 的右子树,用 S 取代 p。若 C 无右子树,用 C 取代 p

void  Delete(BiTree &p)
{
	if (!p->rchild)    // 右子树为空,则只需重链左子
	{
		q = p;   p = p->lchild;   free(q);
	}
	else if (!p->lchild)  // 左子为空,则只重链右子
	{
		q = p;   p = p->rchild;   free(q);
	}
	else     // 左右子树均不空
	{
		q = p;   s = p->lchild;
		while (s->rchild)   // 定位p左子的最右结点
		{
			q = s; s = s->rchild;
		}
		p->data = s->data;  // s指向被删结点的前驱
		if (q != p) q->rchild = s->lchild;  //重链右子
		else         q->lchild = s->lchild;   //重链左子
	}
}

二叉排序树的特点

  • 含有 n 个结点的二叉排序树不唯一,与结点插入的顺序有直接关系。当查找失败后,在"叶"结点插入。
  • 删除某个结点后,二叉排序树要重组。
  • 在随机的情况下查找、插入、删除的平均时间复杂度为O(log n);
  • 最坏情况二叉排序树是单支树时,其平均查找长度为O(n)

2.平衡二叉树

每个结点的左、右子树高度差的绝对值不超过 1的二叉排序树

方法:在插入和删除过程中,采用平衡旋转技术,维护树的深度。

最小不平衡子树: 以离插入节点最近的失衡祖先为根节点的子树

调整策略: 把最小不平衡子树变成平衡子树;同时降低最小不平衡子树深度

旋转:(理解大小关系)

右旋:旋转结点挂到左子树的右子树上,占了原来的右子树的位置,所以原来左子树的右子树变成旋转节点的左子树,旋转结点原来的左子树变成根。

左旋:旋转结点挂到右子树的左子树上,占了原来的左子树的位置,所以原来右子树的左子树变成旋转节点的右子树,旋转结点原来的右子树变成根。

//右旋:根要右旋成其左子树的右子树,其左子树的右子树作为根的新的左子树,其左子树作为新的根
void R_Rotate(BiTree &root)
{
	BiTree left;
	left = root->lchild;
	root->lchild = left->rchild;
	left->rchild = root;
	root = left;
}
//左旋:根要左旋成其右子树的左子树,其右子树的左子树将作为根的新的右子树,其右子树作为新的根
void L_Rotate(BiTree &root)
{
	BiTree right;
	right = root->rchild;
	root->rchild = right->lchild;
	right->lchild = root;
	root = right;
}

1)LL:新插入结点在 A 的左子树(L)根结点的左子树(L)上。A 的平衡因子由1增至 2,导致失衡。

需要右单旋转(R_Rotate)

2)RR:新插入结点在A的右子树根结点的右子树上。A的平衡因子由 -1 增至 -2,导致失衡。需要左单旋转(L_Rotate)

3)LR:新插入结点在A的左子树根结点的右子树上。A的平衡因子由 1 增至 2,导致失衡。需要先对其左孩子进行左旋,再对自己进行右旋。

4)RL:新插入结点在A的右子树根结点的左子树上。A的平衡因子由 -1 增至 -2,导致失衡。需要先对其右孩子进行右旋,再对自己进行左旋。

typedef struct BiTNode {
	char data;
	int height;
	struct BiTNode *lchild, *rchild;
}BiTNode,*BiTree;
int GetHeight(BiTree T) {
	if (T == NULL)	return -1;
	else return T->height;
}
void R_Rotate(BiTree &T) {
	BiTree left = T->lchild;
	T->lchild = left->rchild;
	left->rchild = T;
	T->height= GetHeight(T->lchild) > GetHeight(T->rchild) ? (GetHeight(T->lchild) + 1) : (GetHeight(T->rchild) + 1);
	T = left;
}
void L_Rotate(BiTree &T) {
	BiTree right = T->rchild;
	T->rchild = right->lchild;
	right->lchild = T;
	T->height = GetHeight(T->lchild) > GetHeight(T->rchild) ? (GetHeight(T->lchild) + 1) : (GetHeight(T->rchild) + 1);
	T = right;
}
void Insert(BiTree &T, char c) {
	if (T == NULL) {
		T = new BiTNode();
		T->data = c;
		T->height = 0;
		T->lchild = NULL;
		T->rchild = NULL;
		return;
	}
	else if (c < T->data) {
		Insert(T->lchild, c);
		if (GetHeight(T->lchild) - GetHeight(T->rchild) > 1) {
			if (c < T->lchild->data) {
				R_Rotate(T);
			}
			else {
				L_Rotate(T->lchild);
				R_Rotate(T);
			}
		}
	}
	else if (c > T->data) {
		Insert(T->rchild, c);
		if (GetHeight(T->rchild) - GetHeight(T->lchild) > 1) {
			if (c > T->rchild->data) {
				L_Rotate(T);
			}
			else {
				R_Rotate(T->rchild);
				L_Rotate(T);
			}
		}
	}
	else;
	T->height = GetHeight(T->lchild) > GetHeight(T->rchild) ? (GetHeight(T->lchild) + 1) : (GetHeight(T->rchild) + 1);
}

void Delete(BiTree &T, char c) {
	if (T == NULL) {
		return;
	}
	if (c < T->data) {
		Delete(T->lchild, c);
		if (GetHeight(T->rchild) - GetHeight(T->lchild) > 1) {
			if (GetHeight(T->rchild->lchild) > GetHeight(T->rchild->lchild)) {
				R_Rotate(T->rchild);
				L_Rotate(T);
			}
			else {
				L_Rotate(T);
			}
		}
	}
	else if (c > T->data) {
		Delete(T->rchild, c);
		if (GetHeight(T->lchild) - GetHeight(T->rchild) > 1) {
			if (GetHeight(T->lchild->lchild) < GetHeight(T->lchild->rchild)) {
				L_Rotate(T->lchild);
				R_Rotate(T);
			}
			else {
				R_Rotate(T);
			}
		}
	}
	else {
		if (T->lchild != NULL && T->rchild != NULL) {
			if (GetHeight(T->lchild) >= GetHeight(T->rchild)) {
				BiTree Max = T->lchild;
				while (Max->rchild != NULL) {
					Max = Max->rchild;
				}
				T->data = Max->data;
				Delete(T->lchild, Max->data);
			}
			else {
				BiTree Min = T->rchild;
				while (Min->lchild != NULL) {
					Min = Min->lchild;
				}
				T->data = Min->data;
				Delete(T->rchild, Min->data);
			}
		}
		else {
			BiTree n = T;
			if (T->lchild != NULL) {
				T = T->lchild;
			}
			else if (T->rchild != NULL) {
				T = T->rchild;
			}
			else {
				T = NULL;
			}
			delete(n);
		}
	}
	if (T != NULL) {
		T->height = GetHeight(T->lchild) > GetHeight(T->rchild) ? (GetHeight(T->lchild) + 1) : (GetHeight(T->rchild) + 1);
	}
}

3.B树

多路平衡查找树,一棵 m 阶的 B树,或为空,或为满足下列特征的 m 叉树:

(1)根节点的子树数量∈[2,m],关键字数∈[1,m-1],其它结点的子树数∈[\left \lceil m/2 \right \rceil,m],关键字数∈[\left \lceil m/2 \right \rceil-1,m-1]

(2)对任一结点,其所有子树高度都相同

(3)关键字的值:子树0<关键字1<子树1<关键字2<子树2<...

#define branch 5
typedef struct BNode{
    ElemType Keys[branch-1];
    struct BNode *child[branch];
    int num;
}BNode,*BiTree;

B树的高度log_{m}(n+1)\leqslant h\leqslant log_{\left \lceil m/2 \right \rceil}\frac{n+1}{2}+1

B树的插入和删除

(1)在插入key后,若导致原结点关键字数超过上限m-1,则从中间位置\left \lceil m/2 \right \rceil将其中的关键字分为两部分,含的关键字放在原结点中,右部分包含的关键字放到新结点中,中间位置插入原结点的父节点中。若此时导致其父节点的关键字个数也超过了上限,则继续进行这种分裂操作,直至这个过程传到根节点。

(2)若被删除关键字在终端结点,则直接删除关键字,若被删除关键字在非终端结点,则用左侧指针所指子树最右下的元素替代。若删除导致结点关键字数低于下限,则分为

        ①兄弟够借:当此结点右(或左)兄弟结点的关键字个数还很宽裕,则需要调整该结点,右(或左)兄弟结点及其双亲结点。(填补空缺)

        ②兄弟不够借:若此结点相邻的左右结点的关键字数均等于下限,则将关键字删除后与左(或右)兄弟结点及双亲结点中的关键字进行合并,在合并过程中,双亲结点中的关键字数会减1。同样递归进行以上操作。

4.B+树

一棵m阶的B+树需满足:

(1)每个分支结点最多有m棵子树,非叶根结点至少有两棵子树,其它每个分支结点至少有\left \lceil m/2 \right \rceil子树。

(2)结点的子树个数与关键字个数相等

(3)所有的叶子结点包含全部的关键字及指向相应记录的指针,叶节点中将关键字按大小顺序排列,并且与相邻叶子节点按大小连接起来

(4)所有分支结点只包含它的子节点中关键字的最大值,及指向子节点的指针,不指向数据。

B树和B+树的差异

(1)m阶B+树结点中n个关键字对应n棵子树,而在B树中n个关键字对应n+1棵子树。

(2)m阶的B树,根节点的关键字数[1,m-1],其它节点[\left \lceil m/2 \right \rceil-1,m-1],m阶的B+树根节点的关键字数[1,m],其它节点[\left \lceil m/2 \right \rceil,m]

(3)在B+树中,叶子结点包含全部的关键字,非叶子结点可能含有重复的关键字但不包含信息,在B树中,各结点中包含的关键字是不重复的且包含信息

B+树比B 树更适合作为操作系统的文件索引

        ①B+树的磁盘读写代价更低 B+树节点小,一个节点可容纳更多的关键字,因此,访问外存的次数更少;     

        ②B+树的查询效率更加稳定。 B+树任何关键字的查找必须走一条从根结点到叶子结点的路;

三、哈希表

散列法(哈希法)基本思想:

通过一个确定的函数关系 hash ,以结点的关键字 Key 为自变量 ,函数值 hash(Key) 作为结点的存储地址。检索时根据这个函数计算其存储位置 。通常散列表的存储空间是一个一维数组,散列地址是数组的下标

负载因子 α = n/m (散列表的空间大小为 m 填入表中的结点数为 n)

冲突 某个散列函数对于不相等的关键字计算出了相同的散列地址 在实际应用中,不产生冲突的散列函数极少存在

同义词 发生冲突的两个关键字

1.散列函数的构造方法

直接定址法:取关键字或关键字的某个线性函数值为哈希地址。H(key)=key \; or \; a*key+b

数字分析法:取关键字的若干数位组成哈希地址。  

平方取中法:取关键字平方后的中间几位为哈希地址。

除留余数法:假定散列表表长为m,取一个不大于m但最接近或等于m的质数p。H(key)=key%p

随机数法:选择一个随机函数,取关键字的随机函数值为它的哈希地址。H(key) = random(key)

2.解决冲突的方法

拉链法:把发生冲突的关键字存储在散列表主表之外(链地址法、公共溢出区)

        将哈希地址相同的记录链在同一链表中,建立一个链表方式的同义词子表。

开放定址法:把发生冲突的关键字存储在表中另一个槽(位置)内

        Hi( key ) = ( H(key) + di ) MOD m

        di为增量序列,有三种取法:

        线性探测法:di = 1, 2, 3, … 

        平方探测法:d_{i}=0^{2},1^{2},-1^{2},2^{2},-2^{2},...,k^{2},-k^{2}

        伪随机数法:di = 伪随机数序列

        双散列法:di=i*H2(key)   H2(key) 是另设定的一个哈希函数,它的函数值应和 m 互为素数。

删除记录,填写特殊标识

计算ASL

1)拉链法:

查找成功概率:算出对应的哈希值去寻找比较次数,再加起来乘上概率即可

查找失败概率:计算每一种哈希值的最多比较次数情况,加起来乘上概率

2)开发地址法:

查找成功概率:计算每一个值的比较次数,再加起来乘上概率。

查找失败概率:计算每一种哈希值情况,然后根据对应的探测方法去探测,直到探测到的位置是空的,总的探测次数就是查找失败的长度,再加起来乘上概率。

———————————————————————————————————————————

例题:

 

 ——————————————————————————————————————————

n个结点(关键字)的二叉排序树/平衡二叉树/B树,查找所需的最多比较次数(或树的最大高度)

1)二叉排序树:

单支树情况,需要比较n次

2)平衡二叉树:

T(h)=T(h-1)+T(h-2)+1,T0=0,T1=1。

3)B树:

log_{m}(n+1)<=h<=[log_{\left \lceil m/2 \right \rceil}((n+1)/2)]+1

———————————————————————————————————————————

采用开发地址法产生聚集的原因主要是:解决冲突的方法选择不当

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值