java实现归并排序(详解)
主要思想
歸并排序和快速排序都是基于分而治之的算法思想。
歸并排序先將待排序的數(shù)組不斷拆分,直到拆分到區(qū)間里只剩下一個元素的時候。不能再拆分的時候。這個時候我們再想辦法合并兩個有序的數(shù)組,得到長度更長的有序數(shù)組。當前合并好的有序數(shù)組為下一輪得到更長的有序數(shù)組做好了準備。一層一層的合并回去,直到整個數(shù)組有序。
在這各整個過程當中,不停做的事情就是拆分問題和組合子問題的解,在這里很顯然我們是需要使用遞歸來實現(xiàn)歸并排序。
如何合并兩個有序數(shù)組
在這里我們是一個一個的選出來的。
先選出兩個數(shù)組里最小的元素,然后再選出兩個數(shù)組中第二小的元素,因為兩個數(shù)組都是有序的,所以兩個數(shù)組里的最小的元素肯定是出現(xiàn)在兩個數(shù)組的開頭。
所以我們只需要比較兩個數(shù)組中開頭的兩個元素,較小的那一個就是兩個數(shù)組中最小的那一個,我們就將它拿出來放到一個新的地方,接著選出兩個元素中第二小的元素,依然是比較這個時候兩個數(shù)組開頭的兩個元素,誰較小,誰就是數(shù)組中第二小的,依次向下進行。
直到將所有的元素選擇出來,我們就把兩個有序的數(shù)組成功合并為一個更長的有序數(shù)組。
在我們實際應用的時候,兩個有序數(shù)組是兩個緊挨著的兩個有序空間。
在使用的時候,我們先把原始數(shù)組要合并的部分復制到一個新的區(qū)域,然后在復制出來的輔助數(shù)組上設置兩個下標 i 和 j ,分別指向兩個有序區(qū)間的第一個元素,然后再在輸入數(shù)組上設置一個變量k用于賦值。
首先比較 i 和 j 所指向的元素,我把就把小的數(shù)賦值為k,然后向后移動下標。
這也是個典型的空間換時間的一種做法。
在開始時那為了演示,使用的是同時拆分同時合并的,但在歸并排序當中,實際使用的是按照深度優(yōu)先的順序完成歸并排序的。
按照深度優(yōu)先的順序進行:
0 ~ 7分為0 ~ 3和4 ~ 7兩組;
然后0 ~ 3分為0 ~ 1和2 ~ 3兩組;
然后0 ~ 1分為 0和1;
再然后0和1整理為有序數(shù)組;
再又對2 ~ 3分為 2 和 3;
對2 和3 進行整理為有序數(shù)組;
再將 0 ~ 1和2 ~ 3進行整理為有序數(shù)組;
再對 4 ~ 7分為 4 ~ 5和 6 ~ 7兩組;
。。。。。
最后整理成為有序數(shù)組。
深度優(yōu)先遍歷只要可以繼續(xù)拆分問題,就先拆分問題;直到只有一個元素時不能拆分的時候,才回到之前它走過的地方走另一條還沒有走過的路徑繼續(xù)走下去。直到走完所有的可能走的路徑。因此稱為深度優(yōu)先遍歷
代碼實現(xiàn):
import java.util.Arrays;public class Demo912_2 {public static void main(String[] args) {int[] nums = {-1, 2, -8, -10}; //給定一個數(shù)組int[] after = sortArray(nums); //的帶排序后的數(shù)組System.out.println(Arrays.toString(after)); //打印輸出得到數(shù)組}private static int[] sortArray(int[] nums) {int len = nums.length;int[] temp = new int[len];mergeSort(nums,0,len-1,temp);return nums;}/*** 遞歸函數(shù)對nums[left...right]進行歸并排序* @param nums 原數(shù)組* @param left 左邊的索引* @param right 右邊記錄索引位置* @param temp*/private static void mergeSort(int[] nums, int left, int right, int[] temp) {if (left == right){//當拆分到數(shù)組當中只要一個值的時候,結(jié)束遞歸return;}int mid = (left+right)/2; //找到下次要拆分的中間值mergeSort(nums,left,mid,temp);//記錄樹左邊的mergeSort(nums,mid+1,right,temp);//記錄樹右邊的//合并兩個區(qū)間for (int i = left; i <= right; i++) {temp[i] = nums[i]; //temp就是輔助列表,新列表的需要排序的值就是從輔助列表中拿到的}int i = left; //給輔助數(shù)組里面的值標點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++;}}} }時間復雜度:
對于復雜度分析來講,可以忽略這個常數(shù)項,而且對數(shù)的底我們也不在乎。
所以我們最終的時間復雜度為:
歸并排序的優(yōu)化
第一個優(yōu)化:
可以在小區(qū)間內(nèi)使用插入排序。就是在判斷當left == right這里使用一個區(qū)間的插入排序,當數(shù)組長度較小的時候,使用插入排序。
在這里我們將這個值設置為16。當數(shù)組的長度小于16的時候,就使用插入排序,提高代碼執(zhí)行效率。
第二個優(yōu)化:
就是在合并兩個有序的數(shù)組之前,我們先看看這兩個數(shù)組拼接起來,是不是已經(jīng)成為了有序的數(shù)組。
最終代碼:
歸并排序后兩種優(yōu)化結(jié)束后的最終代碼:
import java.util.Arrays;public class Demo912_2 {public static void main(String[] args) {int[] nums = {-1, 2, -8, -10}; //給定一個數(shù)組int[] after = sortArray(nums); //的帶排序后的數(shù)組System.out.println(Arrays.toString(after)); //打印輸出得到數(shù)組}private static int[] sortArray(int[] nums) {int len = nums.length;int[] temp = new int[len];mergeSort(nums, 0, len - 1, temp);return nums;}/*** 遞歸函數(shù)對nums[left...right]進行歸并排序** @param nums 原數(shù)組* @param left 左邊的索引* @param right 右邊記錄索引位置* @param temp*/private static void mergeSort(int[] nums, int left, int right, int[] temp) {if (right - left < 16) {//當拆分到數(shù)組長度小于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]) {//因為整數(shù)除法是向下取整,所以這里不會代寫mid + 1越界return;}//合并兩個區(qū)間for (int i = left; i <= right; i++) {temp[i] = nums[i]; //temp就是輔助列表,新列表的需要排序的值就是從輔助列表中拿到的}int i = left; //給輔助數(shù)組里面的值標點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++;}}}/*** 執(zhí)行插入排序* @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;}} }總結(jié)
歸并排序采用的是分治算法。
分治算法每一次執(zhí)行都可以分為三個步驟:
O(N logN)比O(N^2)快多少?
一段代碼演示:
我們這采用的是遞歸來實現(xiàn)歸并排序,還有自底向上的歸并排序和原地完成的歸并排序。
在理論自底向上的歸并排序是比采用遞歸快一丟丟的,但是考慮到學習成本來講,就沒必要了╰(●’?’●)╮ ?(?′0`?)?
在上面的代碼中,我們使用了 int mid = (left + right) / 2;來找出當前數(shù)組中的中間值.
在這里我們可以采用一個防止整型溢出的寫法:
int mid = left +(right - left)/ 2;
也可以使用一個無符號右移的方式,理論上更快一點點:
int mid = left + (right - left) >> 1;
也可以直接寫成:
int mid =(right - left) >> 1;
因為即使left +right會造成溢出,但右移以后,無符號的高位依然會補0,所以結(jié)論依然正確。
練習:
劍指offer 51. 數(shù)組中的逆序?qū)?br /> LeetCode315、計算右側(cè)小于當前元素的個數(shù)
總結(jié)
以上是生活随笔為你收集整理的java实现归并排序(详解)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 303,HTTP 的重定向301
- 下一篇: SEO | 重新认识一下Meta标签