1. 分治算法思想
- 分治算法(divide and conquer)的核心思想其实就是四个字,分而治之,也就是将原问题划分为n个规模较小,并且结构与原问题相似的子问题,递归的解决这些子问题,然后再合并其结果,就得到原问题的解。
- 分治和递归的区别:分治是一种处理问题的思想,递归是一种编程技巧。实际上分治算法一般都比较适合用递归来实现。分治算法的递归实现中,每一层递归都会涉及这样三个操作:
- (1)分解:将原问题分解成一系列子问题;
- (2)解决:递归的求解各个子问题,若子问题足够小,则直接求解
- (3)合并:将子问题的结果合并成原问题
- 分治算法能解决的问题,一般需要满足以下几个条件:
- (1)原问题与分解的小问题具有相同的模式
- (2)原问题分解成的子问题可以独立求解,子问题之间没有相关性(这一点是分治算法和动态规划明显的区别)
- (3)具有分解终止条件,就是当问题足够小时,可以直接求解
- (4)可以将子问题合并成原问题,而且这个合并操作的复杂度不能太高(否则就起不到减小算法时间复杂的效果了)
2. 分治算法应用举例
- (1)排序算法中的有序度、逆序度的计算(假设我们有 n 个数据,我们期望数据从小到大排列,那完全有序的数据的有序度就是 n(n-1)/2,逆序度等于 0;相反,倒序排列的数据的有序度就是 0,逆序度是 n(n-1)/2。除了这两种极端情况外,我们通过计算有序对或者逆序对的个数,来表示数据的有序度或逆序度。),即编程求出一组数据的有序对个数和逆序对个数。
- 法一、暴力求解:拿每个数字分别和它后面的数字进行对比,记录下逆序对的个数,然后将每个数字的逆序对个数求和,这样可以得出逆序对的个数(有序对个数求解过程类似)
- 算法的时间复杂度:O(n^2)
- 算法的空间复杂度:O(1)
- 法二、使用分治算法,利用归并排序的思路
- 算法时间复杂度:O(n*lgn)
- 算法空间复杂度:O(n)
- 法一、暴力求解:拿每个数字分别和它后面的数字进行对比,记录下逆序对的个数,然后将每个数字的逆序对个数求和,这样可以得出逆序对的个数(有序对个数求解过程类似)
-
class Solution{ private static int num = 0; //定义一个类变量,记录逆序对的值 public int countReverse(int[] a){ num = 0; if(a.length == 0) return -1; mergeSortCounting(a, 0, a.length-1) return num; }; public void mergeSortCounting(int[] a, int low, int high){ if(p>=q) return; int mid = (low + high)/2; mergeSortCounting(a, low, mid); mergeSortCOunting(a, mid+1, high); merge(a, low, mid, high); }; public void merge(int[] a, int low, int mid, int high){ int i = low, j = mid+1, tempi = 0; int[] temp = new int[high - low + 1]; //用于保存待合并的两个数组 while(i<=mid && j <= high){ if(a[i]<=a[j]){ //合并数组 temp[tempi++] = a[i++] }else{ temp[tempi++] = a[j++]; //右侧数组中的值小于左侧数组,则需要更新num值,并且因为左侧数组是有序的,所以左侧数组a[i]后的其余值全要大于a[j] num += mid-i+1; } } while(i<=mid){ //处理剩下的 temp[tempi++] = a[i++]; } while(j<=high){ //处理剩下的 temp[tempi++] = a[j++]; } for(int m = 0; m<high-low +1; m++){ //将temp数组的值拷贝回a数组中,a数组成为一个有序数组 a[m] = temp[m]; } }; }