查找算法总结

目录


查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本运算,例如编译程序中符号表的查找。
在这里插入图片描述

查找定义:根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。

查找算法分类:
  1)静态查找和动态查找;
    注:静态或者动态都是针对查找表而言的。
    动态表指查找表中有删除和插入操作的表。
  2)无序查找和有序查找。
    无序查找:被查找数列有序无序均可;
    有序查找:被查找数列必须为有序数列。
  平均查找长度(Average Search Length,ASL):需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。(在查找运算中,由于所费时间在关键字的比较上,所以把平均需要和待查找值比较的关键字次数称为平均查找长度。)
  在这里插入图片描述
  其中n为查找表中元素个数,Pi为查找第i个元素的概率,通常假设每个元素查找概率相同,Pi=1/n,Ci是找到第i个元素的比较次数。

**七大查找算法总结

参考博客

一、线性表的查找

在查找表的组织方式中,线性表是最简单的一种。本节将介绍基于线性表的顺序查找、折半
查找和分块查找。

1.1 顺序查找

在这里插入图片描述
在这里插入图片描述
复杂度分析: 
  查找成功时的平均查找长度为:(假设每个数据元素的概率相等) ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
  当查找不成功时,需要n+1次比较,时间复杂度为O(n);
  所以,顺序查找的时间复杂度为O(n)。

顺序查找的优点是:算法简单, 对表结构无任何要求,既适用于顺序结构, 也适用千链式结构, 无论记录是否按关键字有序均可应用。
其缺点是: 平均查找长度较大, 查找效率较低, 所以当n很大时, 不宜采用顺序查找。
  
C++实现

int SequenceSearch(int a[], int value, int n)
{
    int i;
    for(i=0; i<n; i++)
        if(a[i]==value)
            return i;
    return -1;
}

1.2 折半查找(二分法查找)

说明:元素必须是有序的,如果是无序的则要先进行排序操作。

基本思想:也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。

复杂度分析:最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);
  
  折半查找判定树:
在这里插入图片描述
在这里插入图片描述
折半查找过程就是走从根节点到被查节点的一条路径,比较的次数是该路径中节点的个数,也就是该节点在树中的层数。

注:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
  
  折半查找的优点是:比较次数少,查找效率高。
  其缺点是:对表结构要求高,只能用于顺序存储的有序表。查找前需要排序,而排序本身是一种费时的运算。同时为了保持顺序表的有序性,对有序表进行插入和删除时,平均比较和移动表中一半元素,这也是一种费时的运算。因此,折半查找不适用于数据元素经常变动的线性表。
  在这里插入图片描述

C++实现

//二分查找(折半查找),版本1
int BinarySearch(int a[],int val,int n)
{
    int low = 0,high = n -1,mid;
    while (low<= high) {
        mid = low + (low +high)/2; //取区间中点
        if (a[mid] == val)       //查找成功
            return mid;
        else if (a[mid] > val)   //在左区间中查找
            high = mid - 1;
        else                    //在右区间中查找
            low = mid + 1;
    }
    return -1; //查找失败
}

//二分查找,递归版本
int BinarySearch2(int a[], int value, int low, int high){                   
    if(low <= high){   
	    int mid = low+(high-low)/2;
	    if(a[mid]==value)
	        return mid;
	    if(a[mid]>value)
	        return BinarySearch2(a, value, low, mid-1);
	    if(a[mid]<value)
	        return BinarySearch2(a, value, mid+1, high);
	}
	return -1;   //low > high,找不到
}


/*
int BinarySearch2(int a[], int value, int low, int high){                   
    if(low <= high){   
	    int mid = low+(high-low)/2;
	    if(a[mid]==value)
	        return mid;
	    if(a[mid]>value)
	        high = mid-1;
	    if(a[mid]<value)
	       low = mid+1;
	    return BinarySearch2(a, value, low, high);
	}
	return -1;   //low > high,找不到
}
*/

1.3 分块查找

分块查找(Blocking Search) 又称索引顺序查找,这是一种性能介于顺序查找和折半查找之间的一种查找方法。在此查找法中,除表本身以外,尚需建立一个“索引表”(空间换时间)。索引表按关键字有序,则表或者有序或者分块有序。所谓“ 分块有序” 指的是第二个子表中所有记录的关键字均大于第一个子表中的最大关键字,第三个子表中的所有关键字均大千第二个子表中的最大关键字,……,依次类推。

在这里插入图片描述

