常见排序算法分为四类:插入排序、选择排序、交换排序、归并排序
插入排序分为:直接插入排序、希尔排序
选择排序:选择排序、堆排序
交换排序:冒泡排序、快速排序
归并排序:归并排序
1.直接插入排序
基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
/**
* 直接插入排序
* 时间复杂度:O(N^2) 逆序 O(n) 有序
* 空间复杂度:O(1)
* 稳定性:稳定
* @param array
*/
public static void insertSort(int[] array){
for(int i=1;i<array.length;i++){
int tmp=array[i];
int j=i-1;
for(;j>=0;j--){
if(tmp<array[j]){
array[j+1]=array[j];
} else{
break;
}
}
array[j+1]=tmp;
}
}
2.希尔排序
基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
/**
* 希尔排序
* 空间复杂度:O(N)
* 稳定性:不稳定
*/
public static void shellSort(int[] array){
int gap=array.length;
while (gap>1){
gap=gap/2;
shell(array,gap);
}
shell(array,1);
}
/**
* 跳跃的直接插入排序
*/
public static void shell(int[] array,int gap){
for(int i=gap;i<array.length;i++){//从gap开始依次排序
int tmp=array[i];
int j=i-gap;
for(;j>=0;j-=gap){
if(tmp<array[j]){
array[j+gap]=array[j];
} else{
break;
}
}
array[j+gap]=tmp;
}
}
3.选择排序
其基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
/**
* 选择排序
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
public static void selectSort(int[] array){
for(int i=0;i<array.length;i++){
int minindex=i;//记录最小值的下标
for(int j=i+1;j<array.length;j++){
if(array[j]<array[minindex]){
minindex=j;
}
}
swap(array,minindex,i);
}
}
public static void swap(int[] array,int i,int j){
int tmp=array[i];
array[i]=array[j];
array[j]=tmp;
}
4.堆排序
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
/**
*堆排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(1)
* 稳定性:不稳定
* @param array
*/
public static void heapSort(int[] array){
//升序创建大根堆
createHeap(array);
//排序
int end=array.length-1;
while (end>0){
swap(array,0,end);
shiftDown(array,0,end);
end--;
}
}
private static void createHeap(int[] array){
for(int parent=(array.length-1-1)/2;parent>=0;parent--){
shiftDown(array,parent,array.length);
}
}
private static void shiftDown(int[] array,int parent,int len){
int child=parent*2+1;
while (child<len) {
if (child + 1 < len &&array[child]<array[child+1]){
child++;
}
if(array[child]>array[parent]){
swap(array,child,parent);
parent=child;
child=parent*2+1;
}else {
break;
}
}
}
5.冒泡排序
基本思想是按趟进行比较,每一趟都是对相邻记录的关键字值进行比效,如果是逆顺(array[j]>array[j+1]),则将其交换,一趟结束后都将这趟比较的关键字值该中序列最小的或最大的排在最后,最终达到有序化,比如5个字符只需要比较4趟即可有序
/**
* 冒泡排序
* 时间复杂度:未优化O(N^2) 优化后可达到O(n)
* 空间复杂度:O(n)
* 稳定性:稳定
* @param array
*/
public static void bubbleSort(int[] array){
for(int i=0;i<array.length-1;i++){
boolean flg=false;//用于优化代码
for(int j=0;j<array.length-1-i;j++){
if(array[j]>array[j+1]){
swap(array,j,j+1);
flg=true;//发生交换后将flg置为true
}
}
if(flg==false){//如果flg仍是false,说明该趟并为发生交换,即可证明该序列已经有序
break;
}
}
}
6.快速排序
基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
/**
* 快速排序
* 时间复杂度:O(N^2) 最好O(n*logn)
* 空间复杂度:O(n) 最好O(logn)
* 稳定性:不稳定
* @param array
*/
public static void quickSort(int[] array){
quick(array,0,array.length-1);
}
//三数取中法,用于优化,提高效率
private static int threeMid(int[] array,int left,int right){
int mid=(left+right)>>>1;
if(array[left]<array[right]){//左<右
if(array[mid]<array[left]){//中<左
return left;
}else if(array[mid]<array[right]){//中<右,左<中
return mid;
}else{//中>右,左<中
return right;
}
}else{//左>右
if(array[mid]<array[right]){//中<右
return right;
}else if(array[mid]<array[left]){//左>中,右<中
return mid;
}else{//左<中,右<中
return left;
}
}
}
private static void quick(int[] array,int start,int end){
if(start>=end){
return;
}
int index=threeMid(array,start,end);
swap(array,index,start);
int pivot=partition(array,start,end);
quick(array,start,pivot-1);
quick(array,pivot+1,end);
}
private static int partition(int[] array,int left,int right){
int tmp=array[left];
while (left<right){
while (right>left&&array[right]>=tmp){
right--;
}
//右边找到小于tmp的数
array[left]=array[right];
while (left<right&&array[left]<=tmp){
left++;
}
//左边找到大于tmp的数
array[right]=array[left];
}
array[left]=tmp;
return left;
}
/**
* 非递归实现快速排序
* @param array
*/
public static void quickSort2(int[] array){
Stack<Integer> stack=new Stack<>();
int start=0;
int end=array.length-1;
int pivot=partition(array,start,end);
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
if(pivot>start+1){
stack.push(start);
stack.push(pivot-1);
}
while (!stack.empty()){
end=stack.pop();
start=stack.pop();
pivot=partition(array,start,end);
if(pivot<end-1){
stack.push(pivot+1);
stack.push(end);
}
if(pivot>start+1){
stack.push(start);
stack.push(pivot-1);
}
}
}
7.归并排序
该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。
/**
* 归并排序
* 时间复杂度:O(N*logN)
* 空间复杂度:O(N)
* 稳定性:稳定
* @param array
*/
public static void mergeSort(int[] array){
mergeSortFunction(array,0,array.length-1);
}
private static void mergeSortFunction(int[] array, int low,int high) {
if(low>=high){
return;
}
int mid = (low + high) >>> 1;
//分解
mergeSortFunction(array,low,mid);
mergeSortFunction(array,mid+1,high);
//合并
merge(array,low,high,mid);
}
private static void merge(int[] array,int low,int high,int mid){
int[] tmp=new int[high-low+1];
int k=0;
int s1=low;
int e1=mid;
int s2=mid+1;
int e2=high;
while (s1<=e1&&s2<=e2){
if(array[s1]<array[s2]){
tmp[k++]=array[s1++];
}else{
tmp[k++]=array[s2++];
}
}
while (s1<=e1){
tmp[k++]=array[s1++];
}
while (s2<=e2){
tmp[k++]=array[s2++];
}
for(int i=0;i<k;i++){
array[i+low]=tmp[i];
}
}
排序算法复杂度及稳定性分析
排序方法 | 最好 | 平均 | 最坏 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
希尔排序 | O(n) | O(n^1.3) | O(n^2) | O(1) | 不稳定 |
堆排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(1) | 不稳定 |
快速排序 | O(n * log(n)) | O(n * log(n)) | O(n^2) | O(log(n)) ~ O(n) | 不稳定 |
归并排序 | O(n * log(n)) | O(n * log(n)) | O(n * log(n)) | O(n) | 稳定 |