【第三课】二分查找:力扣704(基础)

学姐有事不能带我们了wuwuwu。那接下来的我就根据acwing目录学吧。

还是老样子,通过例题学习知识点。

嗯,写完了才发现有acwing二分模板也能够解决这个问题,而且好像是二分模板里很好的,我在学习学习在关于那个的博客。

如果是想看那个的止步于此吧哈哈哈等我之后更新(老实巴交)

目录

力扣704二分查找

暴力求解代码

二分查找算法

class

vector

二分查找的代码实现思路

左闭右闭写法

左闭右开的区间


力扣704二分查找

此前没有学习过二分思想的我。自然是又用了暴力解法,思想即利用for循环遍历数组元素,使用if语句判断直到找到符合条件的即输出,倘若遍历完整个数组还是没有符合条件的,就返回-1

暴力求解代码

#include<iostream>
using namespace std;
const int N=10000;
int nums[N];
int target,n;
void find(int target,int nums[],int n)
{
    int k=0;
    for(int i=0;i<n;i++)
    {
        if(nums[i]==target)
        {
            k=1;
            cout<<i<<endl;//为了避免有多个目标元素时多次输出下标 添加break
            break;
        }
    }
    if(k==0)cout<<"-1"<<endl;
}
int main()
{
    cin>>n;
    for(int j=0;j<n;j++)
    {
        scanf("%d",&nums[j]);
    }
    printf("target=");
    scanf("%d",&target);
    find(target,nums,n);
    return 0;
}

由于需要遍历整个数组,所以时间复杂度是O(n).为了降级时间复杂度,提高代码效率,出现了二分算法。

二分查找算法

二分查找算法思想:通过改变查找区间,不断更新mid的值,根据mid与目标值的大小关系,选择搜索区间的左半部分或右半部分继续搜索,直到其等于目标值或者确定目标值不存在。

这样的好处是,由于每次折中找到mid的值,将查找区间缩小为原区间的一半, 时间复杂度变为O(log n)。计算如下

看到这个我还是挺不知所措的 ,感觉非常陌生(毕竟刚开始学习,还没见过各种平台的模式) 

由于leedcode上答题是只写核心代码,系统会自动补全主函数,所以我们不必写出主函数(但我还是会写全的 怕我不懂)。

这道题要求是使用class在类中写出实现二分的函数,额,这是c++中的一个关键字,用来定义一个类。嗯,我想应该不用了解太多(只对应比赛的话)应该可能。嗯,还是之前说的,见得多了就习惯了,就熟悉接受这样的存在了,作为小白 不用抗拒或者担心这是啥呀都没见过。我现在还并不能非常清楚的说出他的所有功能。下面就简单介绍一下这道题里会用到的吧。

看过一些教程还有其他同学的看法之后,我发现class类的功能和结构体有一些相似之处,但当然也有不同,所以我将通过比较这两个概念来帮助理解 类 是个什么东西。

class

类(class)和结构体(struct)都是一种复合数据类型,它们都可以用来封装多个相关的数据成员。但是类的功能更加强大,支持面向对象编程的功能,作为小白这些现在先不做过多了解了。

class书写格式

class ClassName {
    // 成员变量
    DataType variableName;

    // 成员函数
    ReturnType functionName(Parameters) {
        // 函数体
    }
};

/* 

//关键字 类名称{
public://访问权限公共
    //定义成员变量   和正常的定义变量格式一样
    //可定义成员函数 和正常的定义函数格式一样

};//注意末尾分号

*/

在C++语言中,类和结构体的主要区别在于默认的访问控制。在类中,成员变量和成员函数默认都是私有的(private),只能在类的内部访问;而在结构体中,成员变量和成员函数默认都是公有的(public),可以在类的外部访问。所以我们这道题在定义类中的成员变量和成员函数之前需要在前面写上public: 这点理解一下哈。

另外提一下c语言中结构体中不能定义函数,但是c++中结构体可以定义函数。

那class的基础知识就先了解到这里,先保证做题够用就行。


另外我们看到给出的代码是让写核心函数即可,题中已经帮我们写好了类,创建好了search函数及其参数。

 int search(vector<int>& nums, int target)

看到它给的参数,类型为vector<int>,得,又是个陌生的东西,看到他后面跟的nums,可以猜出来他是表示数组类型的。

那么下面补充一下vector的一些基础知识

vector

vector是C++标准模板库(STL)中的一个容器,它提供了一种动态数组的实现。

他在使用时,首先要记得引头文件 #include<vector>,其次是他的定义方式是vector<数据类型>。

