文章目录
前言
O(NlogN)的排序
一、数组中点防溢出
在取一个数组的中点时,传统写法为:
- mid=(min+max)/2; 这样会发生(mn+max)溢出。
- min+(max-min)/2; 可以改成这样。
- mn+((max-min)>>1);也可以这样。
二、master公式
1.master公式的使用
T(N)=a*T(N/b)+O(N^d)
其中T(N)表示母问题规模,a表示子问题调用次数,T(N/b)表示子问题规模,O(N^d)表示其它规模。
必须在子问题规模一样的情况下
时间复杂度
- logb^a<d —>O(N ^ d)
- logb^a>d —> O(N ^logb ^a)
- logb^a==d —> O(N ^d *logN)
归并排序
1.流程
将数组沿中点分成两部分,分别将左右两部分先排序,然后申请辅助空间,依次比较 左右两边的数,根据规则将较大或较小数copy到辅助空间,将这一侧往后移一位和另一侧不移动的数比较,以此类推,最终一边越界将另一边剩下的直接copy进辅助空间,就得到了有序数组。
T(N)=2T(N/2)+O(N)
归并排序的时间复杂度为:O(NlogN)
额外空间复杂度为:O(N)
代码如下
public class mergeSort {
public static void process(int []arr,int L,int R){ // 递归
if (L==R){
return;
}
int mid=L+((R-L)>>1);// 取中点
// 两次递归
process(arr,L,mid);
process(arr,mid+1,R);
merge(arr,L,mid,R);
}
public static void merge(int []arr,int L,int M,int R){// 比较
int[] help=new int[R-L+1];// 辅助空间
int i=0;
int p1=L; // 左指针
int p2=M+1; // 右指针
// O(N)
while (p1 <= M && p2 <= R){// 比较
help[i++]= arr[p1]<=arr[p2]?arr[p1++]: arr[p2++];
}
// 最终
while (p1<=M){
help[i++]=arr[p1++];
}
while (p2<=R){
help[i++]=arr[p2++];
}
// O(N)
for (int j=0;j<help.length;j++){
arr[L+j]=help[j];
}
}
}
2.归并排序拓展
(1)求小和问题
描述:在一个数组中,每一个数左边比当前的数大累加起来,叫做这个数组的小和。求一个数组的小和。
例如:
[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。
思路:转换问题,将求左边比当前数小改为求右边比当前数大的个数,再与该数相乘,最后相加得到小和。
例如:
1右边比1大的数有4个,就是14;3右边比3大的有2个,就是32;4右边比4大的有1个,就是41;2右边比2大的有1个,就是21;5右边没有比5大的数;所以小和为14+32+41+21=16。
利用归并找到小和,在左右两边都排序后,只要左边比右边的小,就要进行小和的累加,当相同时,先复制右边的,这样能更快找完但个数的小和。
public class smallSumProblem {
public static int process(int []arr, int L, int R){ // 递归
if (L==R){
return 0;
}
int mid=L+((R-L)>>1);
return process(arr,L,mid) +process(arr,mid+1,R)+merge(arr,L,mid,R); //总小和
}
public static int merge(int []arr,int L,int M,int R){// 比较
int[] help=new int[R-L+1];// 辅助空间
int i=0;
int p1=L; // 左指针
int p2=M+1; // 右指针
int res =0;
while (p1 <= M && p2 <= R){// 比较 求小和
res+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0; // 左边小于右边时,计算小和数
help[i++]= arr[p1]<arr[p2]?arr[p1++]: arr[p2++];// 左右两边相等时先拷贝右边的
}
// 最终
while (p1<=M){
help[i++]=arr[p1++];
}
while (p2<=R){
help[i++]=arr[p2++];
}
for (int j=0;j<help.length;j++){
arr[L+j]=help[j];
}
return res;
}
}
结果:
(2)逆序对问题
描述:在一个数组中,左边的数如果比右边大,则这两个数构成一个逆序对,请打印所有逆序对。
public class reversePairProblem {
public static void main(String[] args) {
int[] arr = {5, 3, 4, 2, 1};
process(arr, 0, 4);
}
public static void process(int[] arr, int L, int R) { // 递归
if (L == R) {
return;
}
int mid = L + ((R - L) >> 1);
process(arr, L, mid);
process(arr, mid + 1, R);
merge(arr,L,mid,R);
}
public static void merge(int[] arr, int L, int M, int R) {// 比较
int p1 = L; // 左指针
int p2 = M + 1; // 右指针
while (p1 <= M && p2 <= R) {// 比较
if (arr[p1]>arr[p2]){
System.out.println("["+arr[p1]+","+arr[p2]+"]");
p1++;
}else {
p2++;
}
}
}
}
结果:
3.荷兰国旗问题
(1)荷兰国旗问题一
描述:
给定一个数组arr,和一个数num,请把小于等于num的数放在数组左边,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)。
思路:
他并不要求有序,值啊要将小于等于num的数放左边,大于num的数放右边就可以。先规定一个小于等于num的区域area,area起始是arr第一个数的左边,让指针i指向arr第一个数,让arr[i]与num比较,如果比num小或等,就让i+1,让area往右边扩一位,将arr[i]包含其中;如果arr[i]大于num时,i+1,area不变,接着比较下一位,知道有一位比num小时,交换这一位和area下一位数的位置,再将area往下扩一位,以此类推。
public class dutchFlagQuestionOne {
public static void main(String[] args) {
int []arr={3,8,5,3,8,1,2,9,7};
int num=5;
hollandOne(arr,num);
System.out.println(Arrays.toString(arr));
}
public static void hollandOne(int []arr,int num){
if (arr==null||arr.length<2){
return;
}
partition(arr,num,0, arr.length-1);
}
public static void partition(int []arr,int num,int L,int R){
int less=L-1;
int more=R+1;
while (L<more){
if (arr[L]<=num){
swap(arr,++less,L++);
}else {
swap(arr,--more,L);
}
}
}
//交换方法
public static void swap(int []arr,int L,int R){
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
}
结果:
(2)荷兰国旗问题二
描述:
给定一个数组arr,和一个数num,请把小于num的数放在数组左边,等于num的数放在数组中间,大于num的数放在数组的右边,要求额外空间复杂度O(1),时间复杂度O(N)。
思路:
和上一个差不多,只不过多了一个等于的区域
- [i]<num,[i]和小于区下一位交换,小于区右扩一位,i++
- [i]=num,i++
- [i]>num,[i]和大于区前一位交换,大于区左扩一位,i不变
public class dutchFlagQuestionOne {
public static void main(String[] args) {
int []arr={3,8,5,3,8,1,2,9,7};
int num=5;
hollandOne(arr,num);
System.out.println(Arrays.toString(arr));
}
public static void hollandOne(int []arr,int num){
if (arr==null||arr.length<2){
return;
}
partition(arr,num,0, arr.length-1);
}
public static void partition(int []arr,int num,int L,int R){
int less=L-1;
int more=R+1;
while (L<more){
if (arr[L]<=num){
swap(arr,++less,L++);
}else {
swap(arr,--more,L);
}
}
}
//交换方法
public static void swap(int []arr,int L,int R){
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
}
结果:
4.快排
(1)快排1(荷兰国旗问题一的递归)
将最后一个数作为划分数进行判断,先判断第一个数与划分数的大小关系,如果小于等于划分数,与小于区后一位交换,小于区域右扩一位;如果大于划分数,与大于区前一位数交换,大于区左扩一位,返回小于区与大于区的交界值,进行递归,最终必定能得到一个有序数组。
时间复杂度为:O(N^2)
空间复杂度为:O(logN)
public class quickRowOne {
public static void main(String[] args) {
int []arr={1,9,4,4,2,5};
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int []arr){
if (arr==null||arr.length<2){
return;
}
quickSort(arr,0,arr.length-1);
}
public static void quickSort(int[]arr,int L,int R){
if (L<R){
int p=partition(arr,L,R);
quickSort(arr,0,p-1);
quickSort(arr,p,R);
}
}
public static int partition(int []arr,int L,int R){
int less=L-1; // 小于等于区右边界
int more=R;// 大于区左边界
while (L<more){
if (arr[L]<=arr[R]){
swap(arr,++less,L++);
}else swap(arr,--more,L);
}
swap(arr,more,R);
return more;
}
public static void swap(int []arr,int L,int R){
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
}
结果:
(2)快排2(荷兰国旗问题二的递归)
在一的基础上添加了=的区域,也就是说=区域的那部分在后面就不用递归排序了。
时间复杂度为:O(N^2)
空间复杂度为:O(logN)
public class quicksortTwo {
public static void main(String[] args) {
int []arr={1,9,4,4,2,5};
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int []arr){
if (arr==null||arr.length<2){
return;
}
quickSort(arr,0,arr.length-1);
}
public static void quickSort(int[]arr,int L,int R){
if (L<R){
int p[]=partition(arr,L,R);
quickSort(arr,0,p[0]-1);
quickSort(arr,p[1]+1,R);
}
}
public static int[] partition(int []arr,int L,int R){
int less=L-1; // 小于等于区右边界
int more=R;// 大于区左边界
while (L<more){
if (arr[L]<arr[R]){
swap(arr,++less,L++);
}else if (arr[L]>arr[R]){
swap(arr,--more,L);
}else L++;
}
swap(arr,more,R);
return new int[]{less+1,more};
}
public static void swap(int []arr,int L,int R){
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
}
结果:
(3)快排3(随机选取数来划分)
选取划分数时,在列表中随机选取一个数,并人为的将其放到列表的最后一位。
时间复杂度为:O(N*logN)
空间复杂度为:O(logN)
public class quickRowThree {
public static void main(String[] args) {
int []arr={1,9,4,2,5};
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void quickSort(int[]arr){
if (arr==null||arr.length<2){
return;
}
quickSort(arr,0,arr.length-1);
}
// 递归排序
public static void quickSort(int[]arr,int L,int R){
if (L<R){
swap(arr,L+(int)(Math.random()*(R-L+1)),R); // 随机范围数,手动交换到最后一位
int[]p=partition(arr,L,R);// 用来确定等于区域边界
quickSort(arr,L,p[0]-1);// 小于区域
quickSort(arr,p[1]+1,R);// 大于区域
}
}
public static int[] partition(int[]arr, int L, int R){
int less=L-1; // 小于取右边界
int more=R;// 大于区左边界
while (L<more){
// [i]<num,[i]和小于区下一位交换,小于区右扩一位,i++
// [i]=num,i++
// [i]>num,[i]和大于区前一位交换,大于区左扩一位,i不变
if (arr[L]<arr[R]){
swap(arr,++less,L++);
}else if (arr[L]>arr[R]){
swap(arr,--more,L);
}else {
L++;
}
}
swap(arr,more,R);
return new int[]{less+1,more};
}
public static void swap(int[]arr,int L,int R){
int temp=arr[L];
arr[L]=arr[R];
arr[R]=temp;
}
}
结果:
总结
上文主要讲了master公式的使用,归并排序以及快排逻辑,以及递归解决问题。