算法分析与设计之分而治之篇(1)

一、归并排序

  • 问题背景:杠铃增重问题,每位参赛运动员向组委会提交排好序得三次举重量,为了便于杠铃得拆卸,组委会需对所有试举重量递增排序。

  •  已学过的解决方案
    • 选择排序:从待排序元素中迭代选出最小值并排序
    • 插入排序:依次将每个元素插入到已排序序列中

  •  分析杠铃增重问题
    • 特点:局部有序
    • 快速合并:比较两个有序数组当前最小元素,将较小者逐一合入新数组
    • 后续策略:
      • 逐一合并
      • 两两合并
  • 杠铃增重问题代码实现
package com.tiger.study;


import java.util.ArrayList;
import java.util.List;

public class MergeSort {
    public static void main(String[] args) {
        // 杠铃增重问题
        int[] l1 = {4, 10, 19};
        int[] l2 = {13, 16, 18};
        int[] l3 = {5, 9, 12};
        int[] l4 = {11, 15, 17};

        int[] mergeList = merge(merge(l1, l2), merge(l3, l4));
        for (int i = 0; i < mergeList.length; i++) {
            System.out.println(mergeList[i]);
        }
    }

    private static int[] merge(int[] a, int[] b) {
        int[] tempList = new int[a.length + b.length];
        int a_head = 0, b_head = 0;
        int count = 0;
        while (a_head < a.length && b_head < b.length) {
            if (a[a_head] <= b[b_head]) {
                tempList[count] = a[a_head++];
                count++;
            } else {
                tempList[count] = b[b_head++];
                count++;
            }
        }
        if (a_head < a.length) {
            for (int i = a_head; i < a.length; i++) {
                tempList[count] = a[i];
                count++;
            }
        } else if (b_head < a.length) {
            for (int i = b_head; i < b.length; i++) {
                tempList[count] = b[i];
                count++;
            }
        }

        return tempList;
    }


}

  •  从杠铃增重问题转换为排序问题
    • 相较于杠铃增重问题而言,排序问题的问题输入有所改变,排序问题中以完整的数组输入,同时局部有序缺失。

  •  针对于问题输入的变化,我们可以将问题分解,当输入长度为1时,数组天然有序,这项可以处理局部有序确实的变化。

  • 通过两两合并构建有序数组

  • 归并排序算法流程:
    • 将数组A[1,n]排序问题分解为A[1,\left \lfloor \frac{n}{2} \right \rfloor]A[\left \lfloor \frac{n}{2} \right \rfloor + 1,n]的排序问题
    • 递归解决子问题得到两个有序的子序列
    • 将两个有序的子数组合并为一个有序数组

  •  分而治之法的一般步骤

  •   归并排序代码实现
package com.tiger.study;


public class MergeSort {
    public static void main(String[] args) {
        // 归并排序
        int[] arr = {24, 17, 40, 28, 13, 14, 22, 32, 40, 21, 48, 4, 47, 8, 37, 18};
        int[] result = new int[arr.length];
        mergeSort(arr, 0, arr.length - 1, result);
        for (int i = 0; i < result.length; i++) {
            System.out.println(result[i]);
        }
    }

    // 递归方法
    private static void mergeSort(int[] arr, int left, int right, int[] result) {
        // 递归结束条件
        if (right == left) return;
        int mid = (right + left) / 2;

        mergeSort(arr, left, mid, result);
        mergeSort(arr, mid + 1, right, result);
        merge(arr, left, right, result);
    }

    // 合并有序的部分
    private static void merge(int[] arr, int left, int right, int[] result) {
        int mid = (right + left) / 2;
        int left_head = left, right_head = mid + 1, result_index = left;
        while (left_head <= mid && right_head <= right) {
            if (arr[left_head] <= arr[right_head]) {
                result[result_index++] = arr[left_head++];
            } else {
                result[result_index++] = arr[right_head++];
            }
        }

        if (left_head <= mid) {
            for (int i = left_head; i <= mid; i++) {
                result[result_index++] = arr[i];
            }
        }

        if (right_head <= right) {
            for (int i = right_head; i <= right; i++) {
                result[result_index++] = arr[i];
            }
        }

        for (int i = left; i <= right; i++) {
            arr[i] = result[i];
        }

    }


}

二、递归式求解

  • 递归树法:用树的形式表示抽象递归

  •  代入法:这种方法比较看直觉,要先猜一个,然后去证明
  • 主定理法:这个方法有一堆的推导,然后我们作题的话只要记住下面的形式就行了

  •  递归式分析方法比较

 三、最大子数组问题

  • 问题描述:数组X中有若干个子数组,每个子数组为X中的一段序列,寻找X中最大的非空子数组。
