歸並排序:數組和鏈表的多種實現


本文主要是數組和鏈表兩種結構,關於歸並排序算法的遞歸實現和非遞歸實現

思想

將數組進行分割,形成多個組合並繼續分割,一直到每一組只有一個元素時,此時可以看作每一組都是有序的

然后逐漸合並相鄰的有序組合(合並之后也是有序的),分組個數呈倍數減少,每一組的元素個數呈倍數增長

一直到只剩下一個組合包含所有元素,將代表着數組排序完畢

歸並排序是一種類似二叉樹遍歷的實現,所以時間復雜度與二叉樹遍歷一樣: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 }
View Code

 

 

 自底向上

自底向上沒有使用遞歸的方式,而是使用循環修改數組

首先設置一個分割值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 }        
View Code

 

鏈表的歸並排序實現

鏈表的數據結構

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 }
View Code

 

自底向上

要使用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 }
View Code

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM