分治法思想-归并排序案例图片详解

分治法简介

分治法思想

​ 分治法,就是将一个难以解决的大问题给分成多个规模较小的子问题,分别解决各个子问题,最后合并子问题的解得到原问题的解。

分治法求解过程:

1、划分():

​ 把规模为n的原问题划分为k(通常k=2)个规模较小的子问题,再对子问题继续划分,直到到达最小子问题时终止(比如说元素只剩1个),一般使用递归来实现。

3、合并():

​ 从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题的解。合并的代价因情况不同有比较大的差异,分治算法的效率很大程度上依赖合并的实现

分治思想在生活中的应用

1、做饭

​ 在做饭的过程中,需要准备主食、汤、菜。

​ 将整个做饭分解成蒸米饭、煮汤、烧菜三部分,三部分完成后再整合再一起放在桌子上,完成做饭。

2、人口普查

​ 全国人口普查,将问题拆分成省级,市级,区级,乡镇级,街道级,小区级。

​ 然后依次向上汇报进行汇总,然后自下而上统计人数。

如何判断是否为分治问题:

一个问题是否适合使用分治解决,通常可以参考以下几个判断依据。

  1. 问题可以分解:原问题可以分解成规模更小、类似的子问题,以及能够以相同方式递归地进行划分。
  2. 子问题是独立的:子问题之间没有重叠,互不依赖,可以独立解决。
  3. 子问题的解可以合并:原问题的解通过合并子问题的解得来。

经典案例-归并排序

分析:

对于一个无序数组,对其进行排序,其满足分治的三个要求:

  1. 问题可以分解:递归地将数组(原问题)划分为两个子数组(子问题)。
  2. 子问题是独立的:每个子数组都可以独立地进行排序(子问题可以独立进行求解)。
  3. 子问题的解可以合并:两个有序子数组(子问题的解)可以合并为一个有序数组(原问题的解)。

过程图解:

​ 使用递归不断的对原数组进行划分,直到划分为最小子问题(单个元素,单个元素一定是有序的)。

​ 然后再依次合并子数组,将其合并为一个较大的有序数组。
在这里插入图片描述

前面说过,分治算法的效率很大程度上依赖合并的实现,这里合并如何实现是比较重要的。

合并数组操作:

​ 两个子数组从下标0开始,依次比较,拿出其中较小的放入合并后数组中。

​ 最后将剩余子数组的数据依次放入结果数组中。

以上面 {2、3、6、7} 与 {0、1、4、5} 两个有序子数组合并为例:

1、初始化

​ 左边数组、右边数组、合并后数组都从下标0开始,即 leftIndex = rightIndex = resulrIndex = 0

2、第一趟比较

​ 右边数组下标rightIndex=0的位置值小于左边数组下标leftIndex=0位置的值。

​ 先将右边数组下标rightIndex=0的位置值放入结果数组中,然后右边数组下标向后移动一位,结果数组下标向后移动,左边数组下标不动。

​ 即result[resultIndex++] = right[RightIndex++]
在这里插入图片描述

3、第二趟比较

​ 右边数组下标rightIndex=1的位置值小于左边数组下标leftIndex=0位置的值。

​ 先将右边数组下标rightIndex=1的位置值放入结果数组中,然后右边数组下标向后移动一位,结果数组下标向后移动,左边数组下标不动。

​ 即result[resultIndex++] = right[RightIndex++]

在这里插入图片描述

4、第三趟比较
在这里插入图片描述

5、第四躺比较
在这里插入图片描述

6、第五躺比较
在这里插入图片描述

7、第六躺比较
在这里插入图片描述

8、第7躺比较

​ 此时,右边数组遍历结束,左边数组还有剩余。

​ 此时依次将左边数组中数据放入结果数组。

在这里插入图片描述

9、最后

在这里插入图片描述

代码实现:

    /**
     * @param data 待排序数组
     * @param left 待排序数组起始下标
     * @param right 待排序数组终止下标
     * @return
     */
    public static int[] execute(int[] data, int left, int right) {
        // 达到最小子问题,即只有一个元素,那么这个元素一定是有序的
        if (left == right) {
            return new int[]{data[left]};
        }
        /**
         * 定义临时变量
         * midIndex:数组中间值,即将数组划分为 left -> midIndex 和 midIndex+1 -> right 两部分
         * leftIndex:合并两个有序子数组时,记录 left -> midIndex 数组的下标
         * rightIndex:合并两个有序子数组时,记录 midIndex+1 -> right 数组的下标
         * resultIndex:合并两个有序子数组时,记录 合并后有序数组 的下标
         */
        int midIndex = (right - left) / 2 + left, 
                leftIndex = 0, rightIndex = 0, resultIndex = 0;
        // 递归求解 left -> midIndex 部分,使这部分有序
        int[] leftResult = execute(data, left, midIndex);
        // 递归求解 midIndex+1 -> right 部分,使这部分有序
        int[] rightResult = execute(data, midIndex + 1, right);
        // 存储合并后的有序数组
        int[] result = new int[right - left + 1];
        // 合并
        while (leftIndex < leftResult.length && rightIndex < rightResult.length) {
            result[resultIndex++] = 
                    leftResult[leftIndex] <= rightResult[rightIndex] ? 
                            leftResult[leftIndex++] : rightResult[rightIndex++];
        }
        // left -> midIndex 部分有剩余,依次填入合并后有序数组
        while (leftIndex < leftResult.length) {
            result[resultIndex++] = leftResult[leftIndex++];
        }
        // midIndex+1 -> right 部分有剩余,依次填入合并后有序数组
        while (rightIndex < rightResult.length) {
            result[resultIndex++] = rightResult[rightIndex++];
        }
        return result;
    }

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值