本文主要是數組和鏈表兩種結構,關於歸並排序算法的遞歸實現和非遞歸實現
思想
將數組進行分割,形成多個組合並繼續分割,一直到每一組只有一個元素時,此時可以看作每一組都是有序的
然后逐漸合並相鄰的有序組合(合並之后也是有序的),分組個數呈倍數減少,每一組的元素個數呈倍數增長
一直到只剩下一個組合包含所有元素,將代表着數組排序完畢
歸並排序是一種類似二叉樹遍歷的實現,所以時間復雜度與二叉樹遍歷一樣:o(n*log2n)
本來畫了個圖,但是因為太丑了,所以放在最下面
數組的歸並排序實現
自頂向下
使用遞歸不斷往下遍歷,一直到當前組合只有一個元素,開始回溯,將相鄰的組合進行合並,直到最后只剩下一個組合,數組即有序。
比如
44,8,33,5,1,9,2這組數字使用歸並排序的流程通過運行結果可以看到
首先遞歸到只有44一個元素的組合,然后回溯,等待相鄰組合變成有序之后,與相鄰組合8進行合並
44 , 8 這個組合 又等待相鄰組合 33 , 5 變成有序之后,進行合並,一直到合並完畢
每次合並的相鄰組合擁有的元素個數,必須與當前組合相等或者不足
實現代碼

