日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java实现归并排序(详解)

發布時間:2023/12/9 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java实现归并排序(详解) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

主要思想

歸并排序和快速排序都是基于分而治之的算法思想。

歸并排序先將待排序的數組不斷拆分,直到拆分到區間里只剩下一個元素的時候。不能再拆分的時候。這個時候我們再想辦法合并兩個有序的數組,得到長度更長的有序數組。當前合并好的有序數組為下一輪得到更長的有序數組做好了準備。一層一層的合并回去,直到整個數組有序。

在這各整個過程當中,不停做的事情就是拆分問題和組合子問題的解,在這里很顯然我們是需要使用遞歸來實現歸并排序。

如何合并兩個有序數組

在這里我們是一個一個的選出來的。

先選出兩個數組里最小的元素,然后再選出兩個數組中第二小的元素,因為兩個數組都是有序的,所以兩個數組里的最小的元素肯定是出現在兩個數組的開頭。
所以我們只需要比較兩個數組中開頭的兩個元素,較小的那一個就是兩個數組中最小的那一個,我們就將它拿出來放到一個新的地方,接著選出兩個元素中第二小的元素,依然是比較這個時候兩個數組開頭的兩個元素,誰較小,誰就是數組中第二小的,依次向下進行。
直到將所有的元素選擇出來,我們就把兩個有序的數組成功合并為一個更長的有序數組。

在我們實際應用的時候,兩個有序數組是兩個緊挨著的兩個有序空間。

在使用的時候,我們先把原始數組要合并的部分復制到一個新的區域,然后在復制出來的輔助數組上設置兩個下標 i 和 j ,分別指向兩個有序區間的第一個元素,然后再在輸入數組上設置一個變量k用于賦值。
首先比較 i 和 j 所指向的元素,我把就把小的數賦值為k,然后向后移動下標。

這也是個典型的空間換時間的一種做法。

在開始時那為了演示,使用的是同時拆分同時合并的,但在歸并排序當中,實際使用的是按照深度優先的順序完成歸并排序的。

按照深度優先的順序進行:
0 ~ 7分為0 ~ 3和4 ~ 7兩組;
然后0 ~ 3分為0 ~ 1和2 ~ 3兩組;
然后0 ~ 1分為 0和1;
再然后0和1整理為有序數組;
再又對2 ~ 3分為 2 和 3;
對2 和3 進行整理為有序數組;
再將 0 ~ 1和2 ~ 3進行整理為有序數組;
再對 4 ~ 7分為 4 ~ 5和 6 ~ 7兩組;
。。。。。
最后整理成為有序數組。

深度優先遍歷只要可以繼續拆分問題,就先拆分問題;直到只有一個元素時不能拆分的時候,才回到之前它走過的地方走另一條還沒有走過的路徑繼續走下去。直到走完所有的可能走的路徑。因此稱為深度優先遍歷

代碼實現:

