// 插入排序:順序地把待排序的數據按關鍵字大小插入到已排序的子集合的適當位置
// 插入排序對於部分有序數組和小規模數組特別高效
void insertSort(int[] A, int n) {
for (int i = 1; i < n; i++) {
int tmp = A[i];
int j = i;
for (; j > 0 && A[j - 1] > tmp; j--) {
A[j] = A[j - 1];
}
A[j] = tmp;
}
}
// 希爾排序:把待排序的數據元素分成若干個小組,對同一組內使用插入法排序
// 距離逐漸減小,直到只比較相鄰元素的最後一趟爲止,因此也稱縮小增量排序
// 希爾排序的運行時間達不到平方級別
void shellSort(int[] A, int n) {
for (int increment = n / 2; increment > 0; increment /= 2) {
for (int i = increment; i < n; i++) {
int tmp = A[i];
int j = i;
for (; j >= increment; j -= increment) {
if (tmp < A[j - increment]) {
A[j] = A[j - increment];
} else {
break;
}
}
A[j] = tmp;
}
}
}
// 選擇排序:維護一個已排序集合,從未排序集合中選擇最小值與第一個元素交換
// 然後增大已排序集合範圍,直到最後一個元素
// 它的運行時間與輸入無關
void selectSort(int[] A, int n) {
for (int i = 0; i < n - 1; i++) {
int small = i;
for (int j = i + 1; j < n; j++) {
if (A[j] < A[small]) {
small = j;
}
}
if (small != i) {
swap(A, i, small);
}
}
}
// 冒泡排序:將大元素依次沉底,然後縮小數據元素範圍.有些待排序元素序列已經基本有序
// 優化:可以設一個flag變量標記本次是否有交換動作,提前結束排序過程
void bubbleSort(int[] A, int n) {
boolean flag = true;
for (int i = 1; i < n && flag; i++) {
flag = false;
for (int j = 0; j < n - i; j++) {
if (A[j] > A[j + 1]) {
flag = true;
swap(A, j, j + 1);
}
}
}
}
// 歸併排序:思想是將數組分成兩部分,分別進行排序,然後歸併起來
static int[] aux;
public void merge(int[] 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 (aux[i] < A[j]) {
A[k] = aux[i++];
} else {
A[k] = aux[j++];
}
}
}
// 自頂向下
public void mergeSort(int[] A, int n) {
aux = new int[n];
mergeSort(A, 0, n - 1);
}
public void mergeSort(int[] A, int lo, int hi) {
if (hi <= lo) return;
int mid = lo + (hi - lo) / 2;
mergeSort(A, lo, mid);
mergeSort(A, mid + 1, hi);
merge(A, lo, mid, hi);
}
// 快速排序:通過一個切分元素將數組分爲兩個子數組,左子數組小於等於切分元素
// 右子數組大於等於切分元素,將這兩個子數組排序也就將整個數組排序了
public void quickSort(int[] A, int n) {
quickSort(A, 0, n - 1);
}
public void quickSort(int[] A, int lo, int hi) {
if (hi <= lo) return;
int j = partition(A, lo, hi);
quickSort(A, lo, j - 1);
quickSort(A, j + 1, hi);
}
/*取 a[lo] 作爲切分元素,然後從數組的左端向右掃描直到找到第一個大於等於它的元素
再從數組的右端向左掃描找到第一個小於等於它的元素,交換這兩個元素,並不斷繼續這個過程
就可以保證左指針的左側元素都不大於切分元素,右指針 j 的右側元素都不小於切分元素
當兩個指針相遇時,將切分元素 a[lo] 和左子數組最右側的元素 a[j] 交換然後返回 j 即可*/
public int partition(int[] A, int lo, int hi) {
int i = lo, j = hi + 1;
int v = A[lo];
while (true) {
while (A[++i] < v) {
if (i == hi) {
break;
}
}
while (A[--j] > v) {
if (j == lo) {
break;
}
}
if (i >= j) break;
swap(A, i, j);
}
swap(A, lo, j);
return j;
}
堆排序:由於堆可以很容易得到最大的元素並刪除它,不斷地進行這種操作可以得到一個遞減序列
如果把最大元素和當前堆中數組的最後一個元素交換位置,並且不刪除它,那麼就可以得到一個從尾到頭的遞減序列,從正向來看就是一個遞增序列
因此很容易使用堆來進行排序,並且堆排序是原地排序,不佔用額外空間。
堆排序要分兩個階段,第一個階段是把無序數組建立一個堆
第二個階段是交換最大元素和當前堆的數組最後一個元素,並且進行下沉操作維持堆的有序狀態
無序數組建立堆最直接的方法是從左到右遍歷數組,然後進行上浮操作
一個更高效的方法是從右至左進行下沉操作,如果一個節點的兩個節點都已經是堆有序
那麼進行下沉操作可以使得這個節點爲根節點的堆有序。葉子節點不需要進行下沉操作
因此可以忽略葉子節點的元素,因此只需要遍歷一半的元素即可
分析
一個堆的高度爲 lgN,因此在堆中插入元素和刪除最大元素的複雜度都爲 lgN。
對於堆排序,由於要對 N 個節點進行下沉操作,因此複雜度爲 NlgN。
堆排序時一種原地排序,沒有利用額外的空間
現代操作系統很少使用堆排序,因爲它無法利用緩存,也就是數組元素很少和相鄰的元素進行比較
int leftChild(int i) {
return 2 * i + 1;
}
void sink(int[] A, int i, int N) {
int child;
int tmp = A[i];
for (; leftChild(i) < N; i = child) {
child = leftChild(i);
if (child != N - 1 && A[child + 1] > A[child]) {
child++;
}
if (tmp < A[child]) {
A[i] = A[child];
} else {
break;
}
}
A[i] = tmp;
}
public void heapSort(int[] A, int n) {
for (int i = n / 2; i >= 0; i--) {
sink(A, i, n); // buildHeap
}
for (int i = n - 1; i > 0; i--) {
swap(A, 0, i); // delMax
sink(A, 0, i);
}
}
// 基數排序
基本思想:m位 d進制整數, 不足m 位的關鍵字高位補0;設置d 個桶,按關鍵字的最低位依次把元素放入桶中,此爲第一次基數排序; 然後依次按次低位,直到 最高位. 經過 m次基數排序後所有元素已經有序.
public void radixSort(int[] A, int n) {
if (A == null || n < 1) {
return;
}
//首先確定排序的趟數;
int max = A[0];
for (int i = 1; i < n; i++) {
if (A[i] > max) {
max = A[i];
}
}
int time = 0;
//判斷位數;
while (max > 0) {
max /= 10;
time++;
}
//建立10個桶;
List<ArrayList<Integer>> queue = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> queue1 = new ArrayList<Integer>();
queue.add(queue1);
}
//進行time次分配和收集;
for (int i = 0; i < time; i++) {
//分配數組元素;
for (int j = 0; j < n; j++) {
//得到數字的第time+1位數;
int x = A[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
ArrayList<Integer> queue2 = queue.get(x);
queue2.add(A[j]);
queue.set(x, queue2);
}
int count = 0;//元素計數器;
//收集隊列元素;
for (int k = 0; k < 10; k++) {
while (queue.get(k).size() > 0) {
ArrayList<Integer> queue3 = queue.get(k);
A[count] = queue3.get(0);
queue3.remove(0);
count++;
}
}
}
}
// 計數排序
public int[] countingSort(int[] A, int n) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (A[i] > max) {
max = A[i];
}
}
int[] count = new int[max + 1];
for (int i = 0; i < n; i++) {
count[A[i]]++;
}
int index = 0;
for (int i = 0; i < max + 1; i++) {
while (count[i] != 0) {
A[index++] = i;
count[i]--;
}
}
return A;
}