认识O(Nlog N)的排序
文章目录
一、master公式求递归算法的时间复杂度
$$
T(N)=a*T(N/b)+O(N^d)\\
(1)log_b a>d =>时间复杂度为O(N^{log_b a})\
(2)log_b a=d =>时间复杂度为O(N^d*logN)\
(3)log_b a<d =>时间复杂度为O(N^d)\
$$
1、例题 :求arr[L...R]
范围上的最大值
//代码:求arr[L...R]范围上的最大值
public class findMax {
public static int process(int[] arr,int L,int R){
if(L==R) return arr[L];
int mid = L+((R-L)>>1); //求中点
int leftMax = process(arr,L,mid); //左边的最大值
int rightMax = process(arr,mid+1,R); //右边的最大值
return Math.max(leftMax,rightMax); //返回最大值
}
public static void main(String[] args) {
int[] arr={4,6,2,3,8,9,3,6,7,8};
int ans = process(arr, 0, arr.length - 1);
System.out.println(ans);
}
}
2、分析:图解
int mid = L+((R-L)>>1);
防止溢出写法,等同于(L+R)/2
-->L+(R-L)/2
3、对例题使用master公式
二、归并排序
1、归并排序的实质(分治法)
- 归并排序的整体就是一个简单递归,左边排好序,右边排好序,最后整体有序
- 将待排序的数组层层分割到不可再分,再排序,然后再层层组合,最后得到有序数组
- 时间复杂度O(N*log N),额外空间复杂度O(N)
2、归并排序图解
3、归并排序经典代码
//代码:经典归并排序的代码
public class mergeSort {
//排序过程
public static void merge(int[] arr,int left,int mid,int right){
int[] help = new int[right-left+1]; //创建一个辅助数组
int i=0;
int p1=left,p2=mid+1; //双指针,一个指向左部分的最左边,一个指向右部分的最左边
//数组双方都没有越界
//将p1和p2指向的元素中较小的一个拷贝进help中
while (p1<=mid&&p2<=right) help[i++] = arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
//p2已越界但p1没有越界
while (p1<=mid) help[i++] = arr[p1++];
//p1已越界但p2没有越界
while (p2<=right) help[i++]=arr[p2++];
//将help中的元素反拷贝进arr中
/**
* 说明:加入现在传进来的部分是arr[]上的(4...6)部分
* 则help[]的长度为3,到这一步的时候help[]有序
* 将help[]反拷贝进arr[]中,从小标为4开始,
* 也就是从left[left]=help[0]
* left[left+1]=help[1]
* ...
* 最后arr[4...6]上有序
*/
for(i=0;i<help.length;i++)
arr[left+i]=help[i];
}
//拆分过程
public static void process(int[] arr,int left,int right){
//拆到元素单位为1,不可以再拆也无需排序,回到上一层
if(left==right) return;
int mid = left+((right-left)>>1); //拿到中点
process(arr, left, mid); //拆出来的左边部分
process(arr, mid+1, right); //拆出来的右边部分
//排序
merge(arr,left,mid,right);
}
public static void main(String[] args) {
int[] arr = {3,2,1,5,6,2};
//数组长度为0或者为1,直接返回
if(arr==null||arr.length < 2) return;
//数组长度大于等于2,进入排序
process(arr,0,arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
三、归并排序的拓展
1、小和问题
1.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.2 过程分析
问题可以转变为:
1:右边比1大的数有4个-->1*4
3:右边比3大的数有2个-->3*2
4:右边比4大的数有1个-->4*1
2:右边比2大的数有1个-->2*1
5:右边比5大的数有0个
最后:1*4+3*2+4*1+2*1=16
1.3 代码实现
public class mergeSortExample {
//排序过程
public static int merge01(int[] arr,int left,int mid,int right){
//创建一个辅助数组
int[] help = new int[right-left+1];
int i=0;
int p1=left,p2=mid+1; //双指针,一个指向左部分的最左边,一个指向右部分的最左边
int res=0;
//数组双方都没有越界
//将p1和p2指向的元素中较小的一个拷贝进help中
while (p1<=mid&&p2<=right){
//求小和
res+=arr[p1]<arr[p2]?(right-p2+1)*arr[p1]:0;
help[i++] = arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
}
//p2已越界但p1没有越界
while (p1<=mid) help[i++] = arr[p1++];
//p1已越界但p2没有越界
while (p2<=right) help[i++]=arr[p2++];
for(i=0;i<help.length;i++)
arr[left+i]=help[i];
return res;
}
//拆分过程
public static int process01(int[] arr,int left,int right){
//拆到元素单位为1,不可以再拆也无需排序,回到上一层
if(left==right) return 0;
int mid = left+((right-left)>>1); //拿到中点
//小和累加
return process01(arr, left, mid)
+process01(arr, mid+1, right)
+merge01(arr,left,mid,right);
}
public static void main(String[] args) {
int[] arr = {1,3,4,2,5};
//数组长度为0或者为1,直接返回
if(arr==null||arr.length < 2) return;
//数组长度大于等于2,进入排序
int res = process01(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println(res);
}
}
2、逆序对问题
2.1 问题描述
在一个数组中,左边的数如果比右边的数大,则两个数构成一个逆序对,请打印所有逆序对。
和求小和是同源问题,只不过现在是求右边有多少个数比当前数小
四、快速排序和荷兰国旗问题
1、快速排序问题引入—荷兰国旗问题
简单问题:给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度0(1),时间复杂度0(N)。
升级–>荷兰国旗问题:给定一个数组arr,和一个数num,请把小于num的数放在数组的 左边,等于num的数放在数组的中间,大于num的数放在数组的 右边。要求额外空间复杂度0(1),时间复杂度ON
2、问题分析
3、快速排序思想
荷兰国旗问题并不要求数组有序,只需要划分出三个区域。
快速排序可以看成在荷兰国旗问题的基础上进行递归。
荷兰国旗只需要一层,分出三个区域。那么在此基础上,对荷兰国旗分出的三个区域中的左右区域再进行分层,直到不能分层,这就成了快速排序。
4、快速排序代码实现
//代码:快速排序最终版
//时间复杂度O(N*log N) 空间复杂度O(log N)
//交换
public static void swap(int[]arr,int pos,int r){
int temp = arr[pos];
arr[pos]=arr[r];
arr[r]=temp;
}
//快速排序
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);
}
}
//处理arr[l...r]
//默认以arr[r]做划分
//返回等语区域的左右边界
public static int[] partition(int[] arr,int l,int r){
int less = l-1; //小于区域的右边界
int more = r; //大于区域的左边界
//l表示当前数的位置,没有撞上大于区域时则进入循环
while(l<more){
//当前位置小于划分值,应该在左边
if(arr[l]<arr[r]){
//当前位置与小于区域的下一个做交换,然后当前位置前进
swap(arr,++less,l++);
} else if (arr[l] > arr[r]) {
//当前位置大于划分值,应该在右边
//当前位置与大于区域的前一个做交换,当前位置原地不动
swap(arr,--more,l);
}else{
//相等的话直接++
l++;
}
}
//将排在最后的划分值arr[r]换回中间
swap(arr,more,r);
//返回左右边界
return new int[]{less+1,more};
}
public static void main(String[] args) {
int[] arr = {2,5,3,7,9,1,3,4,5,8};
QuickSort(arr,0,arr.length-1);
}