分块查找的优点是:在表中插入和删除数据元素时,只要找到该元素对应的块,就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易,无需进行大量移动。如果线性表既要快速查找又经常动态变化,则可采用分块查找。
其缺点是:要增加一个索引表的存储空间并对初始索引表进行排序运算。

C++代码实现

#include <bits/stdc++.h>

using namespace std;

struct index
{
	int key;
	int start;

} newIndex[3];
int search(int key, int* a)
{
	int i = 0, startValue = newIndex[i].start;
	while (i<3 && key>newIndex[i].key)i++;
	if (i >= 3)	return -1;
	while (startValue <= startValue + 5 && a[startValue] != key)startValue++;
	if (startValue > startValue + 5)return -1;
	return startValue;
}
int cmp(const void* a, const void* b)
{
	return (*(struct index*)a).key > (*(struct index*)b).key ? 1 : -1;
}
int main(void)
{
	int a[] = { 33,42,44,38,24,48, 22,12,13,8,9,20, 60,58,74,49,86,53 }, j = -1, key;
	for (int i = 0; i < 3; i++)
	{
		newIndex[i].start = j + 1;
		j += 6;
		for (int k = newIndex[i].start; k <= j; k++)if (newIndex[i].key < a[k])newIndex[i].key = a[k];
	}
	qsort(newIndex, 3, sizeof(newIndex[0]), cmp);
	cin >> key;
	cout << search(key, a);;
}

C语言图解:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、树表的查找

前面介绍的3 种查找方法都是用线性表作为查找表的组织形式,其中折半查找效率较高。但由于折半查找要求表中记录按关键字有序排列,且不能用链表做存储结构,因此,当表的插入或删除操作频繁时,为维护表的有序性,需要移动表中很多记录。这种由移动记录引起的额外时间开销,就会抵消折半查找的优点。所以,线性表的查找更适用于静态查找表,若要对动态查找表进行高效率的查找,可采用几种特殊的二叉树作为查找表的组织形式,在此将它们统称为树表。本节将介绍在这些树表上进行查找和修改操作的方法。

2.1 二叉排序树

基本思想:二叉查找树是先对待查找的数据进行生成树,确保树的左分支的值小于右分支的值,然后在就行和每个节点的父节点比较大小,查找最适合的范围。 这个算法的查找效率很高,但是如果使用这种查找方法要首先创建树。

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:

1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;

2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;

3)任意节点的左、右子树也分别为二叉查找树。

二叉查找树性质:对二叉查找树进行中序遍历,即可得到有序的数列。

不同形态的二叉查找树如下图所示:
在这里插入图片描述
复杂度分析:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡(比如,我们查找上图(b)中的“93”,我们需要进行n次查找操作)。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡二叉树设计的初衷。

基于二叉查找树进行优化,进而可以得到其他的树表查找算法,如平衡树、红黑树等高效算法。

二叉查找树的操作

2.2 平衡二叉树

定义:二叉排序树查找算法的性能取决于二叉树的结构,而二叉排序树的形状则取决于其数据集。如果数据呈有序排列,则二叉排序树是线性的,查找的时间复杂度为O(n); 反之,如果二叉排序树的结构合理,则查找速度较快,查找的时间复杂度为O(lo2g n)。事实上,树的高度越小,查找速度越快。因此,希望二叉树的高度尽可能小。本节将讨论一种特殊类型的二叉排序树,称为平衡二叉树(Balance d Binary Tree或Height-Balanced·Tree),因由前苏联数学家Adelson-Velskii和Land i s提出,所以又称AVL树。

插入结点时, 首先按照二叉排序树处理, 若插入结点后破坏了平衡二叉树的特性, 需对平衡二叉树进行调整。调整方法是:找到离插入结点最近且平衡因子绝对值超过1的祖先结点, 以该结点为根的子树称为最小不平衡子树, 可将重新平衡的范围局限于这棵子树。

2.3 红黑树

从平衡二叉树到红黑树

平衡二叉树(AVL)树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而的英文旋转非常耗时的。所以平衡二叉树(AVL)适合用于插入与删除次数比较少,但查找多的情况。

红黑树放弃了追求完全平衡(弱平衡),追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。

红黑树在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。所以红黑树适用于搜索,插入,删除操作较多的情况。

红黑树的定义

https://blog.csdn.net/v_JULY_v/article/details/6105630

红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。

红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(log n)。

红黑树性质

