排序算法总结
选择排序
算法流程:
- 首先找到数组中最小的元素
- 将这个最小的元素与数组首位元素交换位置
- 在剩下的元素中找到最小的元素,并与数组第二个元素交换位置
- 如此往复直到整个数组排序
算法分析:
- 时间复杂度: N 2 N^2 N2
- 空间复杂度: 1 1 1
- 不稳定
- 原地排序
/**
* 选择排序,一共进行N次交换,(N-1)+(N-2)+...+2+1~N^2/2次比较
* 缺点:已经排列有序的数组与元素全部相等的数组所花的时间一样长
*
* @param a
*/
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);
}
}
插入排序
思想:如果数组部分有序,那么只需把新元素插入到有序部分,为了给插入元素腾出空间需要将其余元素的位置向右移动一位。
算法流程:
- 一开始使用一个指针
i、j
指向数组第二个元素,此时指针左侧元素为有序 - 将
a[j]
与a[j-1]
比较,如果a[j]
小于a[j-1]
则交换位置,递减j
直到j
指向数组首元素或a[j]
比a[j-1]
大为止 - 递增
i
更新j
并重复第二步,当i
到达数组尾部时排序完成
算法分析:
- 时间复杂度: N 和 N 2 N和N^2 N和N2之间
- 空间复杂度:1
- 稳定
- 原地排序
/**
* 插入排序
* 对于 1到N-1之间的每一个i,将a[i]与a[0]到a[i-1]中比它大的元素有序交换,
* 在索引i从左向右变化过程中它的左侧元素总是有序的,所以i达到数组右端后排序就完成了
* @param a
*/
public static void sort(Comparable[] a){
int N = a.length;
for(int i = 1; i < N; i++){
//将a[i]插入到a[i-1]、a[i-2]、a[i-3]...之中
for(int j = i; j > 0 && less(a[j], a[j-1]); j--){//最坏情况需要1+2+3+...+N-1~N^2/2次比较和交换,最好情况下需要N-1次比较0次交换
exch(a, j, j - 1);
}
}
}
希尔排序
思想:如果一个数组是部分有序的,那么对这个数组进行插入排序就会减少很多操作。希尔排序就是的思想就是使数组任意间隔h都是有序的。
算法流程:
- 构造一个递增序列作为间隔,该序列从1开始不断递增,但是不能超过数组长度的1/3
- 一开始以最大的间隔向右遍历,符合条件则交换两头元素
- 将间隔h按1/3缩小,向右遍历,符合条件交换元素
- h=1后在遍历一遍则算法结束
算法分析:
- 时间复杂度与递增序列有关
- 比插入排序与选择排序快的多,数组越大优势越大
- 适合中等大小的数组
- 不稳定
- 原地排序
public static void sort(Comparable[] a){
int N = a.length;
int h = 1;//间隔h
while(h < N/3) h = 3 * h + 1;// 1, 4, 13, 40, 121, 364, 1093,..... 生成递增序列
while(h >= 1){
for(int i = h; i < N; i++){
//对h间隔的数组插入排序,形成有序数组
for(int j = i; j >= h && less(a[j], a[j-h]); j-=h){
exch(a, j, j - h);
}
}
h /= 3;//缩小间隔直到间隔为1
}
}
归并排序
思想:两个有序数组可以通过归并变为更大的有序数组。如果有一个无序数组可以分为左子数组a[lo...mid]
和右子数组 a[mid+1...hi]
,如果这两个数组是有序的那个可以归并为大的有序数组,而左子数组和右子数组又可以划分为更小的子数组,划分到最后只剩下单个元素的数组,其本身就是有序的,因此可以使用递归利用归并操作排序。
算法分析:
- 时间复杂度: NlgN
- 空间复杂度:N
- 稳定
- 不是原地排序
自顶向下
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);
}
/**
* 将两个有序数组进行归并
* @param a
* @param lo
* @param mid
* @param hi
*/
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[i], aux[j])) a[k] = aux[i++];
else a[k] = aux[j++];
}
/**
* 判断v是否小于w
* @param v
* @param w
* @return
*/
private static boolean less(Comparable v, Comparable w){
return v.compareTo(w)<0;
}
private static void exch(Comparable[] a, int i, int j){
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
private static void show(Comparable[] a){
for(int i = 0; i < a.length; i++){
StdOut.print(a[i] + " ");
}
StdOut.println();
}
public static boolean isSorted(Comparable[] a){
//测试数组元素是否有序
for(int i = 1; i < a.length; i++){
if(less(a[i],a[i-1]))return false;
}
return true;
}
public static void main(String[] args){
String[] a = In.readStrings();
sort(a);
assert isSorted(a);
show(a);
}
}
自底向上
先两两归并,再四四归并(大小为2的数组归并为一个有四个元素的数组),然后八八归并,一直下去。
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[N];
for(int sz = 1; sz < N; sz = 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));//设置边界条件
}
/**
* 将两个有序数组进行归并
* @param a
* @param lo
* @param mid
* @param hi
*/
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[i], aux[j])) a[k] = aux[i++];
else a[k] = aux[j++];
}
快速排序
思想:快速排序的思想在于切分,每次切分都会排定数组中一个元素a[j],使得a[lo]
到a[j-1]
都小于a[j]
,a[j+1]
到a[hi]
中的所有元素都不小于a[j]。通过递归的思想将数组左部分不断切分,每次排定一个元素,再将右半部分不断切分排定一个元素,最后排序完成。
算法分析:
- 时间复杂度:NlgN
- 空间复杂度:lgN
- 不稳定
- 原地排序
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); //将左半部分a[lo...j-1]排序
sort(a, j+1, hi); //将右半部分a[j+1...hi]排序
}
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;//从数组左端向右扫描直到找到一个大于等于v的元素
while(less(v, a[--j])) if(j == lo) break;//从数组右端向左扫描直到找到一个小于等于v的元素
if(i >= j) break;
exch(a, i, j);
}
exch(a, lo, j); //将切分元素放入正确位置
return j; //切分完成
}
堆排序
堆结构是一个高效的数据结构。引入堆排序首先讲的是优先队列,优先队列可以实现删除最大数据与插入数据两种操作。
实现优先队列可以使用有序或者无序数组(链表)来实现。使用这两种数据结构需要在插入数据的时候就排序(未雨绸缪)或者当要删除数据的时候进行查找将最大的数据删除(惰性),这种插入和操作都需要线性时间完成。而使用数据结构二叉堆只需要在对数时间的完成插入和删除操作。
用数组表示二叉堆可以使用完全二叉树,将根节点放在位置1,子节点放在位置2,3,子节点的子节点放在4,5与6、7以此类推。这样就有一个好处可以通过计算数组索引在这颗二叉树上下移动。假如当前节点为a[k]
去它的上一层只需要访问a[k/2]
,去它的下一层只需要访问a[2k]
、a[2k+1]
。如果二叉树每个节点比其子节点大则称为堆有序。
因为堆有序,所以每次删除堆根节点的元素就是删除了最大元素,这时破坏了堆的有序结构所以需要使用下沉操作,重新使堆保持有序。如果尾部插入了一个元素也可能破坏堆的有序结构这时候需要上浮操作来维持堆有序。
显而易见,由于堆的有序结构每次取出根节点元素再维持堆有序就很容易实现排序。
算法流程 :
- 使用下沉方法构造堆,使堆有序(只需扫描一半的元素)
- 将根节点与最后一个节点交换,堆的大小减一,使用下沉操作保持堆有序
- 当堆削减为一个元素后排序完成
算法分析:
- 时间复杂度:NlgN
- 空间复杂度:1
- 不稳定
- 原地排序
public class Heap {
private static void sink(Comparable[] a, int k, int N){
while(2*k <= N){
int j = 2 * k;//下一层的节点
if(j < N && less(a,j,j+1))j++;//将较大节点的换上去,以维护堆有序
if(!less(a, k, j))break;
exch(a, k, j);
k = j;//指向下沉后的节点,以便下一次循环判断是否继续下沉
}
}
private static boolean less(Comparable[] a, int i, int j){
return a[i-1].compareTo(a[j-1])<0;
}
private static void exch(Comparable[] a, int i, int j){
Comparable t = a[i-1]; a[i-1] = a[j-1]; a[j-1] = t;
}
public static void sort(Comparable[] a){
int N = a.length;//二叉堆的节点数
//第一步构造堆有序
for (int k = N/2; k >= 1; k--){
sink(a, k, N);
}
//第二步下沉排序
while(N > 1){
exch(a, 1, N--);//将最大的元素放在数组最后,堆的节点减少1
sink(a, 1, N);//下沉操作维持堆有序
}
}
private static void show(Comparable[] a) {
for (int i = 0; i < a.length; i++) {
StdOut.print(a[i]+" ");
}
}
public static void main(String[] args){
String[] strs = StdIn.readAllStrings();
Heap.sort(strs);
show(strs);
}
}
参考:
- Algorithm 第4版
- https://www.cnblogs.com/onepixel/articles/7674659.html