本节内容为部分排序和查找算法,数据结构课讲过,不过当时期末时间紧,没去听课,虽然PPT看懂了,但是从没有自己完整敲过,现在原理也都忘了,学一遍发现确实有些地方容易写错。
快速排序
核心思路
主要思想:分治
- 确定分界点:
q[l]或者q[(l+r)/2]或者q[r]; - 调整范围:目标是左边的数都小于等于x,右边的数都大于等于x(分界点不一定是x);
 - 递归处理左右两端
 
第2步实现
暴力算法
- 开两个数组a[ ]和b[ ];
 - 把q[ ]中从l到r遍历:
 
- q[i]<=x 放入a;
 - q[i]>x 放入b;
 
- 把a[ ]和b[ ]放入q[ ]中
 
常用思路
- 设定两个指针,从左右两端开始:
 
- 左指针从左向右寻找第一个大于等于基准的元素
 - 右指针从右向左寻找第一个小于等于基准的元素
 
- 找到之后交换两个元素
 - 持续移动左右指针并交换元素,直到两个指针相遇,此时结束
 
代码
自己手写了一遍:
void quicksort(int a[], int l, int r){
    if(l>=r) return;
    int cha = a[l];
    int x = l+1, y = r;
    while(x<=y){
        while(x<=y){
            if(a[x]>cha) break;
            x++;
        }
        while(x<=y){
            if(a[y]<cha) break;
            y--;
        }
        if(x<y){
            swap(a[x],a[y]);
        }
    }
    swap(a[l],a[y]);
    if(l<y)quicksort(a,l,y-1);
    if(y+1<r)quicksort(a,y+1,r);
}
老师给的代码:
void quicksort(int q[], int l, int r){
    if(l>=r) return;
    int x = q[l], i = l - 1, j = r + 1; //此处-1和+1都是为了后面do while简洁
    while(i < j){
        do i++; while (q[i] < x);
        do j--; while (q[j] > x);
        if(i<j) swap(q[i],q[j]);
    }
    quicksort(q,l,j);//取j时前面不能是x = q[r]
    quicksort(q,j+1,r);
    //quicksort(q,l,i-1); 取i时前面不能是x = q[l]
    //quicksort(q,i,r);
}
STL的快速排序函数
#include <algorithm>
std::sort(arr, arr + n);
默认从小到大,范围为数组arr到arr+n-1
重构:指定cmp排序函数(以结构体为例):
#include <iostream>
#include <algorithm>
using namespace std;
struct Person {
    string name;
    int age;
};
bool cmp(const Person& a, const Person& b) {
    return a.age < b.age; // 按年龄升序
}
//这里使用(const Person& a, const Person& b)是因为节省空间,使 用引用类型,避免再复制一份变量,使用const是防止函数修改原来的值,const是保护措施。
int main() {
    Person arr[] = { {"Alice", 30}, {"Bob", 25}, {"Charlie", 35} };
    int n = sizeof(arr) / sizeof(arr[0]);
    sort(arr, arr + n, cmp);
    for (int i = 0; i < n; i++) 
        cout << arr[i].name << " " << arr[i].age << endl;
    // 输出:
    // Bob 25
    // Alice 30
    // Charlie 35
    return 0;
}
归并排序
主要思路
主要思想:分治
- 确定分界点: $mid = \frac{l + r}{2}$
 - 递归排序左边和右边
 - 合并两个数组为一个数组(归并)
 
代码
int linshi[100000];
void binggui(int a[], int l, int r){
    if(l>=r) return;
    int mid = (l+r)/2;
    binggui(a,l,mid);
    binggui(a,mid+1,r);
    int i = l, j = mid + 1;
    int now = l;
    while(i<=mid&&j<=r){
        if(a[i]>a[j]) linshi[now++] = a[j++];
        else linshi[now++] = a[i++];
    }
    while(i<=mid) linshi[now++] = a[i++];
    while(j<=r) linshi[now++] = a[j++];
    for(int t = l; t <= r; t++) a[t] = linshi[t];
}
整数二分
有单调性一定可以二分,没有单调性也有可能二分。

步骤
- 选取一个点 $mid = \frac{l + r + 1}{2}$ ($+1$ 是为了上取整,为了下面 $True$ 时不会出现区间变成 $[mid,r] = [l,r]$ 而产生循环)
 - $if(check(mid))$ (检查是否属于左半边,比如检查 $mid \le cha$ )
 
- $Ture$ -> 改变 $l=mid$
 - $False$ -> 改变 $r = mid - 1$
 
此时取得的是等值的最后一个
或者
- 选取 $mid = \frac{l+r}{2}$ (此处同理,默认下取整,为了不会产生 $[l,r]$)
 - $if(check(mid))$ (检查是否属于右半边,比如检查 $mid \ge cha$ )
 
- $True$ -> 改变 $r = mid$
 - $False$ -> 改变 $l = mid + 1$
 
此时取得的是等值的第一个
代码
int bsearch(int a[], int l, int r){
    while(l < r){
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
int bsearch(int a[], int l, int r){
    while(l < r){
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
自带bsearch函数
必须要先引入cstdlib库。
定义
void* bsearch(
    const void* key,     // 你要找的东西的地址
    const void* base,    // 排好序的数组的首地址
    size_t nmemb,        // 数组里有几个元素
    size_t size,         // 每个元素多大(以字节为单位)
    int (*compar)(const void*, const void*)  // 比较函数的地址
);
举例
#include <iostream>
#include <cstdlib>  // bsearch在这里面
// 比较函数
int compare(const void* a, const void* b) {
    int va = *(const int*)a;
    int vb = *(const int*)b;
    if (va < vb) return -1;
    else if (va > vb) return 1;
    else return 0;
}
int main() {
    int arr[] = {1, 3, 5, 7, 9};  // 有序数组
    int n = sizeof(arr) / sizeof(arr[0]);
    int key = 5;
    // 用 bsearch 查找 key
    int* found = (int*) bsearch(&key, arr, n, sizeof(int), compare);
    if (found != nullptr) {
        std::cout << "找到了,值是: " << *found << std::endl;
    } else {
        std::cout << "没找到" << std::endl;
    }
    return 0;
}
浮点数二分
没有整除的考虑,所以简便一些。
举例:查找某个数的开根值。
#include <iostream>
using namespace std;
int main(){
    double x;
    cin >> x;
    double l = 0, r = x;
    while(r- l> 1e-8){
        double mid = (l + r)/2;
        if(mid*mid>=x) r = mid;
        else l = mid;
    }
    printf("%lf\n",l);
    return 0;
}
来自北京