陣列排序問題(一)
目錄
- 氣泡排序
- 選擇排序
- 插入排序
- 歸併排序
- 小和問題
- 逆序對問題
氣泡排序
氣泡排序的思路:每一個數與自己後面的數比較,如果前者>後者,則交換,直到最大的數排到了最後;下一輪繼續執行同樣操作,直到第二最大數拍到了倒數第二個位置。以此類推,每一輪確定一個數最終位置,需要比較的數在遞減,直到所有迴圈結束為止。
外層迴圈:0~N-1位置找出來最大的放在N-1位置 ,N每輪遞減。
內層迴圈:比較兩個相鄰的數,如果前者>後者,則交換
public static void bubbleSort(int[] arr) { if(arr == null || arr.length < 2){ return; } for(int i = arr.length - 1; i > 0; i--){ for(int j = 0; j < i; j++){ if(arr[j] > arr[j + 1]){ swap(arr, j , j + 1); } } } }
氣泡排序的時間複雜度為O(N^2),額外空間複雜度為O(1)。
選擇排序
選擇排序的思路:迴圈遍歷選出最小的一個數放在第一個位置;下一輪再在第二個位置開始遍歷,選出最小的一個數放在第二個位置。以此類推,每一輪選出最小數排到前面,每一輪需要比較的數在遞減,直到所有迴圈結束為止。
外層迴圈:0~N-1位置找出來最大的放在0位置。
內層迴圈:找到最小數的下標,然後交換
public static void selectSort(int[] arr){ if(arr == null || arr.length < 2){ return; } for(int i = 0; i < arr.length; i++){ int minIndex = i; for(int j = i + 1; j < arr.length; j++){ if(arr[minIndex] > arr[j]){ swap(arr, minIndex, j); } } } }
選擇排序的時間複雜度為O(N^2),額外空間複雜度為O(1)。
插入排序
插入排序的思路:將一個數組資料分為有序組和待插入組,每一次從待插入組中去一個數,與有序組中的元素進行對比,並找到合適的位置插入到有序組。每次插入一個元素,有序組增加,待插入組減少。直到待插入組元素個數為0。
外層迴圈:從左至右每次從待插入組中取一個元素進行比較
內層迴圈:將這一元素與有序組中的元素進行對比,找到合適的位置插入。
public static void insertionSort(int[] arr){ if(arr == null || arr.length < 2){ return; } for(int i = 1; i < arr.length; i++){ for(int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--){ swap(arr, j, j + 1); } } }
插入排序的時間複雜度為O(N^2),額外空間複雜度為O(1)。
歸併排序
歸併排序的思路:歸併排序採用分治法,將陣列一分為二,層層向下劃分,直到子陣列只剩下一個數無法劃分為止,然後將相鄰的兩個陣列進行有序合併,層層向上合併,最終完成有序歸併。
歸併排序的核心思想是將兩個有序的數列合併成一個大的有序的序列。通過遞迴,層層合併,即為歸併。
public static void mergerSort(int[] arr) { if (arr == null || arr.length < 2) { return; } mergerSort(arr, 0, arr.length - 1); } public static void mergerSort(int[] arr, int left, int right) { // 當無法再劃分時返回 if (left == right) { return; } // 將陣列劃分為左右兩個陣列,再通過遞歸向下劃分 int mid = (left + right) / 2; mergerSort(arr, left, mid); mergerSort(arr, mid + 1, right); // 將兩個有序數組合並 merge(arr, left, mid, right); } public static void merge(int[] arr, int left, int mid, int right){ // 將兩個有序的數列合併成一個大的有序的序列 int[] help = new int[right - left + 1]; int i = 0; int p1 = left; int p2 = mid + 1; while (p1 <= mid && p2 <= right){ help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } //當上一個迴圈將某一個數組全部填入大陣列後,另一個數組也依次全部填入 //下面兩個wihle迴圈只會執行一個 while (p1 <= mid){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++){ arr[left + i] = help[i]; } }
歸併排序的時間複雜度為O(N * logN),額外空間複雜度為O(N)。
小和問題
在一個數組中,每一個數左邊比當前數小的數累加起來,叫做這個陣列的小和。求一個數組的小和。
例子:[1,3,4,2,5]
- 1左邊比1小的數,沒有;
- 3左邊比3小的數,1;
- 4左邊比4小的數,1、3;
- 2左邊比2小的數,1;
- 5左邊比5小的數,1、3、4、2;
- 所以小和為1+1+3+1+1+3+4+2=16
小和問題可以把問題理解成,左邊的數尋找右邊到底有幾個比自己大的數,如果右邊的陣列是一個有序陣列,那麼只需要找到第一個比自己大的數,就可以得出這個數的右側都比自己大,從而不需要重複比較。
理解了這一層意思,就可以將小和問題和歸併排序同樣看待,除了需要排序以外,還需要在merge的時候得出右側陣列有幾個比自己大的數。
public static int smallSum(int[] arr) { if (arr == null || arr.length < 2) { return 0; } return mergerSort(arr, 0, arr.length - 1); } public static int mergerSort(int[] arr, int left, int right) { if (left == right) { return 0; } //同樣的merge操作,將每次merge得出的小和相加 int mid = (left + right) / 2; int leftSum = mergerSort(arr, left, mid); int rightSum = mergerSort(arr, mid + 1, right); int mergeSum = merge(arr, left, mid, right); return leftSum + rightSum + mergeSum; } public static int merge(int[] arr, int left, int mid, int right){ int[] help = new int[right - left + 1]; int i = 0; int p1 = left; int p2 = mid + 1; int sum = 0; while (p1 <= mid && p2 <= right){ //由於右邊陣列是有序的,所以只需要找到第一個比自己大的數,就可以得出後面的數都比自己大 //從而乘以後面數的個數就可以得到自己的小和 sum += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0; help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while (p1 <= mid){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++){ arr[left + i] = help[i]; } return sum; }
逆序對問題
在一個數組中,左邊的數如果比右邊的數大,則這兩個數構成一個逆序對,請列印所有逆序對。
如果小和問題是左邊的數尋找右邊到底有幾個比自己大的數,那麼逆序對問題就是左邊的數尋找右邊有幾個比自己小的數,演算法與歸併排序基本一致。
public static void reverseOrder(int[] arr) { if (arr == null || arr.length < 2) { return; } mergerSort(arr, 0, arr.length - 1); } public static void mergerSort(int[] arr, int left, int right) { if (left == right) { return; } int mid = (left + right) / 2; mergerSort(arr, left, mid); mergerSort(arr, mid + 1, right); merge(arr, left, mid, right); } public static void merge(int[] arr, int left, int mid, int right){ int[] help = new int[right - left + 1]; int i = 0; int p1 = left; int p2 = mid + 1; while (p1 <= mid && p2 <= right){ //左邊陣列是有序的,所以只需要找到第一個比自己小的數,那麼左邊陣列後面的數都比該數大。 if(arr[p1] > arr[p2]){ for(int j = p1; j <= mid; j++){ System.out.println(arr[j]+":"+arr[p2]); } } help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; } while (p1 <= mid){ help[i++] = arr[p1++]; } while (p2 <= right){ help[i++] = arr[p2++]; } for (i = 0; i < help.length; i++){ arr[left + i] = help[i]; } }
參考資料:牛客網左程雲初級演算法教程