选择排序
思路
首先,找到数组中最小的那个元素;其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素那么它就和自己交换)。再次,在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,直到将整个数组排序。
特点
运行时间和输入无关。为了找出最小的元素而扫描一遍数组并不能为下一遍扫描提供什么信息。一个已经有序的数组或是主键全部相等的数组和一个元素随机排列的数组所用的排序时间是一样的。
数据移动是最少的。每次交换都会改变两个数组元素的值,因此选择排序用了N次交换——交换次数和数组的大小是线性关系。
代码
public class Selection
{
public static void sort(Comparable[] a)
{
int N=a.length();
for(int i=0;i<N;i++)
{
int min=i;
for(int j=i+1;j<N;j++)
{
if(less(a[j],a[min]))
min=j;
}
exch(a,i,min);
}
}
}
选择排序是不稳定的排序方法,时间复杂度为O(n2)。
插入排序
思路
通常人们整理桥牌的方法是一张一张的来,将每一张牌插入到其他已经有序的牌中的适当位置。在计算机的实现中,为了给要插入的元素腾出空间,我们需要将其余所有元素在插入之前都向右移动一位,这种算法叫插入排序。
特点
与选择排序一样,当前索引左边的所有元素都是有序的,但它们的最终位置还不确定,为了给更小的元素腾出空间,它们可能会被移动,但是当索引到达数组的右端时,数组排序就完成了。
插入排序所需的时间取决于输入中元素的初始顺序,对一个很大且其中的元素已经有序的数组进行排序将会比对随机顺序的数组或是逆序数组进行排序要快得多。
代码
public class Insertion
{
public static void sort(Comparable[] a)
{
int N=a.length();
for(int i=1;i<N;i++)
{
for(int j=i;j>0&&less(a[j],a[j-1]);j--)
{
exch(a,j,j-1);
}
}
}
}
插入排序是稳定的排序方法,时间复杂度为O(n2)。
希尔排序
思路
希尔排序的思想是使数组中任意间隔为h的元素都是有序的。这样的数组被称为h有序数组。一个h有序数组就是h个互相独立的有序数组编制在一起组成的一个数组。在进行排序时,如果h很大,我们就能将元素移动到很远的地方,为实现更小的h有序创造方便。使用这种方式,对于任意以1结尾的h序列,我们都能够将数组排序。
特点
希尔排序权衡了子数组的规模和有序性。在排序之初,每个子数组都很短,排序之后的子数组都是部分有序的,这两种情况都很适合插入排序。子数组部分有序的程度取决于递增序列的选择。
代码
public class Shell
{
public static void sort(Comparable[] a)
{
int N=a.length();
int h=1;
while(h<N/3)
h=3*h+1;
while(h>=1)
{
for(int i=h;i<N;i++)
{
for(int j=i;j>=h&&less(a[j],a[j-h]);j-=h)
{
exch(a,j,j-h);
}
}
h=h/3;
}
}
}
希尔排序是不稳定的排序算法,时间复杂度为O(n1.3-2)。
归并排序
思路
将两个有序的数组归并成一个更大的有序数组。要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。
特点
它能够保证将任意长度为N的数组排序所需时间和NlogN成正比;但是它所需的额外空间和N成正比。
原地归并
public 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=lo;k<=hi;k++)
{
if(i>mid)
a[k]=aux[j++];
else if(j>hi)
a[k]=aux[i++];
else if(less(aux[j],aux[i]))
a[k]=aux[j++];
else
a[k]=aux[i++];
}
}
自顶向下的归并排序
public class Merge
{
private static Comparable[] aux;
public static 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+1,hi);
merge(a,lo,mid,hi);
}
}
自底向上的归并排序
public class MergeBU
{
private static Comparable[] aux;
public static void sort(Comparable[] a)
{
int N=a.length();
aux=new Comparable[N];
for(int sz=1;sz<N;sz=sz+sz)
for(int lo=0;lo<N-sz;lo+=sz+sz)
merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1));
}
}
归并排序是稳定的排序算法,时间复杂度是O(NlogN)。
快速排序
思路
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立地排序。快速排序将数组排序的方式是当两个子数组都有序时整个数组也就自然有序了,递归调用发生在处理整个数组之后,切分的位置取决于数组的内容。
特点
快速排序是原地排序且将长度为N的数组排序所需的时间和NlgN成正比。快速排序的内循环比大多数排序算法都要短小。
主要缺点是非常脆弱,在实现时要非常小心才能避免低劣的性能。
代码
public class Quick
{
private static int partition(Comparable[] a,int lo,int hi)
{
int i=lo,j=hi+1;
Comparable v=a[lo];
while(true)
{
while(less(a[++i],v))
if(i==hi)
break;
while(less(v,a[--j]))
if(j==lo)
break;
if(i>=j)
braek;
exch(a,i,j);
}
exch(a,lo,j);
return j;
}
public static void sort(Comparable[] a)
{
StdRandom.shuffle(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,lo,j-1);
sort(a,j+1,hi);
}
}
快速排序是不稳定的排序算法,时间复杂度为O(NlogN)。