使用它的好处是,第一,它可以动态分配内存空间,不需要向定义数组一样提前知道数组中元素的个数,更灵活。第二,它带有很多的函数比较方便,比如,push_back()表示在vector末尾插入数据,insert()表示在vector的任意位置插入数据,erase()表示删除某个数据,size()表示计算vector中的元素个数。


好,有了上面的知识补充,下面开始思考

二分查找的代码实现思路

左闭右闭写法

再回顾一下算法思想

通过改变查找区间,不断更新mid的值,根据mid与目标值的大小关系,选择搜索区间的左半部分或右半部分继续搜索,直到其等于目标值或者确定目标值不存在。

所以首先我们要表示出查找区间,定义两个变量,左边界为0,右边界可以使用size()函数得出,由于数组下标从0开始,所以有边界应表示为size()-1

接下来要通过while循环,当还存在查找范围时,就要不断进行条件判断,边界修改。所以如何表示出是否存在查找范围呢?我们可以表示为left <= right,这样表示查找范围不为空的时候,就执行操作。这里是可以取等的,当left=right时表示只剩下一个元素,也就是mid=left=right,仍然满足条件,所以可以取等。

然后实现循环内的操作,需要先创建一个变量表示中间值,其初始值应为(left+right)/2,对吧?

这里提一下找中间值的写法,有三种

mid=(left+right)/2=left+(right-left)/2=left+right>>1(这种写法>>表示二进制移位,移动一位表示/2,可以理解撒,且在C/C++中其优先级低于+ 所以不必加() )

推荐写第二种 left+(right-left)/2,这样可以避免当左右边界值太大时发生整型溢出。

得出中间值之后,将下标中间值在数组中所对的值与目标值的大小关系比较,实现不同情况下的边界修改。由于这里边界的修改不难理解,就不在赘述了。

c++代码如下

#include <iostream>
#include <vector>
using namespace std;

class solution
{
public:
    int find(vector<int> &nums, int target)//&是c++中的引用符,这里简单理解为,添加了&,表示直接对主函数中的该数组进行操作
    {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right) // 循环出口是 查找范围是否为空
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target)
            {
                right = mid - 1;//由于mid值已经判断过不是我们想要的
            }
            else if (nums[mid] < target)
            {
                left = mid + 1;
            }
            else
            {
                return mid;//一直缩小区间范围,直到mid=target
            }
        }
        return -1;
    }
};
int main()
{
    solution s1;
    int n;
    cin >> n;
    // 输入所需查找数组
    vector<int> nums(n);
    for (int i = 0; i < n; i++)
    {
        cin >> nums[i];
    }
    // 输入target
    int target;
    cin >> target;
    int ans = s1.find(nums, target);//通过.索引到类中的函数
    cout << ans << endl;
    return 0;
}

还有另一种写法,是把初始区间右边界设为size(),这样表达的意思是,

左闭右开的区间

想一下,如果这样改,上面的代码哪里需要修改呢?

首先是while循环出口,应该改为left<right,这是由于,right已经不是区间内的点了,取等的时候,所取到的值已经不在区间范围内了。

第二处,就是边界的更改,当>目标值时,说明应该向左找,此时mid已经不必在区间内了,直接将right=mid,当<目标值,说明应该向右找,left=mid+1

代码如下

#include <iostream>
#include <vector>
using namespace std;
class solution
{
public:
    int find(vector<int> &nums, int target)
    {
        int left = 0;
        int right = nums.size();
        while (left < right) // 左闭右开 right本身并不在范围内
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target)
            {
                return mid;
            }
            else if (nums[mid] > target)
            {
                right = mid; // 由于right本身并不在范围内,所以如果再-1,就会导致范围内少了mid-1这个数
            }
            else
            {
                left = mid + 1;
            }
        }
        return -1; // 扫描完整个范围都没有return 说明该数组中没有这个数
    }
};
int main()
{
    solution s1;
    int n;
    cin >> n;
    vector<int> nums(n);
    for (int i = 0; i < n; i++)
    {
        cin >> nums[i];
    }
    int target;
    cin >> target;
    int ans = s1.find(nums, target);
    cout << ans << endl;
    return 0;
}

这两种写法都可以,拓展思路而已,喜欢哪个用哪个。

嗯,到这里这道题就写完了。

等我都理清楚了,再定位一下这道题和二分模板之间的关系吧,现在有点懵。

如果有问题欢迎指出,非常感谢!

也欢迎交流建议奥。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值