以下如無特殊說明都是按照升序進行排序。
原始碼見最下方
比較類排序
交換排序
氣泡排序
定義
是一種簡單的排序演算法。它重複地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重複地進行直到沒有再需要交換,也就是說該數列已經排序完成。這個演算法的名字由來是因為越小的元素會經由交換慢慢“浮”到數列的頂端--維基百科。
思想
氣泡排序很簡單,顧名思義每輪迴圈中將一個最大/最小的數通過交換一路冒
到陣列頂部。
程式碼
public class BubbleSort {
public static void main(String[] args) {
int[] arr = {4, 12, 2, 8, 453, 1, 59, 33};
for (int i = 0, length = arr.length; i < arr.length - 1; i++) {
for (int j = 0, tempLength = length - 1 - i; j < tempLength; j++) {
//如果當前數大於下一個數那麼和下一個數交換位置
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
System.out.println(Arrays.toString(arr));
}
}
}
快速排序
定義
快速排序(Quicksort)是對氣泡排序的一種改進。由 C. A. R. Hoare 在 1960 年提出。它的基本思想是:通過一趟排序將要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分別進行快速排序,整個排序過程可以遞迴進行,以此達到整個資料變成有序序列--百度百科。
思想
- 使用了分治的思想,先取一個數作為基數(一般選第一個數),然後將這個數移動到一個合適的位置使左邊的都比它小,右邊的都比他大
- 遞迴處理這個數左邊的數和右邊的數,直到所有的數都有序。直到所有的數都有序
程式碼
public class QuickSort {
private static void deal(Integer[] arr, int start, int end) {
if (start >= end) {
return;
}
int base = arr[start], i = start, j = end;
while (i < j) {
//在右邊找一個比基數小的數,直到i,j相等
while (arr[j] >= base && j > i) {
j--;
}
//在左邊找一個比基數大的數,直到i,j相等
while (arr[i] <= base && j > i) {
i++;
}
//如果ij不相等,交換其值
if (i < j) {
ArrayUtil.swap(arr, i++, j--);
}
}
//此時i等於j,交換基數和i/j,使左邊的數小於等於基數,右邊的數大於等於基數
if (start != i) {
ArrayUtil.swap(arr, start, i);
}
deal(arr, start, i - 1);
deal(arr, j + 1, end);
}
public static void main(String[] args) {
Integer[] arr = {1, 43, 2, 7, 5, 6, 555, 200, 21};
deal(arr, 0, arr.length - 1);
System.out.println("結果" + Arrays.toString(arr));
}
}
插入排序
簡單插入排序
是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,通常採用 in-place 排序,因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間--維基百科。
思想
插入排序的思想很簡單直接:
- 從第一個元素開始,該元素可以認為已經被排序
- 取出下一個元素,在已經排序的元素序列中從後向前掃描
- 如果該元素(已排序)大於新元素,將該元素移到下一位置
- 重複步驟 3,直到找到已排序的元素小於或者等於新元素的位置
- 將新元素插入到該位置後
- 重複步驟 2~5
動圖如下:
程式碼
public class InsertSort {
public static void sort(Integer[] arr) {
for (int i = 0, length = arr.length; i < length; i++) {
//有序部分從後向前比較,直到找到合適的位置
int j = i, temp = arr[i];
//如果arr[j-1]<=temp,說明arr[j]需為temp,否則將arr[j-1]向後移動一位
for (; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = temp;
System.out.println("當前陣列狀態為:" + Arrays.toString(arr));
}
}
public static void main(String[] args) {
Integer[] arr = {1, 65, 32, 12, 21};
InsertSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
希爾排序
定義
希爾排序,也稱遞減增量排序演算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序演算法。
希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
- 插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率
- 但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位
思想
- 取一個小於陣列長度 n 的整數 n1,將所有間隔為 n1 的數分成一組,對各組進行直接插入排序.然後 n=n1;
- 重複上述操縱,直到 n1=1 進行一次完整的插入排序後結束。
程式碼
public class ShellSort {
public static void sort(Integer[] arr) {
int n1 = arr.length / 2;
// 也可將do/while替換成尾遞迴
do {
//共n1組資料需要進行直接插入排序
for (int start = 0; start < n1; start++) {
//對一組執行插入排序,第一個數為arr[start],增量為n1
for (int i = start; i < arr.length; i += n1) {
int j = i, temp = arr[i];
for (; j > start && temp < arr[j - n1]; j -= n1) {
arr[j] = arr[j - n1];
}
arr[j] = temp;
}
}
n1 /= 2;
} while (n1 >= 1);
}
public static void main(String[] args) {
Integer[] arr = {1, 65, 32, 12, 21};
ShellSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
選擇排序
簡單選擇排序
定義
是一種簡單直觀的排序演算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然後,再從剩餘未排序元素中繼續尋找最小(大)元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
思想
簡單選擇排序顧名思義,每次從無序部分選出一個最大的數,和無序部分的最後一個值交換,重複 n-1 次後所有的值都變成有序狀態.
動畫如下:
程式碼
public class SimpleSelectSort {
public static void sort(Integer[] arr) {
int length = arr.length;
for (int i = 0; i < length - 1; i++) {
int maxIndex = 0;
for (int j = 1; j < length - i; j++) {
if (arr[j] > arr[maxIndex]) {
maxIndex = j;
}
}
ArrayUtil.swap(arr, maxIndex, length - i);
}
}
public static void main(String[] args) {
Integer[] arr = {1, 65, 32, 12, 21};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
堆排序
定義
堆排序(英語:Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子節點的鍵值或索引總是小於(或者大於)它的父節點。
不瞭解堆
的可以看看這篇,翻譯的挺好的。
思想
- 構建大頂堆(升序用大頂堆,降序用小頂堆) ,i=arr.lengh-1,n=arr.lengh
- 將 arr[0]和 arr[i]互換,
- i--
- 重新將 arr 0 到 i 構建為大頂堆
- 重複 2,3,4 直到 i=1
構建大頂堆過程如下:
- 從最後一個非葉子節點開始從下往上進行調整。
- 將該節點的值調整為 max(節點值,直接子節點值),注意如果產生了交換操作還要調整被交換節點,讓其也是 max(節點值,直接子節點值),直到被交換節點無子節點
程式碼
public class HeapSort {
private static void sort(Integer[] arr) {
int n = arr.length;
//構建大頂堆
for (int i = n / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, n);
}
//排序
for (int i = n - 1; i > 0; i--) {
ArrayUtil.swap(arr, 0, arr[i]);
adjustHeap(arr, 0, i);
}
}
/**
* Description: 調整堆
*
* @param arr 陣列
* @param index 調整index處的對結構
* @param length 堆大小
* @author fanxb
* @date 2019/7/31 19:50
*/
private static void adjustHeap(Integer[] arr, int index, int length) {
if (index >= length) {
return;
}
int maxIndex = index;
for (int i = 2 * index + 1; i < length - 1 && i <= 2 * index + 2; i++) {
if (arr[maxIndex] < arr[i]) {
maxIndex = i;
}
}
//如果進行了交換,還要調整被交換節點
if (maxIndex != index) {
ArrayUtil.swap(arr, maxIndex, index);
adjustHeap(arr, maxIndex, length);
}
}
public static void main(String[] args) {
Integer[] arr = {1, 65, 32, 334, 12, 21, 65, 112, 444443};
ShellSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
歸併排序
二路歸併
定義
歸併排序(英語:Merge sort,或 mergesort),是建立在歸併操作上的一種有效的排序演算法,效率為。1945 年由約翰·馮·諾伊曼首次提出。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用,且各層分治遞迴可以同時進行。--維基百科
思想
歸併排序的核心思想是將兩個有序的陣列合併成一個大的陣列,這個過程稱為 1 次歸併。
一次歸併過程如下(arr1,arr2 兩個有序陣列,arr3 存放排序後的陣列,i=0,j=0,k=0):
如果
arr1[i]<=arr2[j]
,那麼arr3[k]=arr1[i]
,i++,k++
;否則arr3[k]=arr2[j]
,j++,k++
;重複 1,直到某個有序陣列全部加入到 arr3 中,然後將另外一個陣列剩餘的部分加到 arr3 中即可。
但是一個無須陣列顯然不能直接拆成兩個有序陣列,這就需要用到分治
的思想。將陣列一層一層的拆分,直到單個陣列的長度為 1(長度為 1 的陣列可以認為是有序的),然後再反過來一層層進行歸併操作,那麼最後陣列就變成有序的了。
排序過程動圖如下(來自Swfung8):
程式碼
public class MergeSort {
/**
* Description:
*
* @param arr 待排序陣列
* @param start 開始下標
* @param end 結束下標
* @author fanxb
* @date 2019/8/6 9:29
*/
public static void mergeSort(Integer[] arr, int start, int end) {
if (start >= end) {
return;
}
int half = (start + end) / 2;
//歸併左邊
mergeSort(arr, start, half);
//歸併右邊
mergeSort(arr, half + 1, end);
//合併
merge(arr, start, half, end);
}
/**
* Description:
*
* @param arr arr
* @author fanxb
* @date 2019/8/5 17:36
*/
public static void merge(Integer[] arr, int start, int half, int end) {
ArrayList<Integer> tempList = new ArrayList<>();
int i = start, j = half + 1;
// 迴圈比較,將較小的放到tempList中
while (i <= half && j <= end) {
if (arr[i] <= arr[j]) {
tempList.add(arr[i]);
i++;
} else {
tempList.add(arr[j]);
j++;
}
}
if (i > half) {
//說明第一個陣列已經完了,將第二個陣列的剩餘部分放到tempList中
while (j <= end) {
tempList.add(arr[j]);
j++;
}
} else {
//說明第二個陣列已經完了,將第一個陣列剩餘部分放到tempList中
while (i <= half) {
//說明第二個陣列處理完了
tempList.add(arr[i]);
i++;
}
}
//最後將tempList複製到arr中
for (int k = 0, length = tempList.size(); k < length; k++) {
arr[start + k] = tempList.get(k);
}
}
public static void main(String[] args) {
Integer[] arr = {4, 3, 1, 2, 5, 4, 2};
mergeSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
非比較排序
計數排序
定義
計數排序不以比較為基礎,核心在於將數 a 存放在 arr[a]上,排序速度超級快,但是要求輸入的數必須是有確定範圍的整數。
思想
假設對於範圍 0-100 的整數進行排序
- 定義長度為 101 的陣列 arr,並將值初始化為 0
- 讀取一個數 a,然後 arr[a]++
- 遍歷 arr,陣列上的每個值表示對應下標的數的出現次數。
程式碼
public class CountSort {
/**
* Description:
*
* @param arr 待排序陣列
* @return void
* @author fanxb
* @date 2019/8/6 17:36
*/
public static void sort(Integer[] arr, Integer minValue, Integer maxValue) {
int range = maxValue - minValue + 1;
Integer[] numCount = new Integer[range];
Arrays.fill(numCount, 0);
for (Integer item : arr) {
item = item - minValue;
numCount[item]++;
}
int count = 0;
for (int i = 0; i < range; i++) {
if (numCount[i] == 0) {
continue;
}
for (int j = 0; j < numCount[i]; j++) {
arr[count] = minValue + i;
count++;
}
}
}
public static void main(String[] args) {
Integer[] arr = {1, 65, 32, 334, 12, 21, 65, 112, 444443};
sort(arr, 1, 444443);
System.out.println(Arrays.toString(arr));
}
}
PS
計數排序有很多的變種,下面列舉幾種:
- 存在負數怎麼辦?
很簡單,先進行一次遍歷將正數負數分開,在分別進行排序,負數取反後再排。
- 有空間限制且陣列非常大怎麼辦?
這裡可以利用檔案來實現。先將超大的陣列按照規則分成幾個部分,分別存到檔案中(比如 1-1000000 放在檔案 1 中,1000001-2000000 放在檔案 2 中,以此類推)就將超大的陣列分成了小的陣列,然後再分別計數排序即可。
基數排序
定義
是一種非比較型整數排序演算法,其原理是將整數按位數切割成不同的數字,然後按每個位數分別比較。由於整數也可以表達字串(比如名字或日期)和特定格式的浮點數,所以基數排序也不是隻能使用於整數。基數排序的發明可以追溯到 1887 年赫爾曼·何樂禮在打孔卡片製表機(Tabulation Machine)上的貢獻。--來自維基百科
基數排序可以採用 LSD(從高位開始),MSD(從低位開始),這裡以 MSD 為例。
(有興趣的可以思考思考如何用 LSD 實現,目前網上絕大多數都是 MSD 實現的)
思想
- 先建立 10 個桶,分別對應數字 0-9。
- 從左往右取第一位的數字,放到對應的桶中
- 依次從桶中取出數字(要按照先進先出的原則)放到源陣列中。
- 重複 2,3 步驟,依次對第二、第三。。。位的數字排序,直到最大位數處理完畢。
為什麼能夠這樣排序呢?第一遍排序完畢後,所有的數是按照個位排序的,對於所有小於 10 的數來說,他們已經是相對有序(並不是說位置不再變化,只是相對順序不再變化)的了,在第二輪對十位排序時,所有的個位數都將被放到 0 桶了,用先進先出策略處理這些個位數,取出時個位數還是有序的。
第二輪排序後所有小於 10 的數的位置已經確定且不再變化,大於 10 小於 100 的數的位置已經相對有序.在第三輪中所有小於 100 的數都將被放到 0 桶,這時相對有序就變成了絕對的了,取出後位置不再變化。
第三輪排序後所有小於 100 的數的位置已經確定且不再變化。以此類推直到全部排序完成。
動圖如下:
程式碼
public class RadixSort {
@SuppressWarnings("unchecked")
public static void sort(Integer[] arr) {
//定義桶
LinkedList<Integer>[] buckets = new LinkedList[10];
for (int i = 0; i < 10; i++) {
buckets[i] = new LinkedList<>();
}
int size = arr.length;
//當前處理第幾位的數
int count = 0;
while (true) {
//是否繼續進位
boolean isContinue = false;
//將數放到桶中
for (int i = 0; i < size; i++) {
int temp = arr[i] / (int) Math.pow(10, count) % 10;
if (!isContinue && temp != 0) {
// 如果存在一個數取的值不為0,說明還要繼續迴圈。
isContinue = true;
}
buckets[temp].addLast(arr[i]);
}
if (!isContinue) {
return;
}
//從桶中取出放到arr中,注意以什麼順序放進去的就要以什麼順序取出來(先進先出)
int index = 0;
for (int i = 0; i < 10; i++) {
Integer item;
while ((item = buckets[i].pollFirst()) != null) {
arr[index++] = item;
}
}
//位數+1
count++;
}
}
public static void main(String[] args) {
Integer[] arr = {4, 31, 1, 29, 5, 4, 2};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
本文原創釋出於:www.tapme.top/blog/detail/20190806
原始碼:github