Advertisement

【acwing算法基础课笔记】01-基础算法-分治

阅读量:

快速排序 - 分治

1、分界点 `q[1]、q[len/2]、q[r]随机
2、调整区间,大于x一边小于x一边
3、递归调整左右两段

边界问题

必须先j++后i++

一、中间没有需要排序的元素,ij相邻
j先走向左停在i上,i是偏小的一方,与l互换正好一个在中间一个在小的一边

二、中间是一个偏大的元素,ij在这元素两边
j先走会跳过大的,停在偏小i的上,同一

三,中间是一个偏小的元素,j停在中间元素上,i再向右走
大的跳过到小的,同一

https://leetcode.cn/leetbook/read/illustration-of-algorithm/p57uhr/

复制代码
    int partition(vector<int>& nums, int l, int r) {
    // 以 nums[l] 作为基准数
    int i = l, j = r;
    while (i < j) {
        while (i < j && nums[j] >= nums[l]) j--;
        while (i < j && nums[i] <= nums[l]) i++;
        swap(nums[i], nums[j]);
    }
    swap(nums[i], nums[l]);
    return i;
    }
    
    void quickSort(vector<int>& nums, int l, int r) {
    // 子数组长度为 1 时终止递归
    if (l >= r) return;
    // 哨兵划分操作
    int i = partition(nums, l, r);
    // 递归左(右)子数组执行哨兵划分
    quickSort(nums, l, i - 1);
    quickSort(nums, i + 1, r);
    }
    
    // 调用
    vector<int> nums = { 4, 1, 3, 2, 5, 1 };
    quickSort(nums, 0, nums.size() - 1);
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/4abUQRsxu67IKqP8t2Wvnh50YBgS.png)
复制代码
    public void SortArr(int[] arr, int left, int right) {
        // 有请快速排序选手登场
        if (left >= right) { return; } // 数组只有一个元素或者没有,那么不需继续递归
        int indexL = left;
        int indexR = right;
        int sentry = arr[left];
        int swap;
        while (indexL < indexR) {
            while (indexL < indexR && arr[indexR] >= sentry) { indexR--; }
            while (indexL < indexR && arr[indexL] <= sentry) { indexL++; }
            swap = arr[indexL]; arr[indexL] = arr[indexR]; arr[indexR] = swap;
            // 两个指针靠近,顺便保证自己身后没有比哨兵大/小的元素
        }
        // 把哨兵搬到两个指针的位置,这样保证了哨兵两边元素都比它大/小
        swap = arr[left]; arr[left] = arr[indexL]; arr[indexL] = swap;
        // 对数组进行哨兵划分
        SortArr(arr, left, indexL - 1);
        SortArr(arr, indexL + 1, right);
        // 然后继续对子数组做哨兵划分(哨兵两边的数组,不包括它本身),分治
    }
    /*
    两个问题:
    1.为什么比较指针和哨兵要用>=或<=,而不用>或<?
    2.为什么左指针右移和右指针左移顺序不能互换?
    解:1.如果不忽略相等情况,那么就可能L和R数值都等于哨兵,恭喜你,交换后再交换,死循环,
    实际上,与哨兵相等的元素无论在子数组的哪边都不影响结果,所以直接忽略没啥问题。
    2.快速排序必须保证最后哨兵左边的元素小于等于之,在两个指针靠近的时候,左指针的左边都可以满足这个条件;
    但是,当两个指针相撞的时候就不好说了;如果我们在循环中让左指针先走,有如下情况会发生:
    左指针撞在一个大于哨兵的数上,结果右指针撞上了左指针,这个大数被交换到了哨兵左边,导致排序错误。
    如果我们让右指针先走,就可以避免这个问题:左指针要么撞上更大的数,做交换,要么撞上右指针,右指针会停下来肯定是因为这个数比哨兵小,所以被交换到哨兵左边的数仍然小于哨兵,条件满足。
    */
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/Cqy9WNs5deri0KJwhfPL432ZRTO7.png)

归并排序 - 分治

1、确定分界点 mid = (1+r)/2
2、递归排序 left、right
3、归并 - 合二为一

复制代码
    #include <iostream>
    
    using namespace std;
    
    const int N = 36;
    int q[N],tmp[N];
    
    // 归并排序    类似后序遍历
    void merge_sort(int q[],int left,int right){
    if(left>=right) return;
    int mid = (left + right) >> 1; // 确定分界点
    merge_sort(q,left,mid); // 左子数组排序
    merge_sort(q,mid+1,right); // 右子数组排序
    
    // 合并两子数组到tmp,k是tmp的指针,i,j指向左/右子数组头部
    int k=0,i = left, j = mid +1 ; 
    while(i<=mid && j <=right){
        if(q[i]<=q[j])tmp[k++]=q[i++];
        else tmp[k++]=q[j++];
    }
    // 复制没到头的偏大一坨
    while(i <= mid)tmp[k++]=q[i++];
    while(j <= right)tmp[k++]=q[j++];
    
    // 把合并后的tmp 复制到 原数组
    for(i=left,j=0;i<right;++i,++j)
        q[i]=tmp[j];
    }
    
    int main(){
    for(int i=N-1;i>=0;--i){
        q[i]=i;
    }
    merge_sort(q,0,N-1);
    for(int i=0;i<N;++i){
        cout << q[i] << endl;
    }
    
    }
    
    
    C
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/hcqt5xLICkyKaj1ZmWblRuAzQVU2.png)

空间O(n)

时间O(logN) 平均

整数二分 - 不常考

为什么+1,为真可能不会改变检查范围,为假一定会消减一部分,所以尽量让mid往假的那边靠 ,防止死循环

也就是说,+1靠近假,就+,不+靠近假,就不加

整数二分

复制代码
    #include <iostream>
    using namespace std;
     
    const int N = 100010;
     
    int n,m;
    int q[N];
     
    int main()
    {
    //n数组长度 m询问长度
    scanf("%d%d",&n,&m);
    //读入整个数组
    for(int i = 0;i < n;i++ ) scanf("%d",&q[i]);
    //一共有m个询问,对于每一个询问分别来做
    while(m-- )
    {
        //x是要查找的值
        int x;
        scanf("%d",&x);
        //定义左右边界
        int l = 0,r = n - 1;
        //二分找左边的边界点:找起始坐标,找第一个大于等于x的数
        while(l < r)
        {
            //中间值:是否需要补上加 1 可以后面再考虑
            int mid = l + r >> 1;
            //r = mid 使用的是第二个模板 不需要补上加 1 
            if(q[mid] >= x) r = mid;
            else l = mid + 1;
        } 
        //当找到满足大于等于x的第一个数不等于x时,说明数组中不存在x
        if(q[l] != x) cout << "-1 -1" << endl;
        else
        {
            //输出 l 和 r 是一样的 循环结束后 l 和 r 是相等的
            cout << l << ' ';
            int l = 0,r = n - 1;
            //二分找右边的边界点:找终止坐标,找最后一个小于等于x的数
            while(l < r)
            {
                int mid = l + r + 1 >> 1;
                //l = mid 使用第一个模板需要补上加 1
                if(q[mid] <= x) l = mid;
                else r = mid - 1;
            }
            cout << l << endl;
        }
    }
    return 0;
    }
    
    
    C
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/ZWmcOhgMLdD5YwNsHR3Aku4pSl8o.png)

浮点数二分

复制代码
    /*
    浮点数二分 - 求一个数的平方根
    */
    #include <iostream>
    
    using namespace std;
    
    int main(){
    double x;
    cin >> x;
    double l =0,r=x;
    while(r-l > 1e-6){
        double mid = (l+r)/2;
        if(mid*mid>x)r=mid;
        else l=mid;
    }
    
    cout << l << endl;
    return 0;
    }
    
    
    c
    
    
![](https://ad.itadn.com/c/weblog/blog-img/images/2025-08-19/vOj2VrBP6UH1aoQ4A5hYXWlL8CSI.png)

全部评论 (0)

还没有任何评论哟~