希爾排序
排序思想:希爾排序可以說是插入排序的一種變種。無論是插入排序還是冒泡排序,如果數組的最大值剛好是在第一位,要將它挪到正確的位置就需要 n - 1 次移動。當原數組的一個元素如果距離它正確的位置很遠的話,需要與相鄰元素交換多次才能到達正確的位置,這樣效率較低。希爾排序就是插入排序排序的一種簡單改進,交換不相鄰的元素以對數組的局部進行排序,以此來提升效率。
排序過程:
- 先讓數組中任意間隔爲 h 的元素有序,剛開始 h 的大小可以是 h = n / 2
- 接着讓 h = n / 4,讓 h 一直縮小,相當於不斷增大步長,當 h = 1 時,也就是此時數組中任意間隔爲1的元素有序,此時的數組就是有序的了。
package sorting;
import java.util.Scanner;
/**
* @ClassName HillSort.java
* @Description 希爾排序
* @Author ZBW
* @Date 2020年03月07日 19:20
**/
public class HillSort {
public static int[] sort(int array[]) {
if (array == null || array.length < 2) {
return array;
}
int n = array.length;
// 對每組間隔爲 h的分組進行排序,剛開始 h = n / 2;
for (int h = n / 2; h > 0; h /= 2) {
//對各個局部分組進行插入排序
for (int i = h; i < n; i++) {
// 將array[i] 插入到所在分組的正確位置上
insert(array, h, i);
}
}
return array;
}
/**
* 將array[i]插入到所在分組的正確位置上
* array[i]] 所在的分組爲 ... array[i-2*h],array[i-h], array[i+h] ...
*/
private static void insert(int[] array, int h, int i) {
int temp = array[i];
int k;
for (k = i - h; k > 0 && temp < array[k]; k -= h) {
array[k + h] = array[k];
}
array[k + h] = temp;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入待排序數據個數:");
//輸入需要排序的數據個數
int n = in.nextInt();
int[] array = new int[n];
System.out.println("請輸入待排序數據:");
for (int i = 0; i < n; i++) {
array[i] = in.nextInt();
}
int[] res = sort(array);
print(res);
}
public static void print(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
- 時間複雜度爲O(nlogn),空間複雜度:O(1)
- 屬於非穩定排序
- 屬於原地排序
歸併排序
排序思想:將一個大的無序數組有序,我們可以把大的數組分成兩個,然後對這兩個數組分別進行排序,之後在把這兩個數組合併成一個有序的數組。由於兩個小的數組都是有序的,所以在合併的時候是很快的。
注意:在一個歸併排序中,可以將總的數組中n個元素分成logn個層次,每個層次的合併保持在O(n)的複雜度,那麼最後的算法時間複雜度爲O(nlogn)
排序過程:
- 通過遞歸的方式將大的數組一直分割,直到數組的大小爲 1,此時只有一個元素,那麼該數組就是有序的了
- 再把兩個數組大小爲1的合併成一個大小爲2的,再把兩個大小爲2的合併成4的 …
- 直到全部小的數組合並起來。
package sorting;
import java.util.Scanner;
/**
* @ClassName MergeSort.java
* @Description 歸併排序遞歸版本
* @Author ZBW
* @Date 2020年03月07日 22:18
**/
public class MergeSort {
private static int[] sort(int[] array) {
merge(array, 0, array.length - 1);
return array;
}
//遞歸使用歸併排序,對array[l...r]的範圍進行排序
private static void merge(int[] array, int l, int r){
if (l >= r) {
return;
}
int mid = (l + r)/2;
merge(array, l, mid);
merge(array, mid + 1, r);
//此處是一種優化,對於整體數組基本有序時的優化
if (array[mid] > array[mid+1]) {
mergeAll(array, l, mid, r);
}
}
//將array[l...mid]和array[mid+1...r]兩部分進行歸併
private static void mergeAll(int[] array, int l, int mid, int r) {
int[] temp = new int[r-l+1];
for (int i = l; i <= r; i++) {
temp[i-l] = array[i];
}
int i = l, j = mid+1;
for (int k = l; k <= r; k++) {
if (i > mid) {
array[k] = temp[j-l];
j++;
} else if (j > r) {
array[k] = temp[i-l];
i++;
} else if (temp[i-l] < temp[j-l]) {
array[k] = temp[i-l];
i++;
} else {
array[k] = temp[j-l];
j++;
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入待排序數據個數:");
//輸入需要排序的數據個數
int n = in.nextInt();
int[] array = new int[n];
System.out.println("請輸入待排序數據:");
for (int i = 0; i < n; i++) {
array[i] = in.nextInt();
}
int[] res = sort(array);
print(res);
}
public static void print(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
- 分析上面的sort()函數,時間複雜度爲O(nlogn),空間複雜度爲O(n)
- 屬於穩定排序
- 屬於非原地排序
上面的是遞歸版本,下面的是使用迭代進行歸併排序的版本:
package sorting;
import java.util.Scanner;
/**
* @ClassName MergeSort2.java
* @Description //TODO
* @Author ZBW
* @Date 2020年03月08日 20:53
**/
public class MergeSort2 {
public static int[] sort(int[] array) {
for (int size = 1; size <= array.length; size += size) {
for (int i = 0; i < array.length; i += size + size) {
mergeAll(array, i, i+size-1, Math.min(i+size+size-1, array.length-1));
}
}
return array;
}
private static void mergeAll(int[] array, int l, int mid, int r) {
int[] temp = new int[r-l+1];
for (int i = l; i <= r; i++) {
temp[i-l] = array[i];
}
int i = l, j = mid+1;
for (int k = l; k <= r; k++) {
if (i > mid) {
array[k] = temp[j-l];
j++;
} else if (j > r) {
array[k] = temp[i-l];
i++;
} else if (temp[i-l] < temp[j-l]) {
array[k] = temp[i-l];
i++;
} else {
array[k] = temp[j-l];
j++;
}
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入待排序數據個數:");
//輸入需要排序的數據個數
int n = in.nextInt();
int[] array = new int[n];
System.out.println("請輸入待排序數據:");
for (int i = 0; i < n; i++) {
array[i] = in.nextInt();
}
int[] res = sort(array);
print(res);
}
public static void print(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
快速排序(20世紀對世界影響最大的算法之一)牛掰!
排序思路:
首先我們來看總體的排序過程
- 比如說將一個數組中的第一個元素作爲主元,之後,採用雙指針的思想,讓 i = left + 1(此處最外層left即爲0),讓 j = right
- 之後 i 和 j 向數組中間進行移動,如果 arr[i] >= 主元,那麼i停止,同理如果 arr[j] <= 主元,那麼 j 停止,此時將 arr[i] 與 arr[j] 進行交換,然後繼續這樣的過程直到 i >= j
- 這時,除了主元,在 arr[j] 之前的元素都將小於等於主元,在 arr[j] 之後的元素都將大於等於主元
- 此時將 arr[j] 和主元進行交換,就能看到滿意的情況,主元左邊元素均小於主元,右邊元素都大於主元
- 這個時候就可以採用分治的思想,對於主元的左右兩部分數組再分別遞歸地進行上述過程
- 當數組元素只有一個或者零個時,那麼數組整體就是有序的了。
注意:
- 快排中最核心的部分就是劃分的partition過程,而且是需要藉助外部空間的
- 快速排序中partition時主元的選取可以是任意的,不一定必須是第一個元素爲主元,可以選取第一個或者最後一個,也可以利用random隨機生成介於0到數組長度之間的一個整數作爲主元索引,將對應元素作爲主元
給出一張圖,可以結合上面的過程,自己動態得畫一畫這個排序得過程,立馬就明白了,下面給出具體代碼,如果還不是很明白,該部分末尾有一篇很優質得文章推薦。
package sorting;
import java.util.Scanner;
/**
* @ClassName QuickSort.java
* @Description 快速排序
* @Author ZBW
* @Date 2020年03月07日 22:20
**/
public class QuickSort {
private static int[] sort(int[] array) {
quickSort(array, 0, array.length-1);
return array;
}
//對於arr[l....r]部分進行排序
private static void quickSort(int[] array, int l, int r) {
if (l >= r) {
return;
}
int p = partition(array, l, r);
quickSort(array, l, p-1);
quickSort(array, p+1, r);
}
//對array[l...r]部分進行partition操作
//返回p,使得array[l...p-1] < array[p]; array[p+1...r] > arr[p]
//partition過程也是整個排序算法最爲核心的部分
private static int partition(int[] array, int l, int r) {
int v = array[l];
int i = l + 1, j = r;
while (true) {
//i向右遍歷過程,如果比主元大就停止
while (i <= j && array[i] <= v) {
i++;
}
//j向左遍歷過程,如果比主元小就停止
while (i <= j && array[j] >= v) {
j--;
}
if (i >= j) {
break;
}
//對二者進行交換
int temp = array[j];
array[j] = array[i];
array[i] = temp;
}
//將arr[j]和主元繼進行交換,這樣主元之前的元素都小於主元,主元后的元素都大於主元
array[l] = array[j];
array[j] = v;
return j;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入待排序數據個數:");
//輸入需要排序的數據個數
int n = in.nextInt();
int[] array = new int[n];
System.out.println("請輸入待排序數據:");
for (int i = 0; i < n; i++) {
array[i] = in.nextInt();
}
int[] res = sort(array);
print(res);
}
public static void print(int[] array) {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
}
優質文章推薦:別再問我快速排序了
- 觀察上面的排序過程,時間複雜度爲O(nlogn),空間複雜度爲O(logn)
- 屬於非穩定排序
- 屬於原地排序
補充:
- 當選定第一個元素爲主元時,當數組基本有序時,每次只會對主元一邊的數組進行分割,這樣,分割次數會邊多,而算法的時間複雜度會退化爲O(n2),但是當對主元進行隨機選取的時候,就不一樣了,它的時間複雜度的期望值就是**O(logn)了,但是注意,只是期望,就是說快速排序退化成O(n2)**複雜度的概率就很低了,上面的代碼在這方面也可以進行優化
- 當一個數組中,有很多重複元素,partition操作都容易將數組劃分爲極度不平衡的兩部分,即使我們的主元選擇得很合適,這時候複雜度也會退化爲**O(n2)**的複雜度,上面的是已經進行優化過的版本
- 當然,提到了上面的問題,對於大量重複元素存在於數組中的情況,還可以進行三路快速排序,將整個數組劃分爲小於主元,等於主元,大於主元三部分區域
堆排序
堆:對應的應該是一個樹形結構,比如二叉堆
堆排序:堆排序就是把堆頂的元素與最後一個元素交換,交換之後破壞了堆的特性,我們再把堆中剩餘的元素再次構成一個大頂堆,然後再把堆頂元素與最後第二個元素交換….如此往復下去,等到剩餘的元素只有一個的時候,此時的數組就是有序的了。
二叉堆是一顆完全二叉樹,在堆中某個節點的值總是不大於其父節點的值,堆總是一顆完全二叉樹(最大堆),最小堆與之同理
package sorting;
import java.util.Arrays;
import java.util.Scanner;
/**
* @ClassName HeapSort.java
* @Description 堆排序
* @Author ZBW
* @Date 2020年03月15日 22:21
**/
public class HeapSort {
/**
* 下沉操作,執行刪除操作相當於把最後
* 一個元素賦給根元素之後,然後對根元素執行下沉操作
*/
public static int[] downAdjust(int[] arr, int parent, int length) {
//臨時保證要下沉的元素
int temp = arr[parent];
//定位左孩子節點位置
int child = 2 * parent + 1;
//開始下沉
while (child < length) {
//如果右孩子節點比左孩子小,則定位到右孩子
if (child + 1 < length && arr[child] > arr[child + 1]) {
child++;
}
//如果父節點比孩子節點小或等於,則下沉結束
if (temp <= arr[child]) {
break;
}
//單向賦值
arr[parent] = arr[child];
parent = child;
child = 2 * parent + 1;
}
arr[parent] = temp;
return arr;
}
//堆排序
public static int[] heapSort(int[] arr, int length) {
//構建二叉堆
for (int i = (length - 2) / 2; i >= 0; i--) {
arr = downAdjust(arr, i, length);
}
//進行堆排序
for (int i = length - 1; i >= 1; i--) {
//把堆頂的元素與最後一個元素交換
int temp = arr[i];
arr[i] = arr[0];
arr[0] = temp;
//下沉調整
arr = downAdjust(arr, 0, i);
}
return arr;
}
//測試
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("請輸入待排序數據個數:");
//輸入需要排序的數據個數
int n = in.nextInt();
int[] array = new int[n];
System.out.println("請輸入待排序數據:");
for (int i = 0; i < n; i++) {
array[i] = in.nextInt();
}
array = heapSort(array, array.length);
System.out.println(Arrays.toString(array));
}
}
- 堆排序的時間複雜度爲O(nlogn),空間複雜度爲O(1)
- 屬於非穩定排序
- 屬於原地排序
最後,附上一張圖作爲總結: