C++实现常用查找算法(七大查找算法)

常用查找算法

查找算法分类

1)静态查找和动态查找;
注:静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。
2)无序查找和有序查找。
无序查找:被查找数列有序无序均可;
有序查找:被查找数列必须为有序数列。

平均查找长度(Average Search Length,ASL)

需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。

对于含有n个数据元素的查找表,查找成功的平均查找长度为:ASL = Pi*Ci的和。
Pi:查找表中第i个数据元素的概率。
Ci:找到第i个数据元素时已经比较过的次数。

1.顺序查找

基本思想:顺序查找也称为线形查找,属于无序查找算法。从数据结构线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值k相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字等于k的结点,表示查找失败。

顺序查找的时间复杂度为O(n)

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

顺序查找适合于存储结构为顺序存储或链接存储的线性表

2.二分查找

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

复杂度分析:最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);

//二分查找(折半查找),常用版本
int BinarySearch1(int a[], int value, int n)
{
    int low, high, mid;
    low = 0;
    high = n-1;
    while(low<=high)
    {
        mid = (low+high)/2;
        if(a[mid]==value)
            return mid;
        if(a[mid]>value)
            high = mid-1;
        if(a[mid]<value)
            low = mid+1;
    }
    return -1;
}
 
//二分查找,递归版本
int BinarySearch2(int a[], int value, int low, int 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);
}

:折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,折半查找能得到不错的效率。但对于需要频繁执行插入或删除操作的数据集来说,维护有序的排序会带来不小的工作量,那就不建议使用。——《大话数据结构》
元素必须是有序的,如果是无序的则要先进行排序操作

3.差值查找

基本思想:基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。当然,差值查找也属于有序查找。

复杂度分析:查找成功或者失败的时间复杂度均为O(log2(log2n))。

//只适用已经排序好的,和二分查找逻辑一致,修改mid值
int interpolation_search(int* arr, int n, int key)
{
	int left = 0, right = n - 1;
	while (left <= right)
	{
        //优化中间值
		int mid = left + (key - arr[left]) / (arr[right] - arr[left]) * (right - left-1);
		if (key == arr[mid]) return mid;
		else if (key < arr[mid]) right = mid - 1;
		else left = mid + 1;
	}
	return -1;
}

注:对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

4.斐波那契查找

**基本思想:**也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。

**复杂度分析:**最坏情况下,时间复杂度为O(log2n),且其期望复杂度也为O(log2n)。

int Fibonacci(int n)
{
	int f = 0, g = 1;
	while (n--)
	{
		g += f;
		f = g - f;
	}
	return f;
}
int FibonacciSearch(int* a, int n, int key)
{
	int low = 1, high = n, mid, i, k = 0;
	while (n > Fibonacci(k) - 1)k++;
	for (i = n; i < Fibonacci(k) - 1; i++)a[i] = a[n];
	while (low <= high)
	{
		mid = low + Fibonacci(k - 1) - 1;
		if (key < a[mid])
		{
			high = mid - 1;
			k -= 1;
		}
		else if (key > a[mid])
		{
			low = mid + 1;
			k -= 2;
		}
		else
		{
			if (mid <= n)return mid;
			else return n;
		}
	}
	return -1;
}
5.树表查找

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

二叉查找树(BinarySearch Tree,也叫二叉搜索树,或称二叉排序树Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3)任意节点的左、右子树也分别为二叉查找树。

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

**复杂度分析:**它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。

//定义二叉树数据结构
class TreeNode
{
pubic:
    TreeNode(int value)
    {
        this.value = value;
    }
private:
    int value;
    TreeNode left;
    TreeNode right;
}

class TreeSearch
{
	//从根节点为root的树中查找节点的值为value的节点
    TreeNode search(TreeNode root,int value)
    {   //空树
        if (root == nullptr)
        {
            return nullptr;
        }
        //定义当前节点
        TreeNode current = root;
        while(current != nullptr)
        {
            if (current.val < value)
            {
                //如果当前节点的值比value小,则从其右子树中开始找
                current = current.right;
            }
            else if (current.val > value)
            {
                //如果当前节点的值比value大,则从其左子树中开始找
                current = current.left;
            }
            else if (current.val == value)
            {
                //找到则返回这个节点
                return current;
            }
        }
        return nullptr;
    }
}