1.每个结点要么是红的要么是黑的。
2.根结点是黑的。
3.每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
4.如果一个结点是红的,那么它的两个儿子都是黑的。
5. 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

(注:上述第3、5点性质中所说的NULL结点,包括wikipedia.算法导论上所认为的叶子结点即为树尾端的NIL指针,或者说NULL结点。然百度百科以及网上一些其它博文直接说的叶结点,则易引起误会,因,此叶结点非子结点)

红黑树的插入

红黑树的插入相当于在二叉查找树插入的基础上,为了重新恢复平衡,继续做了插入修复操作。

情况一、二:插入的是根节点、插入的节点的父节点是黑色

在这里插入图片描述

情况三:当前节点的父节点是红色且祖父节点的另一个子节点(叔叔节点是红色)是红色

在这里插入图片描述
变化后:
在这里插入图片描述

情况四:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

在这里插入图片描述
变化后:
在这里插入图片描述

情况五:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子

**解决对策是:**父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋

在这里插入图片描述

变化后:
在这里插入图片描述

红黑树的删除

参考【1】【2】【3】

用来替换被删除位置的节点称为当前节点 -x

前面我们将"删除红黑树中的节点"大致分为两步,在第一步中"将红黑树当作一颗二叉查找树,将节点删除"后,可能违反"特性(2)、(4)、(5)"三个特性。第二步需要解决上面的三个问题,进而保持红黑树的全部特性。

为了便于分析,我们假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。为什么呢?
通过RB-DELETE算法,我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设"x包含一个额外的黑色",就正好弥补了"删除y所丢失的黑色节点",也就不会违反"特性(5)"。 因此,假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。
现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是"红+黑"或"黑+黑",它违反了"特性(1)"。

现在,我们面临的问题,由解决"违反了特性(2)、(4)、(5)三个特性"转换成了"解决违反特性(1)、(2)、(4)三个特性"。RB-DELETE-FIXUP需要做的就是通过算法恢复红黑树的特性(1)、(2)、(4)。RB-DELETE-FIXUP的思想是:将x所包含的额外的黑色不断沿树上移(向根方向移动),直到出现下面的姿态:
a) x指向一个"红+黑"节点。此时,将x设为一个"黑"节点即可。
b) x指向根。此时,将x设为一个"黑"节点即可。
c) 非前面两种姿态。

将上面的姿态,可以概括为3种情况。
① 情况说明:x是“红+黑”节点。
处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。
② 情况说明:x是“黑+黑”节点,且x是根。
处理方法:什么都不做,结束。此时红黑树性质全部恢复。
③ 情况说明:x是“黑+黑”节点,且x不是根。
处理方法:这种情况又可以划分为4种子情况:

情况一:当前节点是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。

解法:把父节点染成红色,把兄弟结点染成黑色,然后父结点做一次左旋(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况 [即变成情况2、3、4]
(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟节点为黑色的情况)。具体地:

(1) 将x的兄弟节点设为“黑色”。
(2) 将x的父节点设为“红色”。
(3) 对x的父节点进行左旋。
(4) 左旋后,重新设置x的兄弟节点。

在这里插入图片描述

情况二:当前节点是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。

解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法
(此变换后性质5不变)具体地:

(1) 将x的兄弟节点设为“红色”。
(2) 设置“x的父节点”为“新的x节点”。
在这里插入图片描述

情况三:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色。(其实这里要区分,当前节点是父节点的左子树还是右子树,当前节点是左子树则按这里的规则处理;若当前节点是右子树,则左变右,右变左)

解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法
此是把当前的情况转化为情况4,而性质5得以保持,具体地:

(1) 将x兄弟节点的左孩子设为“黑色”。
(2) 将x兄弟节点设为“红色”。
(3) 对x的兄弟节点进行右旋。
(4) 右旋后,重新设置x的兄弟节点。

在这里插入图片描述

情况四:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意。(其实这里要区分,当前节点是父节点的左子树还是右子树,当前节点是左子树则按这里的规则处理;若当前节点是右子树,则左变右,右变左)

解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确。具体地:

(1) 将x父节点颜色 赋值给 x的兄弟节点。
(2) 将x父节点设为“黑色”。
(3) 将x兄弟节点的右子节设为“黑色”。
(4) 对x的父节点进行左旋。
(5) 设置“x”为“根节点”。
在这里插入图片描述

2.4 B树

B-树

B+树

三、散列表的查找

四、常用排序算法和查找算法的时间复杂度和空间复杂度

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值