二分查找

二分查找,也称折半搜索,是用来在一个有序数组中查找某一元素的算法。

二分思想

  • 二分查找要求搜索的区间是有序的;
  • 二分查找是通过不断缩小解可能存在的范围,从而求得问题最优解的方法;
  • 二分查找每一次检查范围的中点,从而确定答案的位置在中点的左边还是右边,这样每次就能把问题的可能存在范围缩小一半;

查找思路

对于有序数列(从小到大),设定左端 L(最小元素下标)和右端 R(最大元素下标)

当满足条件 L<=R 时,求中点 mid,将中点元素的值与所要查找的值比较。

  • 如果中间元素刚好是要找的,就结束搜索过程;

  • 如果中点元素值比所要查找元素大,那么右侧的只会更大,则应找前半段,所以 R=mid-1。

  • 否则应找后半段 L = mid + 1,直到找到为止。
  • 若 L > R,则说明找不到。

代码模板

1.最基本的二分查找算法

int ans=-1;//保存答案
while(l<=r){
    int mid=(l+r)/2;
    if(a[mid]==x){
        ans=mid;//更新答案
        break;
    }else if(a[mid]>x){//前半段
        r=mid-1;
    }else if(a[mid]<x){//后半段
        l=mid+1;
    }
}

2. 寻找左侧边界的二分查找

例如 1 2 3 3 3 4 4 5 8 10

int ans=-1;//保存答案
while(l<=r){
    int mid=(l+r)/2;
    if(a[mid]==x){
        ans=mid;//更新答案
        r=mid-1;//继续向左查找
    }else if(a[mid]>x){//前半段
        r=mid-1;
    }else if(a[mid]<x){//后半段
        l=mid+1;
    }
}

3.寻找右侧边界的二分查找

例如 1 2 3 3 3 4 4 5 8 10

int ans=-1;//保存答案
while(l<=r){
    int mid=(l+r)/2;
    if(a[mid]==x){
        ans=mid;//更新答案
        l=mid+1;//继续向右查找
    }else if(a[mid]>x){//前半段
        r=mid-1;// 搜索区间变为 [l, mid-1]
    }else if(a[mid]<x){//后半段
        l=mid+1;//搜索区间变为 [mid+1, r]
    }
}

例题:升序数组,各元素不同,查找某元素。

如果该元素存在:输出该元素的下标;

如果不存在该元素,输出 -1。

在数字序列中 1 3 4 6 7 9 11 16 23 45,查找 11 的位置:

img

m = (L + R) / 2
L = m + 1

img

m = (L + R) / 2
R = m - 1

img

m = (L + R) / 2
L = m + 1

img

m = (L + R) / 2

找到 11 的位置是 7。

查找 10 的位置

img

m = (L + R) / 2
R = m - 1

img

m = (L + R) / 2
L = m + 1

img

m = (L + R) / 2
R = m - 1

不再满足 L <= R,查找结束,没有找到。

在 n 个元素的有序序列中进行二分查找:

  • 最大比较次数:[log2n]+1
  • 时间复杂度:O(logn)

二分答案

解题的时候往往会考虑枚举答案然后检验枚举的值是否正确。若满足单调性,则满足使用二分法的条件,把枚举换成二分,就是二分答案。

使用场景:要求我们求出某变量在满足某种条件下的最大值或最小值

一般看到所谓的最大值最小化或者最小值最大化,通常用二分法就可以很好地解决。

使用前提:

  1. 答案在一个固定的区间内
  2. 可能查找一个符合条件的值不是很容易,但给定一个值你可以很快的判断它是不是符合要求
  3. 可行解对于区间要符合单调性

最小值最大化

//验证答案是否正确 
bool check(int x){
    //根据具体问题写验证逻辑 
}
int main() {
    ....//输入数据 
    int l=1,r=n,mid,ans=-1;
    while(l<=r){
        mid=(l+r)/2;
        if(check(mid)){ 
            ans=mid;
            l=mid+1;//调整上限,看能不能更大,到后半段去找 
        }else{
            r=mid-1;
        }
    } 
    cout<<ans;
    return 0;
}

