基础算法--查找

news2025/1/11 4:05:23

一、线性枚举

1、线性枚举定义

线性枚举指的就是遍历某个一维数组(顺序表)的所有元素,找到满足条件的那个元素并且返回,返回值可以是下标,也可以是元素本身。

由于是遍历的,穷举了所有情况,所以一定是可以找到解的,一些资料上也称之为 暴力算法 (Brute Force)。接下来,我们通过一个例子来理解 线性枚举。

2、举例说明

【例题1】给定一个单调不降的有序数组 arr 和 一个值 x,要求找到大于 x 的最小数的下标。

3、算法分析

我们从这个问题中提取几个关键字并分类如下:

1. 前提:单调不降、有序;

2. 条件:大于 x、最小数;

3. 返回结果:下标;

1)前提

前提就是问题给定时的初始数组需要满足的先天性条件,保证数据是能够符合这个前提的。这里的前提是 数组一定是有序的,且是单调不降的,即 数组下标大的数 不会比 数组下标小的数 更小。

2)条件

这个问题中的条件有两个:

1. 大于 x;

2. 值最小;

我们如果仔细分析一下这个问题,就可以发现,正因为这里的数组是单调不降的,所以,一旦满足 某个数大于 x,之后的所有数必然都满足 大于 x 这个条件。所以我们必然可以把数组分成两部分:一部分是 大于 x 一部分是 不大于 x 的。

3)返回结果

这里的返回结果要求是下标,而我们遍历操作也是通过遍历数组的下标进行的,所以找到满足条件的,返回下标即可。

4、画解枚举

接下来,我们通过一组实际的数据来解释这个问题:arr = [1, 3, 4, 6, 6, 6, 7, 8, 9]

1)划分

对于这个数组,当 x = 6 时,我们将数组分成两部分,大于 6 的部分用 绿色 表示,不大于 6 的部分用 红色 表示。这么表示的目的,主要是为了方便记忆,联想一下 红绿灯,绿色代表可以通行,即 "大于6" 这个条件满足;红色代表禁止通行,即条件不满足。

2)游标

设定一个游标,初始时指向数组的第 0 个元素(C语言中数组下标从 0 开始计数)。

游标,顾名思义,就是游动的下标。你也可以叫指针,我之所以没有称之为指针,是不想它和C语言中的指针概念混淆。

3)遍历

遍历就是判断当前游标指向的元素是否是绿色的,如果是绿色的直接返回,因为它一定是大于 x 且值最小的;如果不是,则增加游标的值,继续下一次判断,直到数组遍历完毕。如下图所示:

数字 7 就是我们要找到 大于 6 的最小数,它的下标为 6。

4)详解
int isGreen(int val, int x) { // (1)

    return val > x;

}

int findFirstBiggerThan(int * arr, int arrSize, int x) {

    int i;

    for (i = 0; i < arrSize; ++i) { // (2)

        if (isGreen(arr[i], x)) { // (3)

            return i;

        }

    }

    return arrSize; // (4)

}

(1) int isGreen(int val, int x) 这个函数代表条件是否满足,满足返回 1,否则返回 0;这里的条件便是 val > x。

(2) 下标从小到大,从 0 开始遍历数组 arr;

(3) 一旦遇到大于 x 的数,则返回它的下标,因为是下标从小往大遍历的,所以第一个找到满足条件的数一定是值最小的;

(4) 如果找不到,说明所有的数都是小于等于 x 的,直接返回数组长度;

5、举一反三

接下来,我们来看看线性枚举的其它几种问法。

【例题2】给定一个单调不降的有序数组如下:[1, 3, 4, 6, 6, 6, 7, 8, 9]。要求找到以下元素:

(1) > 6 的 最小数 的下标位置;

(2) ≥ 6 的 最小数 的下标位置;

(3) < 6 的 最大数 的下标位置;

(4) ≤ 6 的 最大数 的下标位置;

对于这四个问题,我们可以发现它们的答案如下所示:

1)大于 x 的最小数的下标

将数组按照条件进行划分,然后利用上文提到的 findFirstBiggerThan 函数求解即可。