6.分块查找

**算法思路:**将n个数据元素”按块有序”划分为m块(m ≤ n)。每一块中的结点不必有序,但块与块之间必须”按块有序”;即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2块中任一元素又都必须小于第3块中的任一元素

算法适用情况(块内无序,块间有序):
由于块内是无序的,故插入和删除比较容易,无需进行大量移动。 如果线性表既要快速查找又经常动态变化,则可采用分块查找。
使用条件:线性表必须要能实现分块之间有序。

步骤如下:
1.首先将查找表分成若干块,在每一块中数据元素的存放是任意的,但块与块之间必须是有序的;
2.建立一个索引表,把每块中最大的关键字值按块的顺序存放在一个辅助数组中,这个索引表也按升序排列;
3.查找时先用给定的关键字值在索引表中查找,确定满足条件的数据元素存放在哪个块中,查找方法既可以是折半方法,也可以是顺序查找。
4.再到相应的块中顺序查找,便可以得到查找的结果。

//在序列数组st中,用分块方法查找target的记录,在arr[]中二分查找,确定属于m个块的那哪一个块中
int blockSearch(int arr[],int st[],int target,int m)
{
    int start=0;
    int end=arr.size()-1;
    int index=binarySearch(arr,start,end,target);
    if(index>=0)
    {
        int j=index>0?m*index:index;
        int length=(index+1)*m;
        //在确定的快中用顺序查找方法查找key
        for(int k=j;k<length;k++)
        {
            if(target==st[k])
            {//查找成功
                return k;
            }
        }
    }
}

7.哈希查找

算法思路:
1.用给定的哈希函数构造哈希表;
2.根据选择的冲突处理方法解决地址冲突;
常见的解决冲突的方法:拉链法和线性探测法。
3.在哈希表的基础上执行哈希查找。

//实则unordered_map数据结构就是这么实现的
int searchHash(int[] hash, int hashLength, int key)
{
    // 哈希函数
    int hashAddress = key % hashLength;
    // 指定hashAdrress对应值存在但不是关键值,则用开放寻址法解决
    while (hash[hashAddress] != 0 && hash[hashAddress] != key)
    {
        hashAddress = (++hashAddress) % hashLength;
    }
    // 查找到了开放单元,表示查找失败
    if (hash[hashAddress] == 0)
        return -1;
    return hashAddress;
}

如同leetcode重建二叉树中适用unordered_map数据结构提高查找速度

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    unordered_map<int,int> map;
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        // 将中序序列用哈希表存储,便于查找根节点
        for(int i = 0;i < inorder.size();i++)
        {
            //依据中序遍历的每个值对应的索引将中序遍历存入哈希表
            map[inorder[i]] = i;
        }        
        // 传入参数:前序,中序,前序序列根节点,中序序列左边界,中序序列右边界
        return build(preorder,inorder,0,0,inorder.size()-1);
    }
    TreeNode* build(vector<int>& preorder, vector<int>& inorder,int pre_root,int in_left,int in_right){
        //递归结束条件
        if(in_left > in_right)
        return NULL;
        //创建根节点
        TreeNode* root = new TreeNode(preorder[pre_root]);
        // 根节点在中序序列中的位置,用于划分左右子树的边界
        int in_root = map[preorder[pre_root]];
        // 根据中序遍历的特性,找到根结点,位于根节点左边的全是左子树的节点值,位于根节点右边的全是右子树的节点值        
        // 左子树在前序中的根节点位于:pre_root+1,左子树在中序中的边界:[in_left,in_root-1]
        root->left = build(preorder,inorder,pre_root+1,in_left,in_root-1);
        
        // 右子树在前序中的根节点位于:根节点+左子树长度+1 = pre_root+in_root-in_left+1
        // 右子树在中序中的边界:[in_root+1,in_right]
        root->right = build(preorder,inorder,pre_root+in_root-in_left+1,in_root+1,in_right);
        return root;
    }
};
  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值