【數據結構】 排序算法
- 一、排序
- 1.1 排序是什么?
- 1.2 排序的應用
- 1.3 常見排序算法
- 二、常見排序算法的實現
- 2.1 插入排序
- 2.1.1 直接插入排序
- 2.1.2 希爾排序
- 2.2 選擇排序
- 2.2.1 直接選擇排序
- 2.2.1.1 方法1
- 2.2.1.1 方法2
- 2.2.2 堆排序(數組形式)
- 2.3 交換排序
- 2.3.1 冒泡排序
- 2.3.2 快速排序
- 2.3.2.1 Hoare版 快速排序
- 2.3.2.2 挖坑法 快速排序
- 2.3.3 快排的優化
- 2.3.4 快速排序非遞歸
- 2.4 歸并排序
- 三、各排序算法的復雜度及穩定性
- 四、 其他排序
- 4.1 計數排序
- 4.2 基數排序
- 4.3 桶排序
一、排序
1.1 排序是什么?
1.2 排序的應用
1.3 常見排序算法
二、常見排序算法的實現
十大經典排序算法 動畫演示
2.1 插入排序
2.1.1 直接插入排序
(1)圖解
(2)直接插入排序 解題思路
(3)Java 代碼實現
public class Sort {/*** 直接插入排序* 時間復雜度: 最壞情況 O(N^2) 最好情況O(N) -- 數據有序的情況下* 直接插入排序使用場景: 給定的數據 趨于有序時,可以使用直接插入排序。* 穩定性: 穩定* @param array*/public static void insertSort(int[] array){for (int i = 1; i < array.length; i++) {//從第2個元素開始排序int tmp = array[i];int j = i-1;for (; j >= 0 ; j--) {if(array[j] > tmp){array[j+1] = array[j];}else{//array[j+1] = tmp;break;}}//j=-1,把tmp的值再放回來array[j+1] = tmp;}}
}
測試類:
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Random;public class Test {//測試 直接排序法的時間效率//構造 有序數組public static void order(int[] array) {for (int i = 0; i < array.length; i++) {array[i] = i;}}//構造 逆序數組public static void reverseOrder(int[] array){for (int i = 0; i < array.length; i++) {array[i] = array.length -i;}}//構造 隨機數組public static void randomOrder(int[] array){Random random = new Random();for (int i = 0; i < array.length; i++) {array[i] = random.nextInt(array.length);}}public static void testInsertSort(int[] array){array = Arrays.copyOf(array,array.length);long startTime = System.currentTimeMillis();Sort.insertSort(array);long endTime = System.currentTimeMillis();System.out.println("直接插入排序耗時:"+(endTime-startTime));}public static void testShellSort(int[] array){array = Arrays.copyOf(array,array.length);long startTime = System.currentTimeMillis();Sort.insertSort(array);long endTime = System.currentTimeMillis();System.out.println("假設 希爾排序耗時:"+(endTime-startTime));}public static void main(String[] args) {int[] array = new int[1_0000];reverseOrder(array);testInsertSort(array);testShellSort(array);}public static void main1(String[] args) {int[] array = {81,12,31,4,15,6};Sort.insertSort(array);System.out.println(Arrays.toString(array));//[4, 6, 12, 15, 31, 81]}
}
2.1.2 希爾排序
(1)希爾排序法的基本思想:
先選定?個整數,把待排序?件中所有記錄分成多個組,所有距離為的記錄分在同?組內,并對每?組內的記錄進?排序。然后,取,重復上述分組和排序的?作。當到達=1時,所有記錄在統?組內排好序。
(2)圖解
(3)Java 代碼實現
/*** 希爾排序* 時間復雜度:O(n^1.3)* 空間復雜度:O(1)* 穩定性:不穩定* @param array*/public static void shellSort(int[] array){int gap = array.length / 2;while(gap > 0){shell(array,gap);gap /= 2;}shell(array,1);}public static void shell(int[] array,int gap){for (int i = gap; i < array.length; i++) {//從第2個元素開始排序int tmp = array[i];int j = i-gap;for (; j >= 0 ; j -= gap) {if(array[j] > tmp){array[j+gap] = array[j];}else{break;}}array[j+gap] = tmp;}}
2.2 選擇排序
2.2.1 直接選擇排序
2.2.1.1 方法1
(1)圖解
(2)直接排序解題思路
(3)Java 代碼實現
/*** 直接選擇排序* 時間復雜度:O(N^2)* 空間復雜度:O(1)* 穩定性:不穩定*/public static void selectSort(int[] array){for (int i = 0; i < array.length; i++) {int minIndex = i;for (int j = i+1; j < array.length; j++) {if(array[minIndex] > array[j]){minIndex = j;}}swap(array,minIndex,i);}}private static void swap(int[] array,int i,int j){int tmp = array[i];array[i] = array[j];array[j] = tmp;}
測試類:
public static void main(String[] args) {int[] array = {81,12,31,4,15,6};Sort.selectSort(array);System.out.println(Arrays.toString(array));//[4, 6, 12, 15, 31, 81]}
2.2.1.1 方法2
(1)圖解
(2)Java 代碼
//選擇排序 方法2/*** 每次找到2個數據 一個最大 一個最小*/public static void selectSort2(int[] array){int left = 0;int right = array.length-1;while(left < right){int minIndex = left;int maxIndex = left;for (int i = left+1; i <= right ; i++) {if(array[i] < array[minIndex]){minIndex = i;}if(array[i] > array[maxIndex]){maxIndex = i;}}swap(array,minIndex,left);//假設第一個值 是最大值 81 3 6 2 4if(left == maxIndex){maxIndex = minIndex;}swap(array,maxIndex,right);left++;right--;}}
}
2.2.2 堆排序(數組形式)
(1)圖解
(2)Java 代碼
//堆排序/*** 時間復雜度:O(n*log2n)* 空間復雜度:O(1)* 穩定性:不穩定*/public static void heapSort(int[] array){createHeap(array);int end = array.length -1;while(end > 0){swap(array,0,end);siftDown(array,0,end);end--;}}public static void createHeap(int[] array){for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {siftDown(array,parent,array.length);}}public static void siftDown(int[] array,int parent,int length){int child = 2 * parent + 1;while(child < length){if(child+1 < length && array[child] < array[child+1]){//存在右孩子 并且 左孩子比右孩子小child++;}if(array[child] > array[parent]){swap(array,child,parent);parent = child;child = 2 * parent + 1;}else{break;}}}
2.3 交換排序
2.3.1 冒泡排序
//冒泡排序/**當前代碼是優化過的版本,分析時間復雜度的時候,不考慮優化情況* 時間復雜度:O(n^2); 如果考慮優化,最好情況下時間復雜度為O(n)* 空間復雜度:O(1)* 穩定性:穩定*/public static void bubbleSort(int[] array){//趟數:10個數據 比較9趟for (int i = 0; i < array.length-1; i++) {boolean flg = false;//未優化的版本:沒有flg//每一趟的比較次數for (int j = 0; j < array.length-1-i; j++) {//未優化的版本:j < array.length-1if(array[j] > array[j+1]){swap(array,j,j+1);flg = true;}}if(!flg){break;}}}
2.3.2 快速排序
2.3.2.1 Hoare版 快速排序
(1)圖解
(2)Java 代碼實現
//Hoare版 快速排序/**時間復雜度:O((n^log2n)* 當前代碼的最壞情況下:O(N^2) 有序 或 逆序*空間復雜度: O(log2n) 最好* O(N) 最壞* 穩定性:不穩定*/public static void quickSort(int[] array){quick(array,0,array.length-1);}public static void quick(int[] array,int start,int end){if(start >= end){return;}int par = partition(array,start,end);quick(array,start,par-1);quick(array,par+1,end);}private static int partitionHoare(int[] array,int left,int right){int i = left;int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}while(left < right && array[left] <= tmp){left++;}swap(array,left,right);}swap(array,left,i);return left;}
2.3.2.2 挖坑法 快速排序
private static int partition(int[] array,int left,int right){int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}array[left] = array[right];while(left < right && array[left] <= tmp){left++;}array[right] = array[left];}array[left] = tmp;return left;}
2.3.3 快排的優化
- 三數取中
三數取中優化:
//三數取中int index = midSum(array,start,end);swap(array,start,index);int par = partition2(array,start,end);quick(array,start,par-1);quick(array,par+1,end);}public static int midSum(int[] array,int left,int right){int mid = (left+right)/2;if(array[left] < array[right]){if(array[mid] < array[left]){return left;}else if(array[mid] > array[right]){return right;}else{return mid;}}else{if(array[mid] > array[left]){return left;}else if(array[mid] < array[right]){return right;}else{return mid;}}}
快速排序:
//Hoare版 快速排序/**時間復雜度:O((n^log2n)* 當前代碼的最壞情況下:O(N^2) 有序 或 逆序*空間復雜度: O(log2n) 最好* O(N) 最壞* 穩定性:不穩定*/public static void quickSort(int[] array){quick(array,0,array.length-1);}public static void quick(int[] array,int start,int end){if(start >= end){return;}//三數取中int index = midSum(array,start,end);swap(array,start,index);int par = partition2(array,start,end);quick(array,start,par-1);quick(array,par+1,end);}public static int midSum(int[] array,int left,int right){int mid = (left+right)/2;if(array[left] < array[right]){if(array[mid] < array[left]){return left;}else if(array[mid] > array[right]){return right;}else{return mid;}}else{if(array[mid] > array[left]){return left;}else if(array[mid] < array[right]){return right;}else{return mid;}}}private static int partitionHoare(int[] array,int left,int right){int i = left;int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}while(left < right && array[left] <= tmp){left++;}swap(array,left,right);}swap(array,left,i);return left;}//挖坑法 快速排序private static int partition2(int[] array,int left,int right){int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}array[left] = array[right];while(left < right && array[left] <= tmp){left++;}array[right] = array[left];}array[left] = tmp;return left;}
- 遞歸到小的子區間時,可以考慮使用插入排序
直接插入排序:
public static void quick(int[] array,int start,int end){if(start >= end){return;}if(end-start+1 <= 10){//采用直接插入排序,把當前區間排序insertSortRange(array,start,end);return;}//三數取中int index = midSum(array,start,end);swap(array,start,index);int par = partition2(array,start,end);quick(array,start,par-1);quick(array,par+1,end);}public static void insertSortRange(int[] array,int left,int right){for (int i = left+1; i <= right; i++) {int tmp = array[i];int j = i-1;for (; j >= left ; j--) {if(array[j] > tmp){array[j+1] = array[j];}else{break;}}array[j+1] = tmp;}}
快速排序:
//Hoare版 快速排序/**時間復雜度:O((n^log2n)* 當前代碼的最壞情況下:O(N^2) 有序 或 逆序*空間復雜度: O(log2n) 最好* O(N) 最壞* 穩定性:不穩定*/public static void quickSort(int[] array){quick(array,0,array.length-1);}public static void quick(int[] array,int start,int end){if(start >= end){return;}if(end-start+1 <= 10){//采用直接插入排序,把當前區間排序insertSortRange(array,start,end);return;}//三數取中int index = midSum(array,start,end);swap(array,start,index);int par = partition2(array,start,end);quick(array,start,par-1);quick(array,par+1,end);}public static void insertSortRange(int[] array,int left,int right){for (int i = left+1; i <= right; i++) {int tmp = array[i];int j = i-1;for (; j >= left ; j--) {if(array[j] > tmp){array[j+1] = array[j];}else{break;}}array[j+1] = tmp;}}public static int midSum(int[] array,int left,int right){int mid = (left+right)/2;if(array[left] < array[right]){if(array[mid] < array[left]){return left;}else if(array[mid] > array[right]){return right;}else{return mid;}}else{if(array[mid] > array[left]){return left;}else if(array[mid] < array[right]){return right;}else{return mid;}}}private static int partitionHoare(int[] array,int left,int right){int i = left;int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}while(left < right && array[left] <= tmp){left++;}swap(array,left,right);}swap(array,left,i);return left;}//挖坑法 快速排序private static int partition2(int[] array,int left,int right){int tmp = array[left];while(left < right){while(left < right && array[right] >= tmp){right--;}array[left] = array[right];while(left < right && array[left] <= tmp){left++;}array[right] = array[left];}array[left] = tmp;return left;}
- 前后指針法 快速排序
//前后指針法 快速排序public static int partition(int[] array,int left,int right){int prev = left;int cur = left+1;while(cur <= right){if(array[cur] <= array[right] && array[++prev] != array[cur]){swap(array,cur,prev);}cur++;}swap(array,prev,left);return prev;
2.3.4 快速排序非遞歸
(1)圖解
(2)Java 代碼實現
//非遞歸的快速排序/** 時間復雜度:O(N*logN)* 空間復雜度:O(logN)* 不穩定*/public static void quickSortNor(int[] array){int left = 0;int right = array.length -1;int par = partition2(array,left,right);Stack<Integer> stack = new Stack<>();//左邊有2個元素及以上if(par > left+1){stack.push(left);stack.push(par-1);}//右邊有2個元素及以上if(par < right-1){stack.push(par+1);stack.push(right);}//相當于把0 4 6 9扔進里面了while(!stack.isEmpty()){right = stack.pop();left = stack.pop();par = partition2(array,left,right);if(par > left+1){stack.push(left);stack.push(par-1);}//右邊有2個元素及以上if(par < right-1){stack.push(par+1);stack.push(right);}}}
2.4 歸并排序
(1)圖解
(2)Java 代碼實現
//歸并排序/** 時間復雜度:O(N*log2N)* 空間復雜度:O(N)* 穩定*/public static void mergeSort(int[] array){mergeSortChild(array,0,array.length-1);}private static void mergeSortChild(int[] array,int left,int right){if( left>= right){return;}int mid = (left+right) / 2;mergeSortChild(array, left, mid);mergeSortChild(array, mid + 1, right);//開始合并merge(array, left, mid, right);}private static void merge(int[] array, int left, int mid, int right) {//臨時數組int[] tmpArr = new int[right-left+1];int k = 0;//tmpArr數組下標int s1 = left;int e1 = mid;int s2 = mid+1;int e2 = right;//當2個段 都有數據的時候while(s1 <= e1 && s2 <= e2){if(array[s1] < array[s2]){tmpArr[k++] = array[s1++];}else{tmpArr[k++] = array[s2++];}}//一個段走完了 把另一個段的數據 拷貝到臨時數組while(s1 <= e1){tmpArr[k++] = array[s1++];}while(s2 <= e2){tmpArr[k++] = array[s2++];}//臨時數組當中存儲的是有序的數組 --> 拷貝數據到原始數組當中for (int i = 0; i < k; i++) {array[i+left] = tmpArr[i];}}
(3)非遞歸 歸并排序
//非遞歸 歸并排序
public static void mergeSortNor(int[] array) {if (array == null || array.length <= 1) {return;}int gap = 1; // 初始子數組長度while (gap < array.length) {// 遍歷整個數組,每次合并兩個相鄰的子數組for (int i = 0; i < array.length; i += 2 * gap) {int left = i;int mid = Math.min(i + gap - 1, array.length - 1); // 防止越界int right = Math.min(i + 2 * gap - 1, array.length - 1); // 防止越界// 合并 [left, mid] 和 [mid+1, right]merge(array, left, mid, right);}gap *= 2; // 子數組長度翻倍}}
三、各排序算法的復雜度及穩定性
四、 其他排序
4.1 計數排序
(1)圖解
(2)java 代碼實現
//計數排序/** 使用場景:數組集中在某個范圍內* 時間復雜度:O(MAX(N,范圍))* 空間復雜度:O(范圍)* 穩定*/public static void countSort(int[] array){//1.求最大值 和 最小值int max = array[0];int min = array[0];for (int i = 1; i < array.length; i++) {if(array[i] < min){min = array[i];}if(array[i] > max){max = array[i];}}//2.定義計數數組,并求數組長度int len = max - min + 1;int[] countArray = new int[len];//3.遍歷原始數組,計數數組開始計數for (int i = 0; i < array.length; i++) {int index = array[i] - min;countArray[index]++;}//4.遍歷計數數組int k = 0;//array數組的新下標for (int i = 0; i < countArray.length; i++) {while(countArray[i] != 0){array[k] = i+min;k++;countArray[i]--;}}//執行到這里 array數組全部都有序了}