先上各种排序算法的实现,面试经常问的。
冒泡排序
public int[] Maopao(int[] nums){
int flag=0;
for(int p=nums.length-1;p>=0;p--){
int j=0;
for(;j<p ;j++){
//每一趟排序都把最大的值放到最后
if(nums[j]>nums[j+1]){
flag=1;
swap(nums,j,j+1);
}
}
if(flag==0)
break;
}
return nums;
}
插入排序
public int[] Insert(int[] nums){
for(int p=nums.length-1;p>=0;p--){
int tmp=nums[p];
int j=p-1;
for(;j>=0&&nums[j]>tmp;j--){
nums[j+1]=nums[j];
}
nums[j+1]=tmp;
}
return nums;
}
希尔排序
public int[] Shell(int[] nums){
for(int h=nums.length/2;h>=1;h/=2){
for(int i=0;i<h;i++){
for(int j=i;j+h<nums.length;j+=h){
if(nums[j]>nums[j+h])
swap(nums,j,j+h);
}
}
}
return nums;
}
堆排序
public int[] Dui(int[] nums){
//把nums变成一个大顶堆
//如果是奇数,说明有右儿子,如果是偶数,说明没有右儿子
int i=nums.length%2==0?(nums.length/2-1):((nums.length-1)/2-1);
for(;i>=0;i--){//从最后一个元素的儿子开始,建最大堆
percDown(nums,i,nums.length-1);
}
//把root放到数组的最后一个,再维持大顶堆
for(int j=nums.length-1;j>=0;j--){
swap(nums,j,0);
percDown(nums,0,j-1);
}
return nums;
}
public void percDown(int[] nums,int hole,int N){//N是结束比较的最后一个点
int tmp=nums[hole];
while((hole+1)*2-1<=N){
//当左儿子满足范围
//如果有右儿子并且右儿子比左儿子大,那么child是右儿子
int child = (hole+1)*2-1!=N &&( nums[(hole+1)*2]>nums[((hole+1)*2)-1] )?((hole+1) *2):(((hole+1) *2)-1);
if(nums[child]>tmp){
nums[hole]=nums[child];//较大的值往上移,如果不比tmp大,说明tmp位置正确
hole=child;
}else{
break;
}
}
nums[hole]=tmp;//忘记这一步
}
归并排序
public int[] Merge_sort(int[] num){
int[] tmp=new int[num.length];
m_sort(num,tmp,0,num.length-1);
return num;
}
public void m_sort(int[] arr1,int[] arr2,int L,int Rend){
int center;
if(L<Rend){
center=(L+Rend)/2;
m_sort(arr1,arr2,L,center);
m_sort(arr1,arr2,center+1,Rend);
merg(arr1,arr2,L,center+1,Rend);
}
}
public void merg(int[] arr1,int[] arr2,int L,int R,int Rend){
int Lend=R-1;
int i=L;
int j=R;
int num=Rend-L+1;
int start=i;
while(i<=Lend&&j<=Rend){
if(arr1[i]<arr1[j])
arr2[start++]=arr1[i++];
else{
arr2[start++]=arr1[j++];
}
}
while(i<=Lend)
arr2[start++]=arr1[i++];
while(j<=Rend)
arr2[start++]=arr1[j++];
for(int n=0;n<num;n++){
arr1[Rend]=arr2[Rend];
Rend--;
}
}
快速排序,手撕被问了两次
public int[] Quick_sort(int[] nums){
q_sort(nums,0,nums.length-1);
return nums;
}
public void q_sort(int[] arr,int left,int right){
if(left>right)return;
int pivot=median(arr,left,right);
int i=left,j=right-1;
int tmp;
while (i<j){
while(++i<j&&arr[i]<pivot){}
while(--j>i&&arr[j]>pivot){}
//此时的ij刚好停在两个错误数的索引上
if(i<j){
tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
}
}
//然后把pivot放在a[i]的位置上
tmp=arr[i];
arr[i]=pivot;
arr[right-1]=tmp;
//此时pivot放在正确位置上。
q_sort(arr,left,i-1);
q_sort(arr,i+1,right);
//左右两边递归结束的时候,就全部排好序了。
}
public int median(int[] nums,int left,int right){
int center=(left+right)/2;
//左中右的顺序不要乱,左大于中,左大于右,中大于右,保证不出错
if(nums[left]>nums[center])
swap(nums,left,center);
if(nums[left]>nums[right])
swap(nums,left,right);
if(nums[center]>nums[right])
swap(nums,center,right);
swap(nums,center,right-1);
return nums[right-1];
}
public void swap(int[] nums,int i,int j){
int tmp=nums[j];
nums[j]=nums[i];
nums[i]=tmp;
}
考虑如何选择排序算法,无非还是从(最好最坏)时间空间复杂度、稳定性、初始状态上来考虑。
- 如果数据初始状态就是无序的,可以从数据的规模来考虑:
数据量少,二次规模的时候,可以直接用插入排序,因为数据量少的,选择O(N2)复杂度的也可以接受。数据量大的时候,就要选择时间复杂度更优的算法,比如 归并、快排、堆时间复杂度为O(NlogN)的算法。
如果数据是有范围的,可以用桶排序或基数排序来实现,时间复杂度O(N)(这里忽略了桶的个数,应该加上) - 如果数据已经基本有序
对于冒泡排序和插入排序,如果数据已经基本有序,时间复杂度接近O(N);而希尔排序仍然是O(N2)
不管是否有序,桶排序和归并排序都是O(NlogN) - 如果使用快速排序,而每次选择的pivot值都是剩余数据里的最大值或最小值的画,则快速排序时间复杂度退化到O(N2)。
- 如果是排名之类的,需要保证稳定性的排序
则只能选择归并、插入、冒泡这些稳定性好的算法。