最大值最小化

//验证答案是否正确 
bool check(int x){
    //根据具体问题写验证逻辑 
}
int main() {
    ....//输入数据 
    int l=1,r=n,mid,ans=-1;
    while(l<=r){
        mid=(l+r)/2;
        if(check(mid)){ 
            ans=mid;
            r=mid-1;//调整下限,看能不能更小,到前半段去找 
        }else{
            l=mid+1;
        }
    } 
    cout<<ans;
    return 0;
}

快速排序

每一轮挑选一个基准元素(随机选择,编程时一般选取第一个),并让比它大或小的元素移动到基准元素的两边,把数列拆解成了两个部分。而后对这两部分分别进行快速排序。

排序流程

1、首先设定一个分界值,通过该分界值将数组分成左右两部分。

2、将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。

3、然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

4、重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

概括来说为 挖坑填数 + 分治法

img

参考代码:

#include<bits/stdc++.h>
using namespace std;
int a[10] = {0, 4, 1, 7, 9, 2, 3, 8, 5, 6};
void quickSort(int start, int end) {
    if (start < end) {//数组有多个元素进行排序
        int base = a[start];//以要进行排序数组第0个元素为base
        int left = start;//左指针
        int right = end;//右指针
        while (left < right) {
            //从右向左找,比base大,right--
            while (left< right && a[right] >= base) right--;
            //比base小,替换left所在位置的数字
            a[left] = a[right];
            //从左向右找,比base小,left++
            while (left < right && a[left] <= base) left++;
            //比base大,替换right所在位置的数字
            a[right] = a[left];
        }
        a[left] = base;//此时left=right,用base替换这个位置的数字
        quickSort(start, left - 1);//排列比base小的数字的数组
        quickSort(left + 1, end);//排列比base大的数字的数组
    }
}

int main(){
    quickSort(0, 9);
    for(int i = 0; i < 10; i++){
        cout << a[i] << ' ';
    }
    return 0;
}

时间复杂度

①理想情况下:O(nlogn)

②最坏情况下:O(N)

空间复杂度O(N)

稳定性:不稳定

归并排序

对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,逐层进行合并。

该算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程;另一个是治,它将两个有序数组合并成一个更大的有序数组。

排序流程

1、将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。 2、将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

img

参考代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[10]= {0, 4, 1, 7, 9, 2, 3, 8, 5, 6}, b[10], ans;
void mergeSort(int left,int right) {//排序left到right区间 
    int mid = (left + right) / 2;
    if(left >= right) return;//区间不存在或者只有一个元素 就不排序了 
    mergeSort(left, mid);//先递归左半部分 
    mergeSort(mid + 1, right);//再递归右半部分 
    int l = left, r = mid+1, p = 0;
    while(l <= mid && r <= right) {//在合理的区间比较 
        if(a[l] <= a[r]) {//左边小于右边 
            b[p++] = a[l++];//存左边 
        } else {
            b[p++] = a[r++];//存右边 
        }
    }
    while(l <= mid) b[p++] = a[l++];//特判左区间没有结束,直接全部拷贝到b数组 
    while(r <= right) b[p++] = a[r++];//特判右区间没有结束,直接全部拷贝到b数组 
    for(int i = left, p = 0; i <= right; i++,p++) {
        a[i] = b[p];//还给a数组(待排原数组) 
    }
}
int main() {
    mergeSort(0,9);
    for(int i=0; i<10; i++) {
        cout<<a[i]<<' ';
    }
    return 0;
}

时间复杂度O(nlogn)

空间复杂度O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。

稳定性:稳定

Copyright ©图灵之星 2024,转载需注明出处该文件修订时间: 2025-04-10 18:02:21

results matching ""

    No results matching ""