2)大于等于 x 的最小数的下标

我们把问题做个变形,将问题变成找 大于等于 x 的最小数的下标(比之前的问题多了一个等于)。按照条件划分的结果应该是包含 6 本身的,所以如下图所示:

遍历数组的部分不变,只不过条件变成了 大于等于。C语言实现如下:

int isGreen(int val, int x) {

    return val >= x; // (1)

}

int findFirstBiggerEqualThan(int * arr, int arrSize, int x) {

    int i;

    for (i = 0; i < arrSize; ++i) {

        if (isGreen(arr[i], x)) {

            return i;

        }

    }

    return arrSize;

}

(1) 将原先的 > 号改成 >= 即可;

3)小于 x 的最大数的下标

上面两个问题能理解的话,我们再来看一个问题,如何找到 小于 x 的最大数的下标 ,要求下标最大,那么我们在枚举的过程中,如果发现一个大于等于 x 的数,那么后续都不用枚举了,并且需要返回这个数的前一个位置。条件划分如下图所示:

我们要做的是返回红色中的最大下标,C语言实现如下:

int isGreen(int val, int x) {

    return val >= x; // (1)

}



int findLastSmallThan(int * arr, int arrSize, int x) {

    int i;

    for (i = 0; i < arrSize; ++i) {

        if (isGreen(arr[i], x)) {

            return i - 1;

        }

    }

    return arrSize - 1;

}

(1) 大于等于 x 时, isGreen 成立;

(2) 由于我们要做的是返回红色中的最大下标,所以一旦遇到大于等于 x 的数(即绿色的情况),则返回它的前一个下标;

(3) 如果找不到,则返回 arrSize - 1,即所有数都是红色的,则最大下标就是数组的最后一个元素的下标;

4)小于等于 x 的最大数的下标

我们把问题继续做变形,将问题变成找 小于等于 x 的最大数的下标(比之前的问题多了一个等于)。划分如下图所示:

遍历数组的部分不变,只不过条件变成了 大于,我们要做的是返回红色中的最大下标,C语言实现如下:

int isGreen(int val, int x) {

    return val > x; // (1)

}



int findLastSmallEqualThan(int * arr, int arrSize, int x) {

    int i;

    for (i = 0; i < arrSize; ++i) {

        if (isGreen(arr[i], x)) {

            return i - 1;

        }

    }

    return arrSize - 1;

}

(1) 将原先的 >= 号改成 > 即可;

6、时间复杂度

以上的内容就是线性枚举的几种常见情况,也就是无脑遍历所有情况,并且在满足条件的第一时间退出循环,当数组长度为 n 时,算法的时间复杂度为 O(n),比较低效,有没有更加高效的算法呢?

接下来出场的,就是本文的主角 —— 二分枚举。

二、二分枚举

1、二分枚举定义

二分枚举,也叫二分查找,指的就是给定一个区间,每次选择区间的中点,并且判断区间中点是否满足某个条件,从而选择左区间继续求解还是右区间继续求解,直到区间长度不能再切分为止。

由于每次都是把区间折半,又叫折半查找,时间复杂度为 O(logn),和线性枚举的求解结果一直,但是高效许多,返回值可以是下标,也可以是元素本身。

2、举例说明

【例题3】只有两种颜色的数组 arr ,左边部分为红色用 0 表示,右边部分为绿色用 1 表示,要求找到下标最小的绿色元素的下标。

如图所示,下标最小的绿色元素的下标为 3,所以应该返回 3。

3、算法分析

1)目标

对于这个问题,当我们拿到这个数组的时候,第一个绿色位置在哪里,我们是不知道的,所以,现在的目标就是要通过二分枚举找到红色区域和绿色区域的边界。

2)游标

利用线性枚举的思路,我们引入游标的概念,只不过需要两个游标,左边一个红色游标,右边一个绿色游标。并且游标初始位置都在数组以外,对于一个 n 个元素的数组,红色游标初始位置在 -1,绿色游标初始位置在 n。

3)二分

