七种排序算法总结
排序算法 | 稳定性 | 原地排序 | 时间复杂度 | 空间复杂度 | 特性 |
---|---|---|---|---|---|
简单插入 | 是 | 是 | O(n)~O(n2) | O(1) | 小规模,部分有序速度快 |
希尔 | 否 | 是 | O(n3/2)?O(nlogn )? | O(1) | 取决于元素排列情况 |
简单选择 | 否 | 是 | O(n2) | O(1) | 取决于元素排列情况 |
堆排序 | 否 | 是 | O(nlogn) | O(1) | / |
冒泡 | 是 | 是 | O(n2) | O(1) | / |
快速 | 否 | 是 | O(nlogn) | O(logn) | / |
归并 | 是 | 否 | O(nlogn) | O(n) | / |
插入排序
简单插入排序
算法思想:
- 第一个外循环i遍历数组a,在每一个内循环j中,索引a[j]值与左边有序数列从后往前进行一个个比较,插入到合适的位置,即a[j]<a[j-1]时,交换位置。
- 直到外循环结束。
public class InsertaionSort
{public static void sort(Comparable[] a)
{ int N = a.length ;
int temp = 0;
for(int i=0; i<N; i++) //外循环
{ for(j=i;j>0&&a[j]<a[j-1];j--) //内循环
{ temp = a[j-1]; //交换位置
a[j-1] = a[j];
a[j] = a[j-1];
}
}
}
}
时间空间复杂度:
- 最好的情况:本身有序的情况下,比较了n-1次,没有移动的次数,时间复杂度为O(n)。
- 最坏的情况:本身逆序的情况下,比较了n*(n-1)/2次,时间复杂度为O(n2)。
- 平均的情况:时间复杂度为O(n2)。
- 空间的复杂度: O(1)。
排序特点:
- 是原地排列,具有稳定性。
- 对于部分有序数组十分高效,适合小规模数组。
希尔排序
如果数组的规模变大而变得不有序时,使用插入排序效率很低,而希尔排序就是想将值大的元素往后移动,元素值小的往前移动,从而形成部分有序。希尔排序是交换不相邻的元素,对数组进行局部排序。
算法思想:
- 设定一个h, 将数组等分为h个子数组,例如h=4, 索引为 0,4,8,12…为一组。用插入排序将h个子数组进行独立的排序。排序后,在每个子数组中大元素在后,小元素在前。
- 以一个特定的递减方式,缩小h,如h/3。再循环进行步骤1,直到h=1。
- 这是一个类似于插入排序而使用不同增量的进程。
public class ShellSort
{
public static void sort(Comparable[] a)
{
int N = a.length;
int h = 1;
while(h<N) h=3*h+1;
while(h>=1)
{ for(i=h;i<N;i++)
{ for(j=i;j>=h&&a[j]<a[j-h];j-=h)
{ temp = a[j-1]; //交换位置
a[j-1] = a[j];
a[j] = a[j-1];
}
}
h = h/3;
}
}
时间空间复杂度:
- 时间的复杂度: O(n3/2)。
- 空间的复杂度: O(1)。
排序特点:
- 是原地排列,希尔排序因为是跳跃式记录,故不是一个稳定的排序算法
- 可用于大规模的数据,对于任意数组的表现也很好,比插入和选择排序快很多,数组越大,优势越明显。
选择排序
简单选择排序
算法思想:
数组a=[a1,a2,a3….an], 一共遍历n-1次。
- 第一次遍历中,从a1遍历到an,找出最小值ai,交换ai和a1的位置和数值。
- 第二次遍历中,从a2遍历到an,找出最小值ai,交换ai和a2的位置和数值。
- 以此类推,第n-1次遍历中,从an-1遍历到an,找出最小值ai,交换ai和an-1的位置。
public class SelectionSort
{
public static void sort(Comparable[] a)
{
int N = a.length;
for(int i =0;i<N-1;i++)
{int min_ind = i;
for(int j=i+1;j<N;j++)
{if (a[j]<a[min_ind])
min_ind=j;
}
if(min_ind!=i)
{int temp = a[i]; //交换位置
a[i] = a[min_ind];
a[min_ind] = temp;
}
}
}
}
时间空间复杂度:
- 时间的复杂度: 最好、最坏、平均都为O(n2)。运行时间和输入无关,无论输入是否有序,都需要做遍历找出最小值。
- 空间的复杂度: O(1),只用到了min_ind和temp两个变量。
排序特点:
- 是原地排列,具有不稳定性,因为找出最小值时使用了交换,会打乱顺序
- 数据移动是最少的。一共都用了N次交换,交换次数和数组的大小成线性关系。
堆排序
堆是一个完全二叉树。分为大根堆和小根堆。
大根堆:每个父节点不小于所有子节点。
小根堆:每个父节点不大于所有子节点。
此处以大根堆为例。
注意:我们用长度为N+1的私有数组a[ ]来表示一个大小为N的堆,不会使用a[0]。
堆有序化:
可以使用下沉或者上游的办法进行堆有序化
下沉(sink)
private void sink(Comparable[]a, int k, int N)
{
while (2*k<=N)
{
j= 2*k;
if (j<N &&(a[j]<a[j+1])) j++;
if(a[k]>=a[j]) break;
// 交换
int temp = a[k];
a[k] = a[j];
a[j] = temp;
k = j;
}
}
上游(swim)
private void swim(Comparable[]a, int k, int N)
{
while(k>1)
{
if(a[k]>a[k/2])
{
int temp = a[k/2];
a[k/2] = a[k];
a[k] = temp;
k = k/2;
}
}
}
堆有序化:找到最后一个非叶子节点, 进行sink
for(k=N/2; k>=1;k--) sink(a,k,N);
堆排序算法思想:
- 对数组进行堆有序化。
- 删除最大元素(将最大元素,即根节点。与堆中最后一个元素互换),互换以后,对根节点进行下沉(sink)操作。
- 循环步骤2,共进行n-1次循环后,使得数组变得从小到大排序。
public class HeapSort
{
public static void sort(Comparable[] a)
{ int N=a.length;
for(int k=N/2;k>0;k--)
sink(a,k,N);
while(N>1)
{
exch(a,1,N--);
sink(a,1,N);
}
}
private void swim(Comparable[]a, int k, int N)
{
while(k>1)
{
if(a[k]>a[k/2])
{
int temp = a[k/2];
a[k/2] = a[k];
a[k] = temp;
k = k/2;
}
}
}
private static void exch(Comparable[] a, int j, int j)
{
int temp = a[i];
a[j]= a[i];
a[i] = temp;
}
}
时间空间复杂度:
-
时间的复杂度: 最好、最坏、平均都为O(nlogn)。
步骤1中,有序化,为O(n), 步骤2中,堆平衡为重复n-1次,每次时间为nlogn,得O((n-1)*(nlogn))。所以总的时间复杂度为O(n)+O(nlogn)=O(nlogn) -
空间的复杂度: O(1)
排序特点:
- 是原地排列,具有不稳定性,进行了值的交换。
- 同时最优的利用时间和空间的方法。
- 无法利用缓存,数组元素很少和相邻的元素进行比较。
交换排序
冒泡排序
算法思想:
数组a=[a1,a2,a3….an], 一共遍历n-1次。
- 第一次遍历中,索引j从1到n-1, 比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。索引i+1,循环比较。当到j=n-1时,得到最大数放在an。
- 第二次遍历中,索引j从1到n-2, 比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。索引i+1,循环比较。当到j=n-2时,得到最大数放在an-1。
- 以此类推,第n-1次遍历中,比较a1和a2,如果啊a1>a2, 交换它们位置,否则不变。
public class BubbleSort
{
public static void sort(Comparable[] a)
{ int last_exchange_index = 0;
int N = a.length;
int sortlength = N-1;
// step 1
for(int i = 0; i<N; i++)
{ boolean flag = true ;// 优化判断每次排序是否发生交换
// step 2
for(j=0; j<sortlength; j++)
{ //bubble, compare
if (a[j]>a[j+1])
{ // exchange
int temp = a[j];
a[j] = a[j+1];
a[j+1] = temp;
last_exchange_index = j;//每次排序中最后的交换位置
flag = flase;
}
}
sortlength = last_exchange_index;
if(flag) break;
}
}
}
优化:
- 每个外循环引入一个flag,判断在一次排序中有没有发生交换。
- 引入变量,记录每次排序中最后的交换位置。
时间空间复杂度:
- 时间的复杂度: 最好O(n),最坏O(n2),平均O(n2)
最好的情况:顺序数组,不交换,只遍历n-1次,O(n)。
最好的情况:逆序数组,依次交换,n-1,n-2……,1次。总和为n(n-1)/2,为O(n2)。
平均的情况:n-1到n(n-1)/2,递增求平均。
E(x) = (n-1)n(2n-1)/6/(n-1)= (2n-1)*n/6,故为O(n2)。 - 空间的复杂度: O(1),只用了几个变量。
排序特点:
- 是原地排列,具有稳定性,虽然有值的交换,但是设定的是等号不交换。
快速排序
选基数切分+分治的思想。
算法思想:
- 选一个数为基数x(一般是第一个数或者中间数,a[initial])。
- 数组a中从左往右遍历(i++),找到第一个大于基数x的数a[i]; 从右往左找到(j–)第一个小于基数x的数a[j] 。交换a[i]和a[j]。
- 继续遍历i,j,重复步骤2,直到结束条件i=j达到。交换a[initial],a[j]。到这里就完成了一个切分的所有过程。
- 对于基数的左边和右边区域切分,重复步骤1,2,3。直到每个部分只有一个元素为止。
public class QuickSort
{
public static void sort(Comparable[] a)
{sort(a,0,a.length-1);}
// 分治 递归 主函数
private static void sort(Comparable[] a, int lo, int hi)
{
if(hi<=lo) return;
int j= partition(a,lo,hi);
sort(a,0,j);
sort(a,j+1,hi);
}
// 选基数 切分
private static int partition(Comparable[] a, int lo, int hi)
{
int i=l0;
int j=hi;
int key = a[lo];
while(true)
{ // 从左往右找到第一个大于key的数
while(a[i]<key) i++;
// 从右往左找到第一个小于key的数
while(a[j]>=key) j--;
// 循环终止条件,i,j相遇
if (i>=j) break;
// 交换
exch(a,i,j)
}
exch(a,io,j);
return j;
}
private static void exch(Comparable[] a, int j, int j)
{
int temp = a[i];
a[j]= a[i];
a[i] = temp;
}
}
非递归的方式:待续。。。。。。
时间空间复杂度:
- 时间的复杂度:
最好的情况:每次都是对半分,O(nlogn)
最好的情况:退化成冒泡,O(n2)。
平均的情况:O(nlogn) - 空间的复杂度: O(logn),递归的深度。
排序特点:
- 是原地排列,具有不稳定性,有值的交换。
- 内循环简洁,在内循环中移动数据。速度快。
归并排序
算法思想:
假设初始序列含有n个记录,则可以看成是n个能有序的子序列,每个子序列的长度为1,然后两两归并,形成有序子序列,再两两归并,…,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为两路归并排序。
public class MergeSort
{
private static Comparable[] aux;
public stativc void sort(Comparable[] a)
{
aux = new Comparable[a.length];
sort(a,0,a.length-1);
}
private static void sort(Comparable[] a,int lo, int hi)
{
if(hi>=lo) return;
int mid= lo+(hi-lo)/2;
sort(a,lo,mid);
sort(a,mid,hi);
merge(a,lo,mid,hi);
}
private static void merge(Comparable[] a,int lo, int mid, int hi)
{
int i=lo,j=mid+1;
for(int k=lo; k<=hi, k++)
aux[k] = a[k];
for(int k=l0,k<=hi, k++)
{
if (i>mid) a[k] = aux[j++];
else if (j<mid) a[k] = aux[i++];
else if (a[i]<a[j]) a[k] = a[i++];
else a[k] = a[j++];
}
}
}
迭代
public class MergeSort
{
private static Comparable[] aux;
public stativc void sort(Comparable[] a)
{
aux = new Comparable[a.length];
sort(a);
}
private static void sort(Comparable[] a)
{
int N=a.length;
for(int size = 1;size<N;size =size+size)
{
for(int i =0;i<N-size;i+=2*size)
merge(a,i,i+size-1,min(i+2*size-1,N-1));
}
}
private static void merge(Comparable []a, int lo, int mid, int hi)
{见上}
}
时间空间复杂度:
- 最好、最坏、平均都是O(nlogn)
有log2n层,一层时间O(n),得到时间复杂度O(nlogn) - 空间的复杂度: O(n),递归的深度。
排序特点:
- 不是原地排列,具有稳定性。
至此,本人整理的七大排序算法已全部完成,因本人水平希望大家给予批评指正,共同进步~
如果觉得这文章还算用心,请劳驾点个赞留言,这是对我做开源分享的最大的肯定,谢谢。