import java.util.Arrays;public class Demo912_2 {public static void main(String[] args) {int[] nums = {-1, 2, -8, -10}; //給定一個數組int[] after = sortArray(nums); //的帶排序后的數組System.out.println(Arrays.toString(after)); //打印輸出得到數組}private static int[] sortArray(int[] nums) {int len = nums.length;int[] temp = new int[len];mergeSort(nums,0,len-1,temp);return nums;}/*** 遞歸函數對nums[left...right]進行歸并排序* @param nums 原數組* @param left 左邊的索引* @param right 右邊記錄索引位置* @param temp*/private static void mergeSort(int[] nums, int left, int right, int[] temp) {if (left == right){//當拆分到數組當中只要一個值的時候,結束遞歸return;}int mid = (left+right)/2; //找到下次要拆分的中間值mergeSort(nums,left,mid,temp);//記錄樹左邊的mergeSort(nums,mid+1,right,temp);//記錄樹右邊的//合并兩個區間for (int i = left; i <= right; i++) {temp[i] = nums[i]; //temp就是輔助列表,新列表的需要排序的值就是從輔助列表中拿到的}int i = left; //給輔助數組里面的值標點int j = mid +1;for (int k = left; k <= right ; k++) {//k 就為當前要插入的位置if (i == mid + 1){nums[k] = temp[j];j++;}else if (j == right+1){nums[k] = temp[i];i++;}else if (temp[i] <= temp[j]){nums[k] = temp[i];i++;}else {nums[k] = temp[j];j++;}}} }

時間復雜度:

對于復雜度分析來講,可以忽略這個常數項,而且對數的底我們也不在乎。
所以我們最終的時間復雜度為:

歸并排序的優化

第一個優化:
可以在小區間內使用插入排序。就是在判斷當left == right這里使用一個區間的插入排序,當數組長度較小的時候,使用插入排序。

在這里我們將這個值設置為16。當數組的長度小于16的時候,就使用插入排序,提高代碼執行效率。

第二個優化:
就是在合并兩個有序的數組之前,我們先看看這兩個數組拼接起來,是不是已經成為了有序的數組。

最終代碼:

歸并排序后兩種優化結束后的最終代碼:

import java.util.Arrays;public class Demo912_2 {public static void main(String[] args) {int[] nums = {-1, 2, -8, -10}; //給定一個數組int[] after = sortArray(nums); //的帶排序后的數組System.out.println(Arrays.toString(after)); //打印輸出得到數組}private static int[] sortArray(int[] nums) {int len = nums.length;int[] temp = new int[len];mergeSort(nums, 0, len - 1, temp);return nums;}/*** 遞歸函數對nums[left...right]進行歸并排序** @param nums 原數組* @param left 左邊的索引* @param right 右邊記錄索引位置* @param temp*/private static void mergeSort(int[] nums, int left, int right, int[] temp) {if (right - left < 16) {//當拆分到數組長度小于16的時候,直接插入排序來提高代碼運行速度insertionSort(nums, right, left);return;}int mid = (left + right) / 2; //找到下次要拆分的中間值mergeSort(nums, left, mid, temp);//記錄樹左邊的mergeSort(nums, mid + 1, right, temp);//記錄樹右邊的if (nums[mid] <= nums[mid + 1]) {//因為整數除法是向下取整,所以這里不會代寫mid + 1越界return;}//合并兩個區間for (int i = left; i <= right; i++) {temp[i] = nums[i]; //temp就是輔助列表,新列表的需要排序的值就是從輔助列表中拿到的}int i = left; //給輔助數組里面的值標點int j = mid + 1;for (int k = left; k <= right; k++) {//k 就為當前要插入的位置if (i == mid + 1) {nums[k] = temp[j];j++;} else if (j == right + 1) {nums[k] = temp[i];i++;} else if (temp[i] <= temp[j]) {nums[k] = temp[i];i++;} else {nums[k] = temp[j];j++;}}}/*** 執行插入排序* @param nums* @param right* @param left*/private static void insertionSort(int[] nums, int right, int left) {for (int i = left + 1; i <= right; i++) {int temp = nums[i];int j;for (j = i; j > left; j--) {if (nums[j - 1] > temp) {nums[j] = nums[j - 1];} else {break;}}nums[j] = temp;}} }

總結

歸并排序采用的是分治算法。

分治算法每一次執行都可以分為三個步驟:

  • 分解:把一個大問題拆成小問題;
  • 解決:逐個解決小問題;
  • 合并:再把這些小問題的結果組合起來組成大問題的解。
  • O(N logN)比O(N^2)快多少?
    一段代碼演示:

    public class Demo {public static void main(String[] args) {for (int N = 1; N <= 100_0000; N *= 10) {System.out.printf("N = % 8d", N);System.out.printf("\t N^2 = % 14d", N*N);System.out.printf("\t NlogN = % 10f", N * Math.log(N));System.out.println();}} }

    我們這采用的是遞歸來實現歸并排序,還有自底向上的歸并排序和原地完成的歸并排序。

    在理論自底向上的歸并排序是比采用遞歸快一丟丟的,但是考慮到學習成本來講,就沒必要了╰(●’?’●)╮ ?(?′0`?)?

    在上面的代碼中,我們使用了 int mid = (left + right) / 2;來找出當前數組中的中間值.
    在這里我們可以采用一個防止整型溢出的寫法:
    int mid = left +(right - left)/ 2;
    也可以使用一個無符號右移的方式,理論上更快一點點:
    int mid = left + (right - left) >> 1;
    也可以直接寫成:
    int mid =(right - left) >> 1;
    因為即使left +right會造成溢出,但右移以后,無符號的高位依然會補0,所以結論依然正確。

    練習:
    劍指offer 51. 數組中的逆序對
    LeetCode315、計算右側小于當前元素的個數

    總結

    以上是生活随笔為你收集整理的java实现归并排序(详解)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。