1 import java.util.Arrays; 2 3 public class MergeSortDemo { 4 5 public static void main(String[] args) { 6 7 int[] arr = new int[]{44,8,33,5,1,9,2}; 8 9 System.out.println(Arrays.toString(arr)); 10 11 mergeSort(arr,0,arr.length-1); 12 13 System.out.println(Arrays.toString(arr)); 14 15 } 16 17 18 public static void mergeSort(int[] arr,int left,int right){ 19 20 if(left >= right){ 21 return; 22 } 23 int mid = (right - left)/2 + left; 24 25 mergeSort(arr,left,mid); 26 27 mergeSort(arr,mid+1,right); 28 29 merge(arr,left,mid,right); 30 } 31 public static void merge(int[] arr,int left,int mid,int right){ 32 33 int len = right - left + 1; 34 35 36 //分別記錄兩組元素的起始位置和結束位置 37 int s1 = left, e1 = mid,s2 = mid+1 , e2 = right; 38 39 int[] newArr = new int[len]; 40 int index = 0; 41 42 //將兩組元素進行合並 43 while (s1 <= e1 && s2 <= e2){ 44 45 if(arr[s1] > arr[s2]){ 46 newArr[index++] = arr[s2++]; 47 }else { 48 newArr[index++] = arr[s1++]; 49 } 50 } 51 52 while (s1 <= e1){ 53 newArr[index++] = arr[s1++]; 54 } 55 56 while (s2 <= e2){ 57 newArr[index++] = arr[s2++]; 58 } 59 //將排序好的結果復制回原數組 60 //新數組的下標[0...index] 對應 原數組[left...right] 61 for (int i = left; i <= right; i++) { 62 arr[i] = newArr[i - left]; 63 } 64 } 65 }
自底向上
自底向上沒有使用遞歸的方式,而是使用循環修改數組
首先設置一個分割值gap進行分組,每個組合的元素數量為gap,從1開始,呈倍數增長,一直到等於數組長度n
在gap每次增長之前,都將相鄰的組合進行合並,由n個組合並為1個組
通過運行結果可以看出,運行中有兩層循環
第一層循環不斷增長gap的值
第二層循環,根據gap的值,沿着數組每次找出兩個組合,進行合並,一直到找不到為止
合並過程與上面的做法類似
實現代碼

1 public class MergeSortDemo { 2 3 public static void main(String[] args) { 4 5 int[] arr = new int[]{44,8,33,5,1,9,2}; 6 7 System.out.println(Arrays.toString(arr)); 8 9 merge1(arr); 10 11 System.out.println(Arrays.toString(arr)); 12 13 } 14 15 public static void merge1(int[] arr){ 16 17 18 int gap = 1 , len = arr.length; 19 20 while (gap < len){ 21 22 int start = 0; 23 24 while (start < len){ 25 //兩個組合的起點 26 int s1 = start , s2 = start + gap ; 27 //終點 28 int e1 = s2 -1,e2 = s2 + gap - 1; 29 30 //數組長度不足,沒有第二個組合了 31 if(s2 >= len){ 32 break; 33 } 34 35 //第二個組合長度小於第一個組合 36 if(e2 >= len){ 37 e2 = len - 1; 38 } 39 40 int left = start,right = e2; 41 42 int index = 0; 43 44 int size = right - left + 1; 45 int[] tmpArr = new int[size]; 46 47 while (s1 <= e1 && s2 <= e2){ 48 if(arr[s1] < arr[s2]){ 49 tmpArr[index] = arr[s1++]; 50 }else { 51 tmpArr[index] = arr[s2++]; 52 } 53 54 index++; 55 } 56 57 while (s1 <= e1){ 58 tmpArr[index++] = arr[s1++]; 59 } 60 61 while (s2 <= e2){ 62 tmpArr[index++] = arr[s2++]; 63 } 64 65 for (int i = left; i <= right ; i++) { 66 arr[i] = tmpArr[i - left]; 67 } 68 69 start = e2 + 1; 70 } 71 72 gap *= 2; 73 } 74 } 75 }
鏈表的歸並排序實現
鏈表的數據結構
public class ListNode { int val; ListNode next; ListNode() { } ListNode(int val) { this.val = val; } ListNode(int val, ListNode next) { this.val = val; this.next = next; } }
鏈表跟數組相比,不能用下標訪問,不知道長度,不能立即分割。
鏈表的賦值使用一個不存儲數據的頭節點,將數據往后插入。
跟數組不一樣的是,鏈表需要真正的把節點一個個分割,在合並時再將節點連接起來。
自頂向下
不斷使用快慢指針得出鏈表中間節點,將整個鏈表進行分割,一直分割到每個鏈表都只有一個節點時再合並
代碼實現

1 public class ListNode { 2 int val; 3 ListNode next; 4 5 ListNode() { 6 } 7 8 ListNode(int val) { 9 this.val = val; 10 } 11 12 ListNode(int val, ListNode next) { 13 this.val = val; 14 this.next = next; 15 } 16 17 18 public static void main(String[] args) { 19 //頭節點不存儲數據 20 ListNode tou = new ListNode(); 21 ListNode tmp = tou; 22 int[] arr = new int[]{44,8,33,5,1,9,2}; 23 24 //借助臨時節點擴充鏈表 25 for (int i = 0; i < 7; i++) { 26 tmp.next = new ListNode(); 27 tmp = tmp.next; 28 tmp.val = arr[i]; 29 } 30 31 ListNode sortedHead = sortList(tou.next); 32 33 while (sortedHead != null){ 34 System.out.print(sortedHead.val + ","); 35 sortedHead = sortedHead.next; 36 } 37 } 38 39 public static ListNode sortList(ListNode head) { 40 41 if(head == null || head.next == null){ 42 return head; 43 } 44 45 ListNode fast = head,slow = head; 46 47 //快指針走的步數 = 慢指針 * 2 48 /* 49 鏈表長度為偶數時,快指針到倒數第二個節點,慢指針到中間兩個節點中的前一個 50 奇數時,快指針到最后一個節點,慢指針到中間節點 51 */ 52 //當快指針沒法走下去時,說明慢指針已經到達了鏈表的中間節點 53 while (fast.next != null && fast.next.next != null){ 54 slow = slow.next; 55 fast = fast.next.next; 56 } 57 //第二部分的開始節點 58 ListNode mid = slow.next; 59 //分割出第一部分 60 slow.next = null; 61 62 //當鏈表的節點數量超過一個時,繼續分割 63 if(head.next != null){ 64 head = sortList(head); 65 } 66 67 if(mid.next != null){ 68 mid = sortList(mid); 69 } 70 71 return merge(head,mid); 72 } 73 74 public static ListNode merge(ListNode l1,ListNode l2){ 75 76 ListNode pre = new ListNode(); 77 ListNode tou = pre; 78 while (l1 != null && l2 != null){ 79 pre.next = new ListNode(); 80 pre = pre.next; 81 82 if(l1.val > l2.val){ 83 pre.val = l2.val; 84 l2 = l2.next; 85 86 }else { 87 pre.val = l1.val; 88 l1 = l1.next; 89 } 90 } 91 //將剩余的節點在后面插入 92 pre.next = l1 == null ? l2 : l1; 93 94 return tou.next; 95 } 96 }
自底向上
要使用gap對鏈表分組,需要先計算鏈表的長度
與數組一樣是兩層循環,第一層gap不斷倍增
第二層循環使用h作為不斷遍歷原鏈表的輔助節點,h1,h2確定兩個要合並的鏈表,i1,i2確定鏈表的長度,然后合並
代碼實現

1 package node; 2 3 public class ListNode { 4 int val; 5 ListNode next; 6 7 ListNode() { 8 } 9 10 ListNode(int val) { 11 this.val = val; 12 } 13 14 ListNode(int val, ListNode next) { 15 this.val = val; 16 this.next = next; 17 } 18 19 20 public static void main(String[] args) { 21 //頭節點不存儲數據 22 ListNode tou = new ListNode(); 23 ListNode tmp = tou; 24 int[] arr = new int[]{44,8,33,5,1,9,2}; 25 26 //借助臨時節點擴充鏈表 27 for (int i = 0; i < 7; i++) { 28 tmp.next = new ListNode(); 29 tmp = tmp.next; 30 tmp.val = arr[i]; 31 } 32 33 34 ListNode sortedHead = merge1(tou.next); 35 36 while (sortedHead != null){ 37 System.out.print(sortedHead.val + ","); 38 sortedHead = sortedHead.next; 39 } 40 } 41 42 //自底向上的歸並排序 43 public static ListNode merge1(ListNode head){ 44 45 int len = 0 , gap = 1; 46 ListNode tmp = head; 47 while (tmp != null){ 48 tmp = tmp.next; 49 len++; 50 } 51 52 ListNode pre,h,h1,h2 ; 53 ListNode tou = new ListNode(); 54 tou.next = head; 55 56 /* 57 每次循環指定一個頭節點,從該節點開始根據gap去獲取要比較的兩部分的頭節點 58 將兩個部分的節點按順序加入該節點 59 該節點指向還沒排序的后續節點 60 */ 61 while (gap < len){ 62 pre = tou; 63 64 h = pre.next; 65 while (h != null){ 66 int i1 = gap; 67 h1 = h; 68 while (i1 > 0 && h != null){ 69 i1--; 70 h = h.next; 71 } 72 //第一部分已經到鏈表的終點 73 if(i1 > 0){ 74 break; 75 } 76 h2 = h; 77 int i2 = gap; 78 while (i2 > 0 && h != null){ 79 i2--; 80 h = h.next; 81 } 82 83 int l1 = gap; 84 //第二部分的長度 85 int l2 = gap - i2; 86 87 /* 88 不用新創建節點的方式 89 而是將現有節點插入到頭節點后面 90 */ 91 while (l1 > 0 && l2 > 0){ 92 if(h1.val > h2.val){ 93 pre.next = h2; 94 h2 = h2.next; 95 l2--; 96 }else { 97 pre.next = h1; 98 h1 = h1.next; 99 l1--; 100 } 101 pre = pre.next; 102 } 103 104 pre.next = l1 == 0 ? h2 : h1; 105 //需要遍歷完已經合並的兩個鏈表,才能合並的后續鏈表 106 while (l1 > 0 || l2 > 0){ 107 pre = pre.next; 108 l1--; 109 l2--; 110 } 111 //將未排序的鏈表接在后面 112 pre.next = h; 113 } 114 115 gap *= 2; 116 } 117 return tou.next; 118 } 119 }
圖