排序方法 | 时间复杂度(平均) | 时间复杂度(最坏) | 时间复杂度(最好) | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
选择排序 | O(n2) | O(n2) | O(n2) | O(1) | 不稳定 |
插入排序 | O(n2) | O(n2) | O(n) | O(1) | 稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
快速排序 | O(nlogn) | O(n2) | O(nlogn) | O(nlogn) | 不稳定 |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
希尔排序 | O(n2) | O(n2) | O(n) | O(1) | 不稳定 |
1.冒泡排序
过程
- 一开始交换的区间为[1,n-1],然后时第一个数和第二个数比较,哪个大就互换到后面。然后第二个数和第三个数比较,哪个大就互换到后面。依次比较并交换,最后最大的数被放到最右边。
- 然后把范围区间缩小为[1,n-2],重复以上过程,直到范围缩小到1,完成排序。
复杂度
- 最好 O(n)
- 最好的情况下,就是正序,所以只要比较一次就行了,复杂度O(n)
- 最坏 O(n2)
- 最坏的情况下,就是逆序,要比较n2次才行,复杂度O(n2)
- 平均O(n2)
总结:时间复杂度O(n2),空间复杂度O(1),当待排序列有序时,效果比较好。
稳定性
- 稳定
比较时,相等的两数不交换位置。
示例代码
import java.util.*;
public class BubbleSort {
public int[] bubbleSort(int[] A, int n) {
// write code here
int k=n-1;
//int pos=n-1;
int pos=0;
for(int i=n-2;i>=0;i--){
for(int j=0;j<k;j++){
if(A[j]>A[j+1]){
swap(A,j,j+1);
pos=j;
}
}
k=pos;
}
return A;
}
public void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
2.选择排序
过程
- 在范围[0,n-1]中选择最小的数,和第一个数交换,在范围[1,n-1]中选择最小的数,和第二个数交换。这样依次直到最后范围只剩一个数时,整个数组变成有序了。
复杂度
- O(n2),和序列的初始状态无关
总结:时间复杂度O(n2),无论最好还是最坏。空间复杂度是O(1)
稳定性
- 不稳定
因为可能需要和范围内最小值交换位置,相等的两个数初始时的相对位置到最后可能改变。
示例代码
import java.util.*;
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
// write code here
int minIndex=0;;
for(int i=0;i<n;i++){
minIndex=i;
for(int j=i+1;j<n;j++){
if(A[j]<A[minIndex]){
minIndex=j;
}
}
swap(A,minIndex,i);
}
return A;
}
public void swap(int[] arr,int a,int b){
int t=arr[a];
arr[a]=arr[b];
arr[b]=t;
}
}
3.插入排序
过程
- 位置1上的数和位置0上的数比较,如果位置1上的数小,则和位置0上的数交换。
- 位置2上的数a依次与位置1、0的数比较,如果1上的数比a大,则往后移一位,a继续依次和前面的数比较,直到找到前面的数小于等于a,把a放入找到的位置。
复杂度
-
最好O(n)
-
最坏O(n2)
-
平均O(n2)
-
直接插入排序法在最好情况下(待排序列已按关键码有序),每趟排序只需作1次比较而不需要移动元素。所以n个元素比较次数为n-1,移动次数0。
-
最差的情况下(逆序),其中第i个元素必须和前面的元素进行比较i次,移动个数i+1
总结:时间复杂度O(n2),排序过程中只要一个辅助空间,所以空间复杂度O(1)
稳定性
- 稳定
相等的两个数相对位置不变。
示例代码
import java.util.*;
public class InsertionSort {
public int[] insertionSort(int[] A, int n) {
// write code here
for(int i=1;i<n;i++){
int j=i-1;
int temp=A[i];
for(;j>=0;j--){
if(temp<A[j]){
A[j+1]=A[j];
}else{
break;
}
}
A[j+1]=temp;
}
return A;
}
public void swap(int[] arr,int a,int b){
int t=arr[a];
arr[a]=arr[b];
arr[b]=t;
}
}
4.归并排序
过程
- 首先让数组中的每一个数单独成为长度为1的区间,然后两两一组有序合并,得到长度为2的有序区间,依次进行,直到合成整个区间。
复杂度
- 最好O(nlogn)
- 最坏O(nlogn)
- 平均O(nlogn)
一趟归并需要将待排序列中的所有记录扫描一遍,因此耗费时间为O(n),而由完全二叉树的深度可知,整个归并排序需要进行[log2n],因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中平均的时间性能
空间复杂度:由于归并过程中需要与原始记录序列同样数量级的 存储空间去存放归并结果及递归深度为log2N的栈空间,因此空间复杂度为O(n+logN) 也就是说,归并排序是一种比较占内存,但却效率高且稳定的算法。
稳定性
- 稳定
示例代码
import java.util.*;
public class MergeSort {
public int[] mergeSort(int[] A, int n) {
// write code here
if(A==null||A.length<2){
return null;
}
__mergeSort(A,0,n-1);
return A;
}
public void __mergeSort(int arr[],int l,int r){
if(l==r){
return;
}
int mid=(l+r)/2;
__mergeSort(arr,l,mid);
__mergeSort(arr,mid+1,r);
__merge(arr,l,mid,r);
}
public void __merge(int[] arr,int left,int mid,int right){
int[] aux=new int[right-left+1];
int l=left;
int r=mid+1;
int i=0;
while (l<=mid&&r<=right){
if(arr[l]<=arr[r]){
aux[i]=arr[l];
l++;
}else {
aux[i]=arr[r];
r++;
}
i++;
}
while (l<=mid){
aux[i]=arr[l];
l++;
i++;
}
while (r<=right){
aux[i]=arr[r];
r++;
i++;
}
for( i=0;i<aux.length;i++){
arr[i+left]=aux[i];
}
}
}
5.快速排序
过程
- 在数组中随机选一个数(默认数组首个元素),数组中小于等于此数的放在左边,大于此数的放在右边,再对数组两边递归调用快速排序,重复这个过程。
复杂度
- 最好O(nlogn)
- 最坏O(n2)
- 平均O(nlogn)
稳定性
- 不稳定
示例代码
import java.util.*;
public class QuickSort {
public int[] quickSort(int[] A, int n) {
// write code here
if(A!=null||A.length>1){
__quickSort(A,0,n-1);
}
return A;
}
public void __quickSort(int[] arr,int left,int right){
if(left>=right){
return;
}
int random= (int) (left+Math.random()*(right-left+1));
swap(arr,random,right);
int mid=partition(arr,left,right);
__quickSort(arr,left,mid-1);
__quickSort(arr,mid+1,right);
}
//划分过程
public int partition(int[] arr,int left,int right) {
int i=left;
int pivot=left-1;
while (i<=right){
if(arr[i]<=arr[right]){
swap(arr,i,++pivot);
}
i++;
}
//i=right时,swag(arr,pivot,right)把基准数arr[right]换到左边小于等于堆右第一个
return pivot;
}
public void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
6.堆排序
过程
- 首先将数组元素建成大小为n的大顶堆,堆顶(数组第一个元素)是所有元素中的最大值,将堆顶元素和数组最后一个元素进行交换,再将除了最后一个数的n-1个元素建立成大顶堆,再将最大元素和数组倒数第二个元素进行交换,重复直至堆大小减为1。
- 大顶堆
大顶堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值。
即,根节点是堆中最大的值,按照层序遍历给节点从1开始编号,则节点之间满足如下关系:
这里写图片描述
(1<=i<=n/2)
复杂度
- 最好O(nlogn)
- 最坏O(nlogn)
- 平均O(nlogn)
稳定性
- 不稳定
示例代码
void heapSort(int array[], int n)
{
int i;
for (i=n/2;i>0;i--)
{
HeapAdjust(array,i,n);//从下向上,从右向左调整
}
for( i=n;i>1;i--)
{
swap(array, 1, i);
HeapAdjust(array, 1, i-1);//从上到下,从左向右调整
}
}
void HeapAdjust(int array[], int s, int n )
{
int i,temp;
temp = array[s];
for(i=2*s;i<=n;i*=2)
{
if(i<n&&array[i]<array[i+1])
{
i++;
}
if(temp>=array[i])
{
break;
}
array[s]=array[i];
s=i;
}
array[s]=temp;
}
void swap(int array[], int i, int j)
{
int temp;
temp=array[i];
array[i]=array[j];
array[j]=temp;
}
7.希尔排序
过程
- 是插入排序改良的算法,希尔排序步长从大到小调整,第一次循环后面元素逐个和前面元素按间隔步长进行比较并交换,直至步长为1,步长选择是关键。
复杂度
- 最好O(n)
- 最坏O(n2)
- 平均O(n2)
稳定性
- 稳定
示例代码
import java.util.*;
public class ShellSort {
public int[] shellSort(int[] A, int n) {
// write code here
int gap=n/2;
for(;gap>0;gap/=2){
for(int i=gap;i<n;i++){
for(int j=i;j-gap>=0&&A[j]<A[j-1];j-=gap){
swap(A,j-1,j);
}
}
}
return A;
}
public void swap(int[]a,int i,int j){
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
参考:https://blog.csdn.net/yushiyi6453/article/details/76407640