常見排序算法總結 -- java實現
<img src="https://files-cdn.cnblogs.com/files/zt19994/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95.gif" width=90%>html
排序算法能夠分爲兩大類:
- 非線性時間比較類排序:經過比較來決定元素間的相對次序,因爲其時間複雜度不能突破O(nlogn),所以稱爲非線性時間比較類排序。
- 線性時間非比較類排序:不經過比較來決定元素間的相對次序,它能夠突破基於比較排序的時間下界,以線性時間運行,所以稱爲線性時間非比較類排序。
<img src="https://images2018.cnblogs.com/blog/849589/201804/849589-20180402133438219-1946132192.png" width=90%>java
相關概念
- 穩定:若是a本來在b前面,而a=b,排序以後a仍然在b的前面。
- 不穩定:若是a本來在b的前面,而a=b,排序以後 a 可能會出如今 b 的後面。
- 時間複雜度:對排序數據的總的操做次數。反映當n變化時,操做次數呈現什麼規律。
- 空間複雜度:是指算法在計算機內執行時所需存儲空間的度量,它也是數據規模n的函數。
1、插入排序
1.1 直接插入排序
插入排序的基本操做就是將一個數據插入到已經排好序的有序數據中,從而獲得一個新的、個數加一的有序數據,算法適用於少許數據的排序,時間複雜度爲O(n^2)。是穩定的排序方法。git
直接插入排序的算法思路:github
- 設置監視哨temp,將待插入記錄的值賦值給temp;
- 設置開始查找的位置j;
- 在數組arr中進行搜索,搜索中將第j個記錄後移,直至temp≥arr[j]爲止;
- 將temp插入arr[j+1]的位置上。
/** * 直接插入排序 */ public void insertSort(int[] arr) { //外層循環肯定待比較數值 //必須i=1,由於開始從第二個數與第一個數進行比較 for (int i = 1; i < arr.length; i++) { //待比較數值 int temp = arr[i]; int j = i - 1; //內層循環爲待比較數值肯定其最終位置 //待比較數值比前一位置小,應插往前插一位 for (; j >= 0 && arr[j] > temp; j--) { //將大於temp的值總體後移一個單位 arr[j + 1] = arr[j]; } //待比較數值比前一位置大,最終位置無誤 arr[j + 1] = temp; } }
1.2 希爾排序
希爾排序(Shell's Sort)是插入排序的一種,又稱「縮小增量排序」(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L.Shell於1959年提出而得名。算法
希爾排序的算法思路:shell
- 把數組按下標的必定增量分組;
- 對每組使用直接插入排序算法排序;
- 隨着增量逐漸減小,每組包含的值愈來愈多,當增量減至1時,整個文件被分紅一組,算法便終止。
/** * 希爾排序 */ public void shellSort(int[] arr) { int d = arr.length; while (d >= 1) { d = d / 2; for (int x = 0; x < d; x++) { //按下標的必定增量分組而後進行插入排序 for (int i = x + d; i < arr.length; i = i + d) { int temp = arr[i]; int j; for (j = i - d; j >= 0 && arr[j] > temp; j = j - d) { //移動下標 arr[j + d] = arr[j]; } arr[j + d] = temp; } } } }
2、交換排序
2.1 冒泡排序
在一組數據中,相鄰元素依次比較大小,最大的放後面,最小的冒上來。api
冒泡排序算法的算法思路:數組
- 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素作一樣的工做,從開始第一對到結尾的最後一對。在這一點,最後的元素應該會是最大的數。
- 針對全部的元素重複以上的步驟,除了最後一個。
- 持續每次對愈來愈少的元素重複上面的步驟,直到沒有任何一對數字須要比較。
/** * 冒泡排序 */ public void bubbleSort(int[] arr) { for (int i = 0; i < arr.length - 1; i++) { for (int j = 0; j < arr.length - i - 1; j++) { if (arr[j] > arr[j + 1]) { // temp 臨時存儲 arr[j] 的值 int temp = arr[j]; //交換 arr[j] 和 arr[j+1] 的值 arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
2.2 快速排序
快速排序(Quicksort)是對冒泡排序的一種改進。<br> 經過一次排序將數組分紅兩個子數組,其中一個數組的值都比另一個數組的值小,而後再對這兩子數組分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。數據結構
快速排序的算法思路:(分治法)ide
- 先從數列中取出一個數做爲中間值middle;
- 將比這個數小的數所有放在它的左邊,大於或等於它的數所有放在它的右邊;
- 對左右兩個小數列重複第二步,直至各區間只有1個數。
/** * 快速排序 * * @param arr 待排序數組 */ public void quickSort(int[] arr) { //查看數組是否爲空 if (arr.length > 0) { sort(arr, 0, arr.length - 1); } } /** * @param arr 待排序數組 * @param low 開始位置 * @param high 結束位置 */ private void sort(int[] arr, int low, int high) { if (low < high) { int mid = getMiddle(arr, low, high); //將numbers數組進行一分爲二 sort(arr, low, mid - 1); //對低字段表進行遞歸排序 sort(arr, mid + 1, high); //對高字段表進行遞歸排序 } } /** * 查找出中軸(默認是最低位low)的在arr數組排序後所在位置 * * @param arr 待排序數組 * @param low 開始位置 * @param high 結束位置 * @return 中軸所在位置 */ private int getMiddle(int[] arr, int low, int high) { int temp = arr[low]; //數組的第一個做爲中軸 while (low < high) { while (low < high && arr[high] >= temp) { high--; } arr[low] = arr[high];//比中軸小的記錄移到低端 while (low < high && arr[low] < temp) { low++; } arr[high] = arr[low]; //比中軸大的記錄移到高端 } arr[low] = temp; //中軸記錄到尾 return low; // 返回中軸的位置 }
3、選擇排序
3.1 簡單選擇排序
選擇排序(Selection-sort)是一種簡單直觀的排序算法。它的工做原理:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,而後,再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾。以此類推,直到全部元素均排序完畢。
簡單選擇排序的算法思路:
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再從剩餘未排序元素中繼續尋找最小(大)元素,而後放到已排序序列的末尾
- 以此類推,直到全部元素均排序完畢。
/** * 簡單選擇排序 */ public void selectSort(int[] arr) { int minIndex = 0; int temp; for (int i = 0; i < arr.length - 1; i++) { minIndex = i; for (int j = i + 1; j < arr.length; j++) { // 找到當前循環最小值索引 if (arr[j] < arr[minIndex]) { minIndex = j; } } temp = arr[i]; // 交換當前循環起點值和最小值索引位置的值 arr[i] = arr[minIndex]; arr[minIndex] = temp; } }
3.2 堆排序
堆排序(英語:Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似徹底二叉樹的結構,並同時知足堆積的性質:即子結點的鍵值或索引老是小於(或者大於)它的父節點。在堆的數據結構中,堆中的最大值老是位於根節點(在優先隊列中使用堆的話堆中的最小值位於根節點)。
堆排序的算法思路:
- 最大堆調整(Max Heapify):將堆的末端子節點做調整,某個節點的值最多和其父節點的值同樣大;
- 建立最大堆(Build Max Heap):將堆中的全部數據從新排序,堆中的最大元素存放在根節點中;
- 堆排序(HeapSort):移除位在第一個數據的根節點,並作最大堆調整的遞歸運算。
/** * 堆排序 */ public void heapSort(int[] arr) { buildMaxHeap(arr); //進行n-1次循環,完成排序 for (int i = arr.length - 1; i > 0; i--) { //最後一個元素和第一個元素進行交換 int temp = arr[i]; arr[i] = arr[0]; arr[0] = temp; // 篩選 R[0] 結點,獲得i-1個結點的堆 將arr中前i-1個記錄從新調整爲大頂堆 heapAdjust(arr, 0, i); } } /** * 構建大頂堆 * <p> * 將數組中最大的值放在根節點 */ private void buildMaxHeap(int[] arr) { for (int i = arr.length / 2; i >= 0; i--) { heapAdjust(arr, i, arr.length - 1); } } /** * 堆調整 * <p> * 將數組中最大的值放在根節點 * * @param arr 待排序數組 * @param parent 父節點索引 * @param length 數組長度 */ private void heapAdjust(int[] arr, int parent, int length) { int temp = arr[parent]; //temp保存當前父節點 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 * child + 1; } arr[parent] = temp; }
4、歸併排序
4.1 二路歸併排序
歸併排序(mergeSort)是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。<br> 若將兩個有序表合併成一個有序表,稱爲二路歸併。例如:將2個有序數組合並。比較2個數組的第一個數,誰小就先取誰,取了後就在對應數組中刪除這個數。而後再進行比較,若是有數組爲空,那直接將另外一個數組的數依次取出便可。<br>
二路歸併排序的算法思路:
- 將數組分紅A,B 兩個數組,若是這2個數組都是有序的,那麼就能夠很方便的將這2個數組進行排序。
- 讓這2個數組有序,能夠將A,B組各自再分紅2個數組。依次類推,當分出來的數組只有1個數據時,能夠認爲數組已經達到了有序。
- 而後再合併相鄰的2個數組。這樣經過先遞歸的分解數組,再合併數組就完成了歸併排序。
/** * 二路歸併排序 */ public void mergeSort(int[] arr) { int[] temp = new int[arr.length]; //臨時數組 sort(arr, temp, 0, arr.length - 1); } /** * @param arr 待排序數組 * @param left 開始位置 * @param right 結束位置 */ private void sort(int[] arr, int[] temp, int left, int right) { if (left >= right) { return; } int mid = left + (right - left) / 2; sort(arr, temp, left, mid); sort(arr, temp, mid + 1, right); merge(arr, temp, left, mid, right); } /** * 將兩個有序表歸併成一個有序表 * * @param arr 待排序數組 * @param temp 臨時數組 * @param leftStart 左邊開始下標 * @param leftEnd 左邊結束下標(mid) * @param rightEnd 右邊結束下標 */ private static void merge(int[] arr, int[] temp, int leftStart, int leftEnd, int rightEnd) { int rightStart = leftEnd + 1; int tempIndex = leftStart; // 從左邊開始算 int len = rightEnd - leftStart + 1; // 元素個數 while (leftStart <= leftEnd && rightStart <= rightEnd) { if (arr[leftStart] <= arr[rightStart]) { temp[tempIndex++] = arr[leftStart++]; } else { temp[tempIndex++] = arr[rightStart++]; } } // 左邊若是有剩餘 將左邊剩餘的歸併 while (leftStart <= leftEnd) { temp[tempIndex++] = arr[leftStart++]; } // 右邊若是有剩餘 將右邊剩餘的歸併 while (rightStart <= rightEnd) { temp[tempIndex++] = arr[rightStart++]; } // 從臨時數組拷貝到原數組 for (int i = 0; i < len; i++) { arr[rightEnd] = temp[rightEnd]; rightEnd--; } }
5、計數排序
計數排序(Counting sort)不是基於比較的排序算法,其核心在於將輸入的數據值轉化爲鍵存儲在額外開闢的數組空間中。做爲一種線性時間複雜度的排序,計數排序要求輸入的數據必須是有肯定範圍的整數。
計數排序的算法思路:
- 求出待排序數組的最大值 max 和最小值 min。
- 實例化輔助計數數組temp,temp數組中每一個下標對應arr中的一個元素,temp用來記錄每一個元素出現的次數。
- 計算 arr 中每一個元素在temp中的位置 position = arr[i] - min。
- 根據 temp 數組求得排序後的數組。
/** * 計數排序 */ public void countSort(int[] arr) { if (arr == null || arr.length == 0) { return; } int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; //找出數組中的最大最小值 for (int i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); min = Math.min(min, arr[i]); } int[] temp = new int[max]; //找出每一個數字出現的次數 for (int i = 0; i < arr.length; i++) { //每一個元素在temp中的位置 position = arr[i] - min int position = arr[i] - min; temp[position]++; } int index = 0; for (int i = 0; i < temp.length; i++) { //temp[i] 大於0 表示有重複元素 while (temp[i]-- > 0) { arr[index++] = i + min; } } }
6、桶排序
桶排序 (Bucket sort)的工做原理是將數組分到有限數量的桶裏。每一個桶再分別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。當要被排序的數組內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並非比較排序,他不受到 O(n log n) 下限的影響,桶排序可用於最大最小值相差較大的數據狀況。
桶排序的算法思路:
- 找出待排序數組中的最大值max和最小值min;
- 咱們使用動態數組ArrayList 做爲桶,桶裏放的元素也用 ArrayList 存儲。桶的數量爲 (max-min) / arr.length + 1;
- 遍歷數組 arr,計算每一個元素 arr[i] 放的桶;
- 每一個桶各自排序;
- 遍歷桶數組,把排序好的元素放進輸出數組。
/** * 桶排序 * * @param arr 待排序數組 */ public static void bucketSort(int[] arr) { int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for (int i = 0; i < arr.length; i++) { max = Math.max(max, arr[i]); min = Math.min(min, arr[i]); } //桶數 int bucketNum = (max - min) / arr.length + 1; ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum); for (int i = 0; i < bucketNum; i++) { bucketArr.add(new ArrayList<>()); } //將每一個元素放入桶 for (int i = 0; i < arr.length; i++) { int num = (arr[i] - min) / arr.length; bucketArr.get(num).add(arr[i]); } //對每一個桶進行排序 for (int i = 0; i < bucketNum; i++) { Collections.sort(bucketArr.get(i)); } int position = 0; //合併桶 for (int i = 0; i < bucketNum; i++) { for (int j = 0; j < bucketArr.get(i).size(); j++) { arr[position++] = bucketArr.get(i).get(j); } } }
7、基數排序
基數排序(radix sort)是桶排序的擴展,基本思想是將整數按位數切割成不一樣的數字,而後按每一個位數分別比較。<br> 基數排序法是屬於穩定性的排序,其時間複雜度爲O (nlog(r)m),其中r爲所採起的基數,而m爲堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。
基數排序的算法思路:
- 取得數組中的最大數,並取得位數;
- arr爲原始數組,從最低位開始取每一個位組成radix數組;
- 對radix進行計數排序(利用計數排序適用於小範圍數的特色)。
/** * 基數排序 * * @param arr 待排序數組 */ public void radixSort(int[] arr) { int max = getMax(arr); // 數組arr中的最大值 for (int exp = 1; max / exp > 0; exp *= 10) { //從個位開始,對數組arr按"exp指數"進行排序 countSort(arr, exp); //bucketSort(arr, exp); } } /** * 獲取數組中最大值 */ private int getMax(int[] arr) { int max = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } /** * 對數組按照"某個位數"進行排序(計數排序) * <p> * 例如: * 一、當exp=1 表示按照"個位"對數組進行排序 * 二、當exp=10 表示按照"十位"對數組進行排序 * * @param arr 待排序數組 * @param exp 指數 對數組arr按照該指數進行排序 */ private void countSort(int[] arr, int exp) { int[] temp = new int[arr.length]; // 存儲"被排序數據"的臨時數組 int[] buckets = new int[10]; // 將數據出現的次數存儲在buckets[]中 for (int i = 0; i < arr.length; i++) { buckets[(arr[i] / exp) % 10]++; } // 計算數據在temp[]中的位置 0 1 2 2 3 --> 0 1 3 5 8 for (int i = 1; i < 10; i++) { buckets[i] += buckets[i - 1]; } // 將數據存儲到臨時數組temp[]中 for (int i = arr.length - 1; i >= 0; i--) { temp[buckets[(arr[i] / exp) % 10] - 1] = arr[i]; buckets[(arr[i] / exp) % 10]--; } // 將排序好的數據賦值給arr[] for (int i = 0; i < arr.length; i++) { arr[i] = temp[i]; } } /** * 桶排序 */ private void bucketSort(int[] arr, int exp) { int[][] buckets = new int[10][arr.length]; //這是二維數組組成的桶 int[] counter = new int[10]; //此數組用來記錄0-9每一個桶中的數字個數,計數器 for (int i = 0; i < arr.length; i++) { int index = (arr[i] / exp) % 10; //得出相應位置(如個位、十位)上的數字 buckets[index][counter[index]] = arr[i]; //取出來放到桶裏 counter[index]++; //相應的計數器加1 } int position = 0; //合併桶 for (int i = 0; i < 10; i++) { for (int j = 0; j < counter[i]; j++) { arr[position++] = buckets[i][j]; } } }
源碼地址:https://github.com/zt19994/leetcode/tree/master/src/Learn/SortAlgorithm