package com.tiger.study;

// 最大子数组问题:数组X中有若干个子数组,每个子数组为X中的一段序列,寻找X中最大的非空子数组


import java.util.ArrayList;
import java.util.Arrays;

public class MaxSubArray {
    public static void main(String[] args) {
        int[] arr = {1, -2, 4, 5, -2, 8, 3, -2, 6, 3, 7, -1};

        // 暴力解法
        ArrayList<Integer> maxSubArray = new ArrayList<>();
        int maxValue = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            int temp = 0;
            for (int j = i; j < arr.length; j++) {
                temp += arr[j];
                if (temp >= maxValue) {
                    maxValue = temp;
                    maxSubArray.clear();
                    for (int k = i; k <= j; k++) {
                        maxSubArray.add(arr[k]);
                    }
                }
            }
        }
        for (int i = 0; i < maxSubArray.size(); i++) {
            System.out.println(maxSubArray.get(i));
        }

        // 分治策略
        int[] maxSubArray1 = maxSubArray(arr, 0, arr.length - 1);
        for (int i = 0; i < maxSubArray1.length; i++) {
            System.out.println(maxSubArray1[i]);
        }

    }

    private static int[]  maxSubArray(int[] arr, int low, int high) {
        if (low == high) {
            return new int[]{arr[low]};
        } else {
            int mid = (low + high) / 2;
            int[] s1 = maxSubArray(arr, low, mid);
            int[] s2 = maxSubArray(arr, mid + 1, high);
            int[] s3 = crossingSubArray(arr, low, mid, high);
            int[] sMax = max(s1, s2, s3);
            return sMax;
        }
    }

    private static int[] crossingSubArray(int[] arr, int low, int mid, int high) {
        int left_head = mid, right_head = mid + 1;
        int temp = 0;
        int max = Integer.MIN_VALUE;
        for (int i = mid; i >= low; i--) {
            temp += arr[i];
            if (temp > max) {
                left_head = i;
                max = temp;
            }
        }
        temp = 0;
        max = Integer.MIN_VALUE;
        for (int i = mid + 1; i <= high; i++) {
            temp += arr[i];
            if (temp > max) {
                right_head = i;
                max = temp;
            }
        }
        int[] result = new int[right_head - left_head + 1];
        for (int i = left_head, j = 0; i <= right_head && j < result.length; i++, j++) {
            result[j] = arr[i];
        }
        return result;
    }

    private static int[] max(int[] s1, int[] s2, int[] s3) {
        int sum1 = Arrays.stream(s1).sum();
        int sum2 = Arrays.stream(s2).sum();
        int sum3 = Arrays.stream(s3).sum();
        int max = Math.max(Math.max(sum1, sum2), sum3);
        if (max == sum1) {
            return s1;
        } else if (max == sum2) {
            return s2;
        } else {
            return s3;
        }
    }
}

四、逆序对计数问题

  • 问题描述:数组中存在下标小的值比下标大的值大的称为逆序对,求数组中一共有多少个逆序对?
package com.tiger.study;

// 逆序对计数问题:数组中共有多少个逆序对


public class CountingInversions {
    public static void main(String[] args) {
        int[] arr = {13, 8, 10, 6, 15, 18, 12, 20, 9, 14, 17, 19};
        System.out.println(countingInversions(arr));

        System.out.println(countInver(arr, 0, 2));
    }

    // 暴力方法
    private static int countingInversions(int[] arr) {
        int count = 0;
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    count++;
                }
            }
        }
        return count;
    }


    // 分治策略
    private static int countInver(int[] arr, int left, int right) {
        if (left == right) {
            return 0;
        }
        int mid = (left + right) / 2;
        int s1 = countInver(arr, left, mid);
        int s2 = countInver(arr, mid + 1, right);
        int s3 = mergeCount(arr, left, mid, right);
        int s = s1 + s2 + s3;
        return s;
    }

    private static int mergeCount(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int tempIndex = 0;
        int left_head = left, right_head = mid + 1;
        int s3 = 0;
        while (right_head <= right && left_head <= mid) {
            if (arr[left_head] > arr[right_head]) {
                temp[tempIndex++] = arr[right_head++];
            } else {
                temp[tempIndex++] = arr[left_head++];
                s3 += right_head - mid - 1;
            }
        }
        if (left_head <= mid) {
            for (int i = left_head; i <= mid; i++) {
                temp[tempIndex++] = arr[i];
                s3 += (right - mid);
            }
        }
        if (right_head <= right) {
            for (int i = right_head; i <= right; i++) {
                temp[tempIndex++] = arr[i];
            }
        }
        for (int i = 0; i < temp.length; i++) {
            arr[left + i] = temp[i];
        }
        return s3;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值