基础算法 快速排序、归并排序、二分答案

基础算法 快速排序、归并排序、二分答案

一、快速排序


步骤:
  1. 确定分界点:q[l] q[r] q[(l + r) / 2] 随机

  2. 调整区间,从小到大为例:|------ \(\le x\)--|----------------\(\ge x\)---------------|

  3. 递归处理左右两段

调整划分区间的几种方法:
  1. 简单方法(0),额外开辟两个空间 a[] b[]\(\le x\) 的放a[] \(\ge x\)b[],最后合并回q[]
  2. 方法(1),利用两个双指针i ji 向右移动直到 q[i] \(\ge x\),j 向左一移动直到 q[j] \(\le x\),然后swap(q[i], q[j]),重复以上操作直到 i j相遇。说明:任何时刻,i 左边的数都小与x,j 右边的数都大于x。
  3. 方法(2),利用两个双指针,i指向q[l],此时q[l] 空着,j从右向左移动,直到q[r] \(\le x\)q[i++] = q[j],然后 i开始从左向右移动,重复以上操作,直到i == j ,最后q[i] = x
时间复杂度:
  1. 期望左右划分为\(\frac{n}{2}\) ,则一共用n除以2,\(\log _2n\)次,所以一共有\(\log _2n\)层。
  2. 每层处理的复杂度为\(O\left(n\right)\)
  3. 则一共的复杂度为\(O\left(nlogn\right)\)
代码模板以及边界问题

方法(1):

注意边界问题,分别用 j 和 i 划分

void quick_sort(int *q, int l, int r) {
    if (l >= r) return ;
    int i = l - 1, j = r + 1, x = q[(l + r) >> 1]; // q[l]
    while (i < j) {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, j), quick_sort(q, j + 1, r); // 以j划分
}
void quick_sort(int *q, int l, int r) {
    if (l >= r) return ;
    int i = l - 1, j = r + 1, x = q[(l + r + 1) >> 1]; // q[r]
    while (i < j) {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(q, l, i - 1), quick_sort(q, i, r); // 以i划分
}

二、归并排序


步骤:

  1. 确定边界 mid = (l + r) >> 1
  2. 递归排序 left right
  3. 合并

时间复杂度:

  1. 左右划分为\(\frac{n}{x}\) ,则一共用n除以x,\(\log_x n\)次,所以一共有\(\log_x n\)层。
  2. 每层处理的复杂度为\(O\left(n\right)\)
  3. 则一共的复杂度为\(O\left(n\log_xn\right)\)

代码模板

void merge_sort(int *num, int l, int r) {
    if (l >= r) {
        return ;
    }
    int mid = (l + r) >> 1;
    merge_sort(num, l, mid);
    merge_sort(num, mid + 1, r);
    
    int k = 0, x = l, y = mid + 1;
    while (x <= mid && y <= r) {
        if (num[x] <= num[y]) temp[k++] = num[x++];
        else temp[k++] = num[y++];
    }
    while (x <= mid) temp[k++] = num[x++];
    while (y <= r) temp[k++] = num[y++];
    for (int i = l, j = 0; i <= r; i++, j++) {
        num[i] = temp[j];
    }
}

自己原来学的方法

void merge_sort(int *num, int l, int r) {
    if (r - l <= 1) {
        if (r - l == 1 && num[r] < num[l]) {
            int t = num[r];
            num[r] = num[l];
            num[l] = t;
        }
        return ;
    }
    int mid = (l + r) >> 1;
    merge_sort(num, l, mid);
    merge_sort(num, mid + 1, r);
    
    int p1 = l, p2 = mid + 1, k = 0;
    while (p1 <= mid || p2 <= r) {
        if (p2 > r || (p1 <= mid && num[p1] <= num[p2])) temp[k++] = num[p1++];
        else temp[k++] = num[p2++];
    }
    for (int i = l, j = 0; i <= r; i ++, j ++ ) num[i] = temp[j];
    return ;
}

借助库函数申请空间malloc 和 深度拷贝合并memcpy,更快一些

void merge_sort(int *num, int l, int r) {
    if (r - l <= 1) {
        if (r - l == 1 && num[r] < num[l]) {
            int t = num[r];
            num[r] = num[l];
            num[l] = t;
        }
        return ;
    }
    int mid = (l + r) >> 1;
    merge_sort(num, l, mid);
    merge_sort(num, mid + 1, r);
    
    int p1 = l, p2 = mid + 1, k = 0;
    int *temp = (int *)malloc(sizeof(int) * (r - l + 1));
    while (p1 <= mid || p2 <= r) {
        if (p2 > r || p1 <= mid && num[p1] <= num[p2]) temp[k++] = num[p1++];
        else temp[k++] = num[p2++];
    }
    memcpy(num + l, temp, sizeof(int) * (r - l + 1));
    free(temp);
    return ;
}

三、二分


整数二分

二分本质不是单调性,是删除不存在答案的区间

|--------------------|x|-|y|-----------------------------|

找到一个性质,将待求解区间一分为二

情况一:\(mid = \frac{l + r + 1}{2}\),1111111100000000,求最后一个1

if(check(mid)):

  1. true : 表明mid在1里面,答案区间在:[mid ~ r],令l = mid
  2. flase: 表明mid在0里面,答案区间在:[l ~ mid - 1],令r = mid - 1

问题:为什么\(mid = \frac{l + r + 1}{2}\) ,要额外加1

解释:由于C/C++默认向下取整,当 l = r - 1,令l = mid 会进入死循环 l = l

情况二: \(mid = \frac{l + r}{2}\),000000001111111,求第一个1

if(check(mid)):

  1. true : 表明mid在1里面,答案区间在:[l ~ mid],令r = mid
  2. flase: 表明mid在0里面,答案区间在:[mid + 1, r],令l = mid + 1

再次强调:二分本质不是单调性,是删除不存在答案的区间

代码模板:

学习地址:常用代码模板1——基础算法 - AcWing

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r) {
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r) {
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

浮点数二分

例:求一个数的平方根

double bin_sqrt(double x) {
    double l = 0, r = max(1.0, x);
    while (r - l > 1e-8) {
		double mid = (l + r) / 2;
        if (mid * mid > x) r = mid;
        else l = mid;
    }
    return l;
}

三、简单练习


快速排序:

  1. AcWing 785. 快速排序 - AcWing
  2. 786. 第k个数 - AcWing

归并排序:

  1. 787. 归并排序 - AcWing

  2. 788. 逆序对的数量 - AcWing

二分:

  1. 789. 数的范围 - AcWing

  2. 790. 数的三次方根 - AcWing

更多等待更新。。。。。。

posted @ 2022-01-12 00:30  关键概念  阅读(76)  评论(0编辑  收藏  举报