分治算法解析与实战【九大经典例子(附完整代码)】(上篇)

学习网站推荐:前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站

【分治算法】<上篇>


一、算法思想简介

1.基本思想:”分而治之“,将一个复杂问题分解成两个或多个相同或相似的子问题,再把子问题分解成更小的子问题…直到最后子问题可以简单的直接求解,原问题的解即为所有子问题的解的合并。

2.经典例子:

  • 汉诺塔
  • 二分查找(递归版)
  • 归并排序(递归版)
  • 快速排序(递归版)

二、经典例子的代码实现

1.汉诺塔:

  • 目标:将所有盘子从A柱移动到C柱,同一根柱子在任何时候不允许出现上面的某个盘子大于下面的某个盘子的情况。
  • 基本思想(图解):将A柱的n个盘子全部移动到C柱,可先将A柱的前n-1个盘子移动到B柱子,再将第n个盘子移到C柱,再将B柱的n-1个盘子移到C柱。
    在这里插入图片描述
  • 代码实现
public class Hanoitower {

    public static void main(String[] args) {
        hanoiTower(3, 'A', 'B', 'C');
    }

    private static void hanoiTower(int num, char a, char b, char c) {
        //如果只有一个盘子
        if (num == 1) {
            System.out.println("第1个盘子从" + a + "->" + c);
        } else { //如果有n>=2个,可抽象成两个盘,一个是最下面的一个盘,另一个是上面的所有盘
            //将上面的盘从A->B
            hanoiTower(num - 1, a, c, b);
            //移动最下面的盘从A->C
            System.out.println("第" + num + "个盘从" + a + "->" + c);
            //将上面的盘从B->C
            hanoiTower(num - 1, b, a, c);
        }
    }

}

2.二分查找(递归版):

  • 目标:在有序表查找某值是否存在。

  • 基本思想(图解)
    在这里插入图片描述

  • 代码实现

public class BinarySearch {

    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4, 5};
        System.out.println(search(a, 1));
        System.out.println(search(a, 3));
        System.out.println(search(a, 5));
        System.out.println(search(a, 9));
    }

    public static boolean search(int[] a, int key) {
        return inSearch(a, 0, a.length - 1, key);
    }

    private static boolean inSearch(int[] a, int low, int high, int key) {
        int mid;
        while (low <= high) {
            mid = (low + high) / 2;
            if (key == a[mid]) {
                return true;
            } else if (key < a[mid]) {
                return inSearch(a, 0, mid - 1, key);
            } else {
                return inSearch(a, mid + 1, high, key);
            }
        }
        return false;
    }

}

3.归并排序(递归版):

  • 目标:使序列有序。

  • 基本思想(图解):初始序列含有n个元素,则可看成是n个有序子序列,每个子序列的长度为1,然后两两归并,得到Math.ceil(n/2)个长度为2或1的有序子序列;再两两归并,…,如此重复,直到得到一个长度为n的有序序列位置。
    在这里插入图片描述

  • 代码实现

public class MergeSort {

    public static void sort(int[] a) {
        int n = a.length - 1;
        MSort(a, a, 1, n);
    }

    //将SR[s..n]归并为有序的TR1[s..n]
    private static void MSort(int[] SR, int[] TR1, int s, int n) {
        int m;
        int[] TR2 = new int[n + 1]; // SR, TR1, TR2等长
        if (s == n) {
            TR1[s] = SR[s];
        } else {
            m = (s + n) / 2; // 将SR[s..n]平均分为SR[s..m]和SR[m+1..n]
            MSort(SR, TR2, s, m); // 递归将SR[s..m]归并为有序的TR2[s..m]
            MSort(SR, TR2, m + 1, n); // 递归将SR[m+1..n]归并为有序的TR2[m+1..n]
            Merge(TR2, TR1, s, m, n); // 将TR2[s..m]和TR2[m+1..n]归并到TR1[s..n]
        }
    }

    // 将有序的SR[s..m]和SR[m+1..n]归并为有序的TR[i..n]
    private static void Merge(int[] SR, int[] TR, int s, int m, int n) {
        int j, k, l; // k为左块的起始下标,j为右块的起始下标
        for (k = s, j = m + 1; s <= m && j <= n; k++) { //SR中记录由小到大归并入TR
            if (SR[s] < SR[j]) {
                TR[k] = SR[s++];
            } else {
                TR[k] = SR[j++];
            }
        }
        if (s <= m) {
            for (l = 0; l <= m - s; l++) {
                TR[k + l] = SR[s + l]; // 将剩余的SR[s..m]复制到TR
            }
        }
        if (j <= n) {
            for (l = 0; l <= n - j; l++) {
                TR[k + l] = SR[j + l]; // 将剩余的SR[j..m]复制到TR
            }
        }
    }

}

4.快速排序(递归版):

  • 目标:使序列有序。

  • 基本思想:通过一趟排序将待排序记录分割成独立的两部分,其中每一部分记录的关键字均比另一部分记录的关键字小,则可以分别对着两部分记录继续进行排序,以达到整个序列有序的目的。

  • 代码实现

public class QuickSort {

    public static void sort(int[] a) {
        int n = a.length - 1;
        QSort(a, 1, n);
    }

    private static void QSort(int[] a, int low, int high) {
        int pivot; // 枢轴的下标,将某个数放在此位置,使得它左边的值都比它小,右边的都比它大
        if (low < high) {
            pivot = Partition(a, low, high); // 将a[low..high]一分为二,算出枢轴下标pivot
            QSort(a, low, pivot - 1); // 对低子表递归排序
            QSort(a, pivot + 1, high); // 对高子表递归排序
        }
    }

    // 交换顺序表a中子表的记录,使枢轴记录到为,并返回其位置
    private static int Partition(int[] a, int low, int high) {
        int pivotkey = a[low]; // 用子表的第一个记录作枢轴记录
        while (low < high) { // 从表的两端交替向中间扫描
            while (low < high && a[high] >= pivotkey) {
                high--;
            }
            swap(a, low, high); // 将比枢轴值小的记录交换到低端
            while (low < high && a[low] <= pivotkey) {
                low++;
            }
            swap(a, low, high); // 将比枢轴值大的记录交换到高端
        }
        return low; // 最终low == high,所有返回枢轴所在位置
    }

    private static void swap(int[] a, int x, int y) {
        int temp;
        temp = a[x];
        a[x] = a[y];
        a[y] = temp;
    }

}

请添加图片描述

  • 21
    点赞
  • 110
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超周到的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值