分治法

许多算法在结构上是递归的, 为了解决问题,往往一次或多次调用其自身(递归)解决相关的子问题

分治法的思想

讲原问题分为几个规模较小但类似于原问题的子问题, 再递归地求解这些问题, 然后再合并这些子问题的解来建立原问题的解(最优子结构)

分治模每层递归的三个步骤

分解原问题为若干子问题, 这些子问题是原问题的较小的实例

解决这些子问题, 递归地求解各子问题, 若子问题规模足够小, 则直接求解(递归结束)

合并这些子问题的解成原问题的解

 

基于分治的排序算法

 

快速排序

算法流程

1) 随便找一个数组中的"值" $x$ 作为分界点, 如 $a[l], a[r], a[l + r >> 1]$ (值越靠近中位数越好)

2) 根据 $x$ 的值来调整区间,最终使得 $x$ 左边的数都 $\leq x$, 而在 $x$ 右边的数都 $\geq x$

3) 递归得处理左右两端(经过步骤 $2$ 后数组已经相对有序, 左半边的最大值 $\leq$ 右半边的最小值)

void quick_sort(int l, int r) {     //对l~r进行排序
    if (l >= r) return;
    int x = a[l + r >> 1];          //确定分界点
    int i = l - 1, j = r + 1;       //确定双指针处理每轮的调整区间
    while (i < j) {
        do i++; while (a[i] < x);    //找到第一个大于等于分界点的值
        do j--; while (a[j] > x);    //找到第一个小于等于分界点的值
        if (i < j) swap(a[i], a[j]); //交换刚刚找出的两个不符合顺序的值, 一轮结束左半边数都小于x, 右半边数都大于等于x
    }
    quick_sort(l, j), quick_sort(j + 1, r);    //递归处理左右两边
}

寻找第k大的数

基于快速排序的思想, 但只要每次递归寻找一边即可, O(n)

void quick_search(int l, int r) {
    if (l >= r) return;
    int x = a[l + r >> 1];      //分界值
    int i = l - 1, j = r + 1;
    while (i < j) {
        do i++; while (a[i] < x);
        do j--; while (a[j] > x);
        if (i < j) swap(a[i], a[j]);
    }
    if (j >= k - 1) quick_search(l, j);  //要找的数在j的左边
    else quick_search(j + 1, r);        //右边
}

 

归并排序

算法流程

1) 递归  以下标的中心点来划分区间成左右两边, 然后先递归排序左右两边

2) 合并  由于返回后左右两端各自有序, 这步需要将两个各自有序的序列合二为一

void merge_sort(int l, int r) {
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);      //先进行递归, 划分出小区间
    int i = l, j = mid + 1, k = 0;      //l~mid 和 mid+1~r 两个区间各自有序(元素可能只有1个)
    //另开一个辅助数组来归并两个数组
    while (i <= mid && j <= r) {         //分别扫描比较两个数组的头, 拿出较小的
        if (a[i] <= a[j]) fz[k++] = a[i++];
        else fz[k++] = a[j++];
    }
    //一个数组用完而另一个数组还有
    while (i <= mid) fz[k++] = a[i++];
    while (j <= r) fz[k++] = a[j++];
    //最后一步将辅助数组中存储的a[l~r]一段的有序子数组回填
    for (int i = l, j = 0; i <= r; i++, j++) a[i] = fz[j];
}

求逆序对的数量

在进行归并排序的时候可以顺便求出序列的逆序对数量

void merge_sort(int l, int r) {
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) fz[k++] = a[i++];
        else {      //只有这种情况会出现逆序对
            ans += mid - i + 1; //j 和 i~n 都会产生一个逆序对
            fz[k++] = a[j++];
        }
    }
    while (i <= mid) fz[k++] = a[i++];
    while (j <= r) fz[k++] = a[j++];
    for (int i = l, j = 0; i <= r; i++, j++) a[i] = fz[j];
}

 

posted @ 2020-10-23 22:58  yikanji  阅读(69)  评论(0)    收藏  举报