一、快速排序
1.基本思想
从待排序的关键字中选取一个关键字,将小于该关键字的移到前面,而将大于该关键字的移到后面,结果将待排序记录序列分为两个子表,最后将该关键字插到其分界线处。这个过程称为一趟快速排序。然后对两个子表继续进行快速排序。
2.具体步骤
1)选择一个基准值(选择最右区间,最右边的数作为基准值) 选择基准值的方法有取边法、随机法和三数取中法。
2)遍历整个区间,每个数都和基准值做比较,并且发生一定交换。(partition的方法有hover法、挖坑法和前后下标法)
遍历结束后,使得
比基准值小的数(包括等于)都在基准值的左边
比基准值大的数(包括等于)都在基准值的右边
3)分治算法
分别对左右两个小区间进行同样的方式处理,直到小区间的size==0或者size==1
切割的算法分析:
1)hover法:
从左边找比基准值大的数,从右边找比基准值小的数。然后交换这两个数,直到begin==end,再将基准值插入到begin中。
2)挖坑法:
先记录基准值,然后从左边选出比基准值大的数,将该数填到基准值的位置上,接着从右边遍历,找出比基准值小的数,填到begin的位置上。直到begin==end,最后将基准值填到begin的位置上。
3)前后下标法:
设置两个下标引用:d和i,保证d前面的数都比基准值小(只要array[i]<pivot,就交换d和i对应的元素)[d,i]无序,直到i==right,则d的位置就是基准值的位置,将arr[d]和arr[right]交换。
3.代码
public class QuickSort {
private static void quickSort(int[] array){
quickSortIR(array,0,array.length-1);
}
//[left,right]
private static void quickSortIR(int[] array, int left, int right) {
//终止条件 区间里只有一个或者没有
if(left>=right){
return;
}
//1.取基准值
int key=array[right];
//2.分割
int pivotIndex=partition3(array,left,right);//返回基准值在的下标
//[left,pivotIndex-1]:比基准值小的数 [pivotIndex+1,right]:比基准值大的数
quickSortIR(array,left,pivotIndex-1);
quickSortIR(array,pivotIndex+1,right);
}
//分割 hover法 从左边找比基准值大的数,从右边找比基准值小的数。然后交换这两个数,直到begin==end,再将基准值插入到begin中。
private static int partition1(int[] array, int left, int right) {
int begin=left;
int end=right;
int pivot=array[right];
while(begin<end){
while(begin<end&&array[begin]<=pivot){
begin++;
}
while(begin<end&&array[end]>=pivot){
end--;
}
swap(array,begin,end);
}
//begin==end 则基准值要插入的位置是array[begin],交换两个数
swap(array,begin,right);
return begin;
}
//挖坑法 先记录基准值,然后从左边选出比基准值大的数,将该数填到基准值的位置上,接着从右边遍历,找出比基准值小的数,填到begin的位置上。
//直到begin==end,最后将基准值填到begin的位置上。
private static int partition2(int[] array,int left,int right){
int pivot=array[right];
int begin=left;
int end=right;
while(begin<end){
while(begin<end&&array[begin]<=pivot){
begin++;
}
array[end]=array[begin];
while(begin<end&&array[end]>=pivot){
end--;
}
array[begin]=array[end];
}
array[begin]=pivot;
return begin;
}
//前后下标法 设置两个下标引用,d和i,保证d前面的数都比基准值小(只要array[i]<pivot,就交换d和i对应的元素)[d,i]无序,直到i==right,则d的位置就是基准值的位置,将arr[d]和arr[right]交换。
private static int partition3(int[] array,int left,int right){
int d=left;
for(int i=left;i<=right;i++){
if(array[i]<array[right]){
swap(array,i,d);
d++;
}
}
swap(array,d,right);
return d;
}
private static void swap(int[] array, int begin, int end) {
int temp=array[begin];
array[begin]=array[end];
array[end]=temp;
}
public static void main(String[] args) {
int[] array=new int[]{33,4,8,3,10,21,9,4};
quickSort(array);
for(int item:array){
System.out.print(item+" ");
}
}
}
取基准值还有三数取中法,取值的代码为:
//返回三数取中的下标
private static int sanshuQuZhong(int[] array,int left,int right){
int mid=left+(right-left)/2;
if(array[left]>array[right]){
if(array[left]<array[mid]){
return left;
}else if(array[mid]>array[right]){
return mid;
}else{
return right;
}
}else{
if(array[right]<array[mid]){
return right;
}else if(array[mid]>array[left]){
return mid;
}else{
return left;
}
}
}
private static void quickSortInnerSan(int[] array, int left, int right) {
//直到size==1或size==0
if(left==right){
return;//size==1
}
if(left>right){
return;//size==0
}
//要排序的区间是array[left,right]
//1.找基准值
int originIndex=sanshuQuZhong(array,left,right);
swap(array,originIndex,right);
//2.遍历整个区间,把区间分为三部分。 pivotIndex:基准值的下标
int pivotIndex=parition3(array,left,right);
//比基准值小的[left,pivotIndex-1]
//比基准值大的[pivotIndex+1,right]
//3.分治算法
//处理比基准值小的区间
quickSortInner(array,left,pivotIndex-1);
//处理比基准值大的区间
quickSortInner(array,pivotIndex+1,right);
}
4.复杂度与稳定性
时间复杂度 空间复杂度
最好 平均 最坏 最好 平均 最坏
O(nlog(n)) O(nlog(n)) O(n^2) O(logn) O(log(n)) O(n)
不稳定
二、归并排序
1.基本思想
将待排序的元素序列分成两个长度相等的子序列,对每一个子序列进行排序,然后将它们合并成一个序列。
2.基本步骤
1)把要排序区间平均切分两部分
2)分治算法,对左右两个区间进行同样方式的排序
直到
size==1 已经有序
size==0 区间没有数了(实际不会走到)
3)合并左右两个有序区间到一个有序区间(需要额外空间)
3.代码
public class mergeSort {
public static void mergeSort(int[] array){
int[] extra=new int[array.length];
mergeInner(array,0,array.length,extra);
}
//分治算法,对左右两个区间进行同样方式的排序 [low,high)
private static void mergeInner(int[] array,int low,int high,int[] extra){
if(low==high-1||low>=high){//如果区间中只有一个数,或没有数,此时有序。则直接返回
return;
}
//计算左右区间 [low,mid) 右:[mid,high)
int mid=low+(high-low)/2;
// 分治算法,处理两个小区间
mergeInner(array,low,mid,extra);
mergeInner(array,mid,high,extra);
//两个数组已经有序,下面进行合并两个有序数组
merge(array,low,mid,high,extra);
}
//合并两个有序数组 [low,mid) [mid,high)
private static void merge(int[] array, int low, int mid,int high, int[] extra) {
int x=0;
int i=low;
int j=mid;
while(i<mid&&j<high){
if(array[i]<=array[j]){
extra[x++]=array[i++];
}else{
extra[x++]=array[j++];
}
}
//有一个数组的所有数都被搬完了
while(i<mid){
extra[x++]=array[i++];
}
while(j<high){
extra[x++]=array[j++];
}
//搬移 extra[0,high-low)-->array[low,high)
for(int k=low;k<high;k++){
array[k]=extra[k-low];
}
}
public static void main(String[] args) {
int[] array=new int[]{23,1,45,9,8,5,6,3,10};
mergeNoR(array);
for(int item:array){
System.out.print(item+" ");
}
}
//非递归版本
public static void mergeNoR(int[] array){
int[] extra=new int[array.length];
for(int i=1;i<array.length;i=i*2){
for(int j=0;j<array.length;j+=2*i){
int low=j;
int mid=j+i;
if(mid>=array.length){
//越界
continue;
}
int high=mid+i;
if(high>array.length){
high=array.length;
}
merge(array,low,mid,high,extra);
}
}
}
}
4.复杂度与稳定性分析
时间复杂度:O(n*log(n))
空间复杂度:O(n)-->extra[array.length](统一开辟的) 合并n长 log(n)调用栈 "+"的关系
稳定
三、各排序算法的应用场景
冒泡排序:优化后的冒泡排序可用于当数据基本有序且数据量较少时。
直接插入排序:在待排序的关键字序列基本有序且数据量较少时。
希尔排序:数据量较小且基本有序时。
选择排序:数据规模较小时。
堆排序:处理数据量大的情况,数据呈流式输入时用堆排序也很方便。
归并排序:数据量较大且要求排序稳定时。
快速排序:处理大量数据时。
排序算法的稳定性:
稳定算法:冒泡排序、直接插入排序、归并排序。
不稳定算法:希尔排序、简单选择排序、堆排、快排