我们将两个游标相加,并且除 2,从而得到游标的中点,并且判断中点所在位置的颜色,发现是绿色的,这说明从 中点游标绿色游标 的元素都是绿色的。如下图所示:

于是,我们可以把 绿色游标 替换成 中点游标,如下图所示:

这样就完成了一次二分,区间相比之前,缩小了一半。注意,我们要求的解,一定永远在 红色游标绿色游标 之间。

然后,我们继续将两个游标相加,并且除 2,从而得到游标的中点,并且判断中点所在位置的颜色,发现是红色的,这说明从 红色游标中点游标 的元素都是红色的。如下图所示:

于是,我们可以把 红色游标 替换成 中点游标 ,如下图所示:

同样上述算法,再经过两次二分以后,我们得到了如下结果:

这个时候,这个时候 红色游标 和 绿色游标 的位置一定相差 1,并且 绿色游标 的位置就是我们这个问题要求的解。

4)时间复杂度

由于每次操作都是将区间减小一半,所以时间复杂度为 O(logn)。

4、源码详解

那么接下来,我们来看下,如何用 C语言来 实现这个问题。

1)条件判定

判断一个元素是绿色还是红色,我们可以单独用一个函数来实现,根据题意,当值为 1 时代表绿色,值为 0 时代表红色,C语言实现如下:

int isGreen(int val) {

    return val == 1;

}
2)二分枚举模板

接下来的二分枚举模板可以解决大部分二分枚举的问题,请妥善保管。

int binarySearch(int * arr, int arrSize, int x) {

    int l = -1, r = arrSize; // (1)

    int mid;

    while (r - l > 1) { // (2)

        mid = l + (r - l) / 2; // (3)

        if (isGreen(arr[mid], x)) // (4)

            r = mid; // (5)

        else

            l = mid; // (6)

    }

    return r; // (7)

}

(1) l 代表 红色游标,r 代表 绿色游标

(2) 当区间长度大于 2 的时候,二分缩小区间,这一步被称为 区间收敛;

(3) mid 为计算出来的区间 [l, r] 的中点;

(4) 判断区间中点对应的元素是 绿色 还是 红色

(5) 如果 中点元素 是 绿色,则从 中点 到 r 的值都为 绿色,用 中点 替换 绿色游标

(6) 如果 中点元素 是 红色,则从 l 到 中点 的值都为 红色,用 中点 替换 红色游标

(7) 这个地方是模板需要变通的地方,如果需要返回红色边界,那么应该返回 l;反之,如果需要返回绿色边界,则应该返回 r。这个问题中,是后者。

5、细节解析

1)迭代的过程

整个二分的过程是一个不断迭代区间的过程,并且 红色游标 指向的元素始终是 红色 的;绿色游标 指向的元素始终是 绿色 的。迭代的过程就是不断向 红绿边界 逼近的过程。

2)结束条件

迭代结束时,红色游标绿色游标 刚好指向 红绿边界,且区间长度为 2。

3)游标初始值

为什么 红色游标 初始值为 -1,绿色游标 初始值为 n ?

能否将 红色游标 初始化为 0,绿色游标 初始化为 n-1 ? 答案是否定的,试想一下,如果数据元素都是绿色,红色游标 初始化为 0 就违背了 " 红色游标 指向的元素始终是 红色 的 " 这个条件;反之,如果元素都是红色的,也有类似问题。

4)中点位置

由于中点的位置是需要去访问数组来获取值的,所以必须满足始终在 [0, n) 区间范围内。

中点位置计算公式为:

l 的最小值为 -1,r 的最小值为 l+2,所以 mid 的最小值就是:

r 的最大值为 n,l 的最大值为 r-2,所以 mid 的最大值就是:

综上所述,中点的下标位置始终在 [0, n) 区间范围内。

5)死循环

上面的程序模板是否会进入死循环?

我们可以这么来看,当区间为 2 时,循环结束。当区间为 3 时,它一定可以变成区间为 2 的情况,当区间为4时,一定可以变成区间为 2 或者 3 的情况,也就是任何一种情况下,区间一定会减小,并且当等于 2 时,循环结束。所以不会造成死循环。

索引存储结构和分块查找

       索引表中的每一项称为索引项,索引项的一般形式是:(关键字,地址)

      关键字唯一标识一个结点,地址为该关键字元素在数据表中的存储地址,整个索引表按关键字有序排列。

 

按关键字k的查找过程

先在索引表按折半查找方法找到关键字为 k 的索引项,得到其地址,所花时间为 O(log 2 n)
再通过地址在数据表中找到对应的元素,所花时间为 O(1) ,合起来的查找时间为 O(log 2 n)

 

分块查找(块间有序,块内无序 

块间有序:分成若干子表,要求每个子表中的数值都比后一块中数值小(但子表内部未必有序)。

将各子表中的最大关键字构成一个索引表,索引表中包含每个子表的起始地址(即头指针)。

 二叉排序树(BST

二叉排序树Binary Sort Tree简称BST)又称二叉查找(搜索)树,其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树:

若它的左子树非空,则左子树上所有结点值(默认为结点关键字)均小于根结点值。

若它的右子树非空,则右子树上所有结点值均大于根结点值。

左、右子树本身又各是一棵二叉排序树。

 

   递归查找算法SearchBST()如下(在二叉排序树bt上查找关键字为k的记录,成功时返回该结点指针,否则返回NULL):

typedef struct node 
{       KeyType key;            	  //关键字项
     InfoType data;          	  //其他数据域
        struct node *lchild,*rchild; 	  //左右孩子指针
}  BSTNode;
BSTNode *SearchBST(BSTNode *bt,KeyType k)
{ 
      if (bt==NULL || bt->key==k) 	            //递归出口
            return bt;
      if (k<bt->key)
        return SearchBST(bt->lchild,k);   //在左子树中递归查找
      else
        return SearchBST(bt->rchild,k);   //在右子树中递归查找
}
int InsertBST(BSTNode *&p,KeyType k)	
{    if (p==NULL)	 //原树为空, 新插入的记录为根结点
     {     p=(BSTNode *)malloc(sizeof(BSTNode));
            p->key=k;p->lchild=p->rchild=NULL;
            return 1;
      }
      else if  (k==p->key) 	//存在相同关键字的结点,返回0
           return 0;
      else if (k<p->key) 
          return InsertBST(p->lchild,k); 	//插入到左子树中
      else  
          return InsertBST(p->rchild,k);  	//插入到右子树中
 }
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针
{      BSTNode *bt=NULL;    //初始时bt为空树
       int i=0;
       while (i<n) 
       {    InsertBST(bt,A[i]);  //将A[i]插入二叉排序树T中
       i++;
       }
       return bt;       	    //返回建立的二叉排序树的根指针
} 

平衡二叉树(AVL

若一棵二叉树中每个结点的左、右子树的高度至多相差1,则称此二叉树为平衡二叉树(AVL

 平衡因子:该结点左子树的高度减去右子树的高度。 

如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转

调整操作可归纳为4种情况。

LL 平衡旋转
RR 平衡旋转
LR 平衡旋转
RL 平衡旋转

 

函数类二分

x 的平方根

剑指 Offer II 072. 求平方根

有效的完全平方数

排列硬币

第一个错误的版本

H 指数 II

乘法表中第k小的数

数组类二分

二分查找

剑指 Offer 53 - I. 在排序数组中查找数字 I

在排序数组中查找元素的第一个和最后一个位置

寻找比目标字母大的最小字母

搜索插入位置

剑指 Offer II 068. 查找插入位置

区间内查询数字的频率

统计「优美子数组」

两数之和 II - 输入有序数组

剑指 Offer II 006. 排序数组中两个数字之和

和为s的两个数字

两数之和

统计有序矩阵中的负数

检查整数及其两倍数是否存在

排序矩阵查找

采购方案

早餐组合

交换和

分割数组的最大值

找出给定方程的正整数解

比较字符串最小字母出现频次

同时运行 N 台电脑的最长时间

二分答案

丑数 III

第 k 个缺失的正整数

尽可能使字符串相等

制作 m 束花所需的最少天数

最大连续1的个数 III

水位上升的泳池中游泳

剑指 Offer II 073. 狒狒吃香蕉

每个小孩最多能分到多少糖果

爱吃香蕉的珂珂

分配给商店的最多商品的最小值

花园的最大总美丽值

找出第 k 小的距离对

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2274689.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

G1垃圾回收器的FullGC

如何确定GarbageFirst回收器发生的是FullGC ? 必须出现FullGC字样才算是FUllGC&#xff0c;例如下图&#xff1a;因为内存分配失败&#xff08;Allocation Failure&#xff09;导致 如果不出现FullGC的字样说明它不是FUllGC&#xff0c;并不像Serial GC、ParallelGC的在老年代…

Golang的代码压缩技术应用案例分析与研究实践

Golang的代码压缩技术应用案例分析与研究实践 一、介绍 是一种具有强大性能和便捷开发特性的编程语言&#xff0c;除了其优秀的语法和标准库外&#xff0c;它还拥有很多高级特性&#xff0c;其中之一就是代码压缩技术。本文将从常见的Golang代码压缩技术应用案例出发&#xff0…

【Uniapp-Vue3】image媒体组件属性

如果我们想要在页面上展示图片就需要使用到image标签。 这部分最重要的是图片的裁剪&#xff0c;图片的裁剪和缩放属性&#xff1a; mode 图片裁剪、缩放的模式 默认值是scaleToFill 我将用两张图片对属性进行演示&#xff0c;一张是pic1.jpg&#xff08;宽更长&#xf…

【网络协议】交换机概念与配置(第一部分)

概述 本文将探讨交换机的概念以及交换机的基础配置&#xff0c;并以此引入对 VLAN 的讨论。 文章目录 概述CSMA/CD以太网通信单播&#xff08;Unicast&#xff09;多播&#xff08;Multicast&#xff09;广播&#xff08;Broadcast&#xff09; MAC 地址以太网中的双工设置半双…

oracle位运算、左移右移、标签算法等

文章目录 位运算基础与或非同或同或应用场景 异或异或应用场景 什么是真值表 oracle基础函数创建bitor(按位或)函数bitnot(按位非)函数bitxor(按位异或)函数左移函数BITSHIFT()函数(实测不可用&#xff0c;废弃掉该方案)右移函数(略&#xff0c;有此场景吗?) 实际应用资质字典…

(五)ROS通信编程——参数服务器

前言 参数服务器在ROS中主要用于实现不同节点之间的数据共享&#xff08;P2P&#xff09;。参数服务器相当于是独立于所有节点的一个公共容器&#xff0c;可以将数据存储在该容器中&#xff0c;被不同的节点调用&#xff0c;当然不同的节点也可以往其中存储数据&#xff0c;关…

《零基础Go语言算法实战》【题目 1-18】切片的反转

《零基础Go语言算法实战》 【题目 1-18】切片的反转 请编写一个名为 reverse 的函数&#xff0c;采用整数切片并在不使用临时切片的情况下将切片反转。 【解答】 可以通过 for 循环交换切片中每个元素的值&#xff0c;使其从左向右滑动。最终&#xff0c;所有元素都将 被反转。…

Elasticsearch:搜索相关性

这里写目录标题 一、相关性的概述二、自定义评分策略1、TF-IDF算法2、BM25算法 三、自定义评分策略1、Index Boost&#xff1a;在索引层面修改相关性2、boosting&#xff1a;修改文档相关性3、negative_boost&#xff1a;降低相关性4、function_score&#xff1a;自定义评分5、…

【C++经典例题】求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a; 期待您的关注 题目描述&#xff1a; 原题链接&#xff1a; 求123...n_牛客题霸_牛客网 (nowcoder.com) 解题思路&#xff1a; …

淺談Cocos2djs逆向

前言 簡單聊一下cocos2djs手遊的逆向&#xff0c;有任何相關想法歡迎和我討論^^ 一些概念 列出一些個人認為比較有用的概念&#xff1a; Cocos遊戲的兩大開發工具分別是CocosCreator和CocosStudio&#xff0c;區別是前者是cocos2djs專用的開發工具&#xff0c;後者則是coco…

概率论与数理统计总复习

复习课本&#xff1a;中科大使用的教辅《概率论和数理统计》缪柏其、张伟平版本 目录 0.部分积分公式 1.容斥原理 2.条件概率 3.全概率公式 4.贝叶斯公式 5.独立性 6.伯努利分布&#xff08;两点分布&#xff09; 7.二项分布 8.帕斯卡分布&#xff08;负二项分布&am…

el-table自定义按钮控制扩展expand

需求&#xff1a;自定义按钮实现表格扩展内容的展开和收起&#xff0c;实现如下&#xff1a; 将type“expand”的表格列的宽度设置为width"1"&#xff0c;让该操作列不展示出来&#xff0c;然后通过ref动态调用组件的内部方法toggleRowExpansion(row, row.expanded)控…

大语言模型训练的数据集从哪里来?

继续上篇文章的内容说说大语言模型预训练的数据集从哪里来以及为什么互联网上的数据已经被耗尽这个说法并不专业&#xff0c;再谈谈大语言模型预训练数据集的优化思路。 1. GPT2使用的数据集是WebText&#xff0c;该数据集大概40GB&#xff0c;由OpenAI创建&#xff0c;主要内…

【C++习题】22.随机链表的复制

文章目录 题目&#xff1a;138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09;代码&#xff1a; 题目&#xff1a;138. 随机链表的复制 - 力扣&#xff08;LeetCode&#xff09; 链接&#x1f517;&#xff1a;138. 随机链表的复制 - 力扣&#xff08;LeetCode&…

C# 或 .NetCore 如何使用 NPOI 导出图片到 Excel 文件

今天在本文中&#xff0c;我们将尝试使用NPOI库将图像插入到 Excel 文件的特定位置。请将以下逻辑添加到您的写作方法中&#xff0c;在 Excel 文件中添加图像&#xff08;JPEG、PNG&#xff09;,我已经有一个示例 jpeg 文件 - Read-write-excel-npoi.jpg &#xff0c;我们将尝试…

Photon最新版本PUN 2.29 PREE,在无网的局域网下,无法连接自己搭建的本地服务器

1.图1为官方解答 2.就是加上这一段段代码&#xff1a;PhotonNetwork.NetworkingClient.SerializationProtocol SerializationProtocol.GpBinaryV16; 完美解决 unity 商店最新PUN 2 插件 不能连接 &#xff08;环境为&#xff1a;本地局域网 无外网情况 &#xff09; …

Python 爬虫验证码识别

在我们进行爬虫的过程中&#xff0c;经常会碰到有些网站会时不时弹出来验证码识别。我们该如何解决呢&#xff1f;这里分享 2 种我尝试过的方法。 0.验证码示例 1.OpenCV pytesseract 使用 Python 中的 OpenCV 库进行图像预处理&#xff08;边缘保留滤波、灰度化、二值化、…

【Rust自学】10.7. 生命周期 Pt.3:输入输出生命周期与3规则

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 10.7.1. 深入理解生命周期 1.指定生命周期参数的方式依赖于函数所做的事情 以上一篇文章的代码为例子&#xff1a; fn longest<a&g…

创建并配置华为云虚拟私有云

目录 私有云 创建虚拟私有云 私有云 私有云是一种云计算模式&#xff0c;它将云服务部署在企业或组织内部的私有基础设施上&#xff0c;仅供该企业或组织内部使用&#xff0c;不对外提供服务.私有云的主要特点包括&#xff1a; 私密性&#xff1a;私有云的资源&#xff08;如…

子父组件传值

Angular 2 及以上版本中的父子组件通信方式 在 Angular 2 及以上版本中&#xff0c;父子组件通信主要通过以下几种方式实现&#xff1a; 一、使用Input()进行父向子通信 父组件通过属性绑定的方式将数据传递给子组件&#xff0c;子组件使用Input()装饰器来接收这些数据。 二…