• 【算法】分治的思路


    一、算法理解

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

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

    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++];
                }
            }
        }
    }
    
  • 相关阅读:
    HTTP协议实体的基本讲解
    了解Xcode目录结构 开发者必看
    图解oracle 之从客户端到服务端的sql追击
    iPhone开发经典语录集锦
    iphone网络编程 http请求 get / post(发送二进制文件) 总结
    iPhone 路径大全
    http发送文件的原理
    iPhone开发面试题葵花宝典
    来自mooon的最简单的日志类CSimpleLogger
    不再担心日志文件过大:通用日志滚动脚本
  • 原文地址:https://www.cnblogs.com/yickel/p/14825465.html
Copyright © 2020-2023  润新知