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

【分治算法】<上篇>


一、算法思想简介

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

2.经典例子:

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

二、经典例子的代码实现

1.汉诺塔:

  • 目标:将所有盘子从A柱移动到C柱,同一根柱子在任何时候不允许出现上面的某个盘子大于下面的某个盘子的情况。
  • 基本思想(图解):将A柱的n个盘子全部移动到C柱,可先将A柱的前n-1个盘子移动到B柱子,再将第n个盘子移到C柱,再将B柱的n-1个盘子移到C柱。
    分治算法解析与实战【九大经典例子(附完整代码)】(上篇)_第1张图片
  • 代码实现
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.二分查找(递归版):

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

  • 基本思想(图解)
    分治算法解析与实战【九大经典例子(附完整代码)】(上篇)_第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的有序序列位置。
    分治算法解析与实战【九大经典例子(附完整代码)】(上篇)_第3张图片

  • 代码实现

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;
    }

}

你可能感兴趣的:(数据结构,数据结构,算法,快速排序,排序算法,java)