【算法】分治的思路

一、算法理解

分治,字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

如果想用分治,需要下面的几个条件:

1)该问题的规模缩小到一定的程度就可以容易地解决;

2)该问题可以分解为若干个 规模较小的相同子问题

3)利用该问题分解出的子问题的解可以 合并为该问题的解

4)该问题所分解出的各个子问题是 相互独立 的,即子问题之间不包含公共的子问题。(非必需)


具体场景分析

  • 第一条是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;
  • 第二条是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;
  • 第三条是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备 第三条特征,则可以考虑用贪心法或动态规划法;
  • 第四条不是必要条件,但涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地 解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。(这一点是分治算法跟动态规划的明显区别。 可以说,动态规划法的实质是: 分治算法思想 + 解决子问题冗余情况。)

分治法在每一层递归上都有三个步骤:

  • step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
  • step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
  • step3 合并:将各个子问题的解合并为原问题的解。

【分治和动态规划的区别】:

  1. 分治策略分解的子问题是相互独立且与原问题相同的。很多时候原问题分解成两个子问题多数情况下也是行之有效的。
  2. 动态规划:也是把一个复杂的问题分成若干个子问题,但与分治法不同,适用动态规划的问题分解后的子问题通常是不互相独立的。这时候,若还用分治的话,会因为子问题太多以至于最后解决问题需要耗费指数级的时间。

​ 一般动态规划步骤分为:

​ 1)刻画最优解的结构特征;

​ 2)递归地定义最优值;

​ 3)以底向上的方法计算最优值;

​ 4)构造最优解;

四、分治使用案例

1) 二分查找

不赘述。

2)归并排序

A. 自顶而下的归并排序

自顶向下的归并排序就是把数组元素不断的二分,直到子数组的元素个数为1,然后将两个有序的子序列合并成一个新的有序序列,两个新的有序序列又可以合并成另一个新的有序序列,以此类推,直到合并成一个有序的数 组。
image

代码实现参考:

public class Solution {
    private int[] tmpArray;

    private void sort(int[] data) {
        // 创建好一个临时数组,避免排序过程中重复创建数组
        tmpArray = new int[data.length];

        mergeSort(data, 0, data.length - 1);
    }

    /* 方式一:自顶而下的归并排序
     */
    private void mergeSort(int[] data, int start, int end) {
        // 分解到最子序列是1个元素,不需要排序,直接返回
        if (start == end) {
            return;
        }

        // 需要排序序列拆分成2个序列。两个子序列分别排序后,合并。
        int mid = start + (end - start) / 2;
        mergeSort(data, start, mid);
        mergeSort(data, mid + 1, end);
        // 合并排序后的两个序列
        merge(data, start, mid, end);
    }

    /*
     * 合并两个有序序列。
     * 分别用lfe指向左序列索引,right指向右序列索引,比较大小,放入排序后目标序列
     */
    private void merge(int[] data, int start, int mid, int end) {
        System.arraycopy(data, start, tmpArray, start, end - start + 1);

        int left = start;
        int right = mid + 1;
        for (int i = start; i <= end; i++) {
            if (left > mid) {
                data[i] = tmpArray[right++];
            } else if (right > end) {
                data[i] = tmpArray[left++];
            } else if (tmpArray[left] < tmpArray[right]) {
                data[i] = tmpArray[left++];
            } else {
                data[i] = tmpArray[right++];
            }
        }
    }
}

B.自底而上的归并排序

自底向上的归并排序算法的思想就是把数组从头开始,临近的两个元素归并成两两有序的序列,两两有序的序列再 归并成四个有序的序列,以此类推,直到整个数组有序。需要注意的是数组按照归并长度划分,最后一个子数 组可能不满足长度要求,这个情况需要特殊处理。自顶向下的归并排序一般用递归来实现,而自底向上的一般用循 环来实现。
image

代码参考:

public class MergeSort {
    private int[] tmpArray;

    private void sort(int[] data) {
        // 先创建好一个临时数组,避免排序过程中重复创建数组
        tmpArray = new int[data.length];

        mergeSort(data, 0, data.length - 1);
    }

    private void mergeSort2(int[] data, int start, int end) {
        //排序步长,从最小长度为1的两个子序列排序开始
        int step = 1;

        while (step <= end) {
            for (int i = start; i <= end; i += step * 2) {
                int innerStart = i;
                int innerEnd = i + step * 2 - 1;
                int innerMid = i + step - 1;

                if (innerEnd > end) {
                    innerEnd = end;
                }

                if (innerMid > end) {
                    innerMid = end;
                }

                merge(data, innerStart, innerMid, innerEnd);
            }
            step *= 2;
        }
    }

    /*
     * 合并两个有序序列。
     * 分别用lfe指向左序列索引,right指向右序列索引,比较大小,放入排序后目标序列
     */
    private void merge(int[] data, int start, int mid, int end) {
        System.arraycopy(data, start, tmpArray, start, end - start + 1);

        int left = start;
        int right = mid + 1;
        for (int i = start; i <= end; i++) {
            if (left > mid) {
                data[i] = tmpArray[right++];
            } else if (right > end) {
                data[i] = tmpArray[left++];
            } else if (tmpArray[left] < tmpArray[right]) {
                data[i] = tmpArray[left++];
            } else {
                data[i] = tmpArray[right++];
            }
        }
    }
}
posted @ 2021-05-29 15:42  小拙  阅读(67)  评论(0编辑  收藏  举报