归并排序 快速排序
整体是递归,左边排好序+右边排好序+merge让整体有序
归并排序复杂度
T(N) = 2*T(N/2) + O(N^1)
根据master可知时间复杂度为O(N*logN)
merge过程需要辅助数组,所以额外空间复杂度为O(N)
归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快
public static int[] sort(int []arr){
pross(arr,0,arr.length-1);
return arr;
}
public static void pross(int[]arr,int L,int R){
if (L==R)return;
int mid=L+((R-L)>>1);
pross(arr,L,mid);
pross(arr,mid+1,R);
merge(arr,L,mid,R);
}
private static void merge(int[] arr, int L, int mid, int R) {
int[] help=new int[R-L+1];
int index=0;
int p1=L; //从左边排好序的地方开始
int p2=mid+1; //右边排好序的地方开始
while(p1<=mid&&p2<=R){
help[index++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
}
while (p1<=mid){
help[index++]=arr[p1++];
}
while (p2<=R){
help[index++]=arr[p2++];
}
for (int i=0;i<help.length;i++){
arr[i+L]=help[i]; //排好序的元素 重新刷回原数组
}
}
非递归方式
public static int[] sort2(int []arr){
if (arr==null||arr.length<=1)return arr;
int mergeSize=1;
int len=arr.length;
while(mergeSize<len){
int L=0;
while(L<len){
int mid=L+mergeSize-1;
if (mid>=len)break;
int R=Math.min(mid+mergeSize,len-1); //右边可能不足mergeSize
merge(arr,L,mid,R);
L=R+1;
}
if (mergeSize>len/2)break;
mergeSize<<=1; //mergeSize乘以2
}
return arr;
}
题目一
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [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 .
实质上就是看右边的数比左边的数大
public static int sum(int []arr){
return pross(arr,0,arr.length-1);
}
public static int pross(int []arr,int L,int R){
if (L==R)return 0;
int mid=L+((R-L)>>1);
return pross(arr,L,mid)+ pross(arr,mid+1,R)+ merge(arr,L,mid,R);
}
private static int merge(int[] arr, int L, int mid, int R) {
int result=0;
int []help=new int[R-L+1];
int p1=L;
int p2=mid+1;
int index=0;
while(p1<=mid&&p2<=R){
result+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;
help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
while(p1<=mid){
help[index++]=arr[p1++];
}
while(p2<=R){
help[index++]=arr[p2++];
}
for (int i=0;i<help.length;i++){
arr[i+L]=help[i];
}
return result;
}
快速排序
给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
public static int [] partition(int []arr,int num){
pross(arr,num);
return arr;
}
//小于区域的下标从-1 开始, 当arr[i]<=num 与 小于区域的下一个数交换 小于区域+1
public static void pross(int[]arr,int num){
int index=-1;
for (int i=0;i<arr.length;i++){
if (arr[i]<=num){
index++;
int temp=arr[i];
arr[i]=arr[index];
arr[index]=temp;
}
}
}
给定一个数组arr,和一个整数num。请把小于num的数放在数组的左边,等于num的数放在中间,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)
public static void process1(int []arr,int L,int R){
if (L >= R) {
return;
}
int[] equalArea = netherlandsFlag(arr, L, R);
process1(arr,L,equalArea[0]-1);
process1(arr,equalArea[1]+1,R);
}
//两个指针 左指针 -1位置 右指针 在length位置
//当arr[i]<num arr[i]与左指针的下一个数交换位置 左指针++ i++
//当arr[i]>num arr[i]与右指针的上一关数交换位置 右指针++ i不变 因为刚刚交换的数还没做判断
//当arr[i]=num i++
public static int[] netherlandsFlag(int[]arr,int L,int R){
if (L > R) {
return new int[] { -1, -1 };
}
if (L == R) {
return new int[] { L, R };
}
int left=L-1;
int rigth=R;
int index=L;
int num=arr[R]; //以最后一个数作为num
while(index<rigth){
if (arr[index]<num){
swap(arr,index++,++left);
}else if(arr[index]==num){
index++;
}else{
swap(arr,index,rigth--);
}
}
swap(arr, rigth, R);
return new int[]{left+1,rigth};
}
快速排序1.0
在arr[L..R]范围上,进行快速排序的过程:
1)用arr[R]对该范围做partition,<= arr[R]的数在左部分并且保证arr[R]最后来到左部分的最后一个位置,记为M; <= arr[R]的数在右部分(arr[M+1..R])
2)对arr[L..M-1]进行快速排序(递归)
3)对arr[M+1..R]进行快速排序(递归)
因为每一次partition都会搞定一个数的位置且不会再变动,所以排序能完成
快速排序2.0
在arr[L..R]范围上,进行快速排序的过程:
1)用arr[R]对该范围做partition,< arr[R]的数在左部分,== arr[R]的数中间,>arr[R]的数在右部分。假设== arr[R]的数所在范围是[a,b]
2)对arr[L..a-1]进行快速排序(递归)
3)对arr[b+1..R]进行快速排序(递归)
快速排序1.0和2.0的时间复杂度分析
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
数组已经有序的时候就是复杂度最高的时候
时间复杂度O(N^2)
快速排序3.0
在arr[L..R]范围上,进行快速排序的过程:
1)在这个范围上,随机选一个数记为num,
1)用num对该范围做partition,< num的数在左部分,== num的数中间,>num的数在右部分。假设== num的数所在范围是[a,b]
2)对arr[L..a-1]进行快速排序(递归)
3)对arr[b+1..R]进行快速排序(递归)
因为每一次partition都会搞定一批数的位置且不会再变动,所以排序能完成
随机快排的时间复杂度分析
1)通过分析知道,划分值越靠近中间,性能越好;越靠近两边,性能越差
2)随机选一个数进行划分的目的就是让好情况和差情况都变成概率事件
3)把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N
4)那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望!