1、递归相关算法的时间复杂度分析(master公式引入)
一切递归行为都可以变成非递归行为,递归就是系统帮你压栈,子程序运行完之后弹栈,还原,继续往下执行
在学排序算法的时候,首先学习的一定是选择、插入(希尔排序是插入排序的一种)、冒泡三个排序,这个三个排序的时间复杂度均为O(N^2),空间复杂度为O(1)(关于这几个算法,请看:选择排序、插入排序、希尔排序 、冒泡排序)
对于涉及到递归的,如紧接着这三个排序下面的归并排序(归并排序),这里要引入一个公式:master公式(这是最简形式,不考虑其他复杂变种,并且看递归次数只看第一级)
比如归并排序(自顶向下)算法,对半开递归+merge:2*T(N/2) + O(N),对照master公式算一下为:O(N*logN),额外空间复杂度O(N)(因为用到了辅助数组)
2、归并思路练习:小和问题和逆序对问题
Question1:小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:
[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16
=======================================================================
Question2:逆序对问题
在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序
对。
刚开始想这个问题的时候,有个问题困扰我,就是为什么要用归并排序的思路来做这个问题,为什么还要弄个辅助数组,这个辅助数组好像没有用啊,我又不是在排序,我是在做求和问题,所以我之前一直在想这个辅助数组的问题,直接比较arr[i]和arr[j],然后求和不就行了,后来发现,不管我怎么做,都是要加上这个辅助数组的
- 这里涉及到一个加快这个小和计算效率的方法,当排好序后,如果你比右边的某个数小,那么你就比(比这个数大的右边的数)都要小,一次性地帮你算完,就没你的事了
- 而且这个因为是递归的,会从相临近的两个数开始比较,不必担心顺序的问题,因为它会帮你自顶向下全部计算一下
- 辅助数组是提高它计算效率的关键,这个思路除了排序、计算小和、还可以得出逆序数组逆序
- 如果你既要用递归,又不用辅助数组,那么你就会少算一些东西,比如你算了比一个数小,指针拨过去了,这个数就不会再被计算了
所以你只要在归并排序的基础上,再添加一些东西就行了
小和问题代码(我这里用的是《算法_第四版》上面的归并排序思路,用的是if语句,可能和其他的文章不太一样,个人觉得更好理解:归并排序):
package cn.nupt.sort;
/**
* @Description: 小和问题和逆序对问题
*
* @author PizAn
* @date 2019年1月22日 下午12:14:50
*
*/
public class SmallSum {
public void mergeSort(int[] arr) {
int lo = 0;
int hi = arr.length - 1;
int sort = mergeSort(arr, lo, hi);
System.out.println(sort);
}
private int mergeSort(int[] arr, int lo, int hi) {
if (lo == hi)
return 0;
int mid = lo + ((hi - lo) >> 1); // 用位运算符,更有逼格
return mergeSort(arr, lo, mid) + mergeSort(arr, mid + 1, hi) + merge(arr, lo, mid, hi);
}
==========================小和问题=========================================
private int merge(int[] arr, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
int[] arrSup = new int[arr.length];
int sum = 0;
for (int k = lo; k <= hi; k++) {
arrSup[k] = arr[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) { //左半边用尽,用右半边元素
arr[k] = arrSup[j++];
} else if (j > hi) { //右半边用尽,用左半边元素
arr[k] = arrSup[i++];
} else if (arrSup[i] < arrSup[j]) {
============主要添加的地方===========================
sum += arrSup[i] * (hi - j + 1);
arr[k] = arrSup[i++];
} else { //用完再用左半边
arr[k] = arrSup[j++];
}
}
return sum;
}
=========================逆序对求解====================================
private int merge(int[] arr, int lo, int mid, int hi) {
int i = lo;
int j = mid + 1;
int[] arrSup = new int[arr.length];
int sum = 0;
for (int k = lo; k <= hi; k++) {
arrSup[k] = arr[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) { //左半边用尽,用右半边元素
arr[k] = arrSup[j++];
} else if (j > hi) { //右半边用尽,用左半边元素
arr[k] = arrSup[i++];
} else if (arrSup[i] > arrSup[j]) {
===========主要添加的地方=============
System.out.println("[" + arrSup[i] + ":" + arrSup[j] + "]");
arr[k] = arrSup[j++];
} else { //用完再用左半边
arr[k] = arrSup[i++];
}
}
return sum; //这里是为了不报错,删掉然后把上面的稍微改一下就行了
}
}