1. 程式人生 > >合並兩個有序單鏈表

合並兩個有序單鏈表

理解 你在 ati write 編程 ems index all rebuild

在歸並排序中,對順序存儲的且為升序的兩個列表a和b進行合並,合並後的列表為c,實現如下:

 1 /**
 2  * Merge two sorted src array a[] and b[] to dst array c[]
 3  */
 4 void merge0(int c[],  size_t nc, int a[], size_t na, int b[], size_t nb)
 5 {
 6         int i = 0; /* walk array a : read  */
 7         int j = 0; /* walk array b : read  */
 8         int
k = 0; /* walk array c : write */ 9 10 while (i < na && j < nb) { 11 int t = 0; 12 13 if (a[i] < b[j]) { 14 t = a[i]; /* save a[i] to t */ 15 i++; /* move index of a[] forward */ 16 } else
{ 17 t = b[j]; /* save b[j] to t */ 18 j++; /* move index of b[] forward */ 19 } 20 21 c[k] = t; /* now save x to c[k] */ 22 k++; /* move index of c[] forward */ 23 }
24 25 /* copy the left of a[] to c[] */ 26 while (i < na) 27 c[k++] = a[i++]; 28 29 /* copy the left of b[] to c[] */ 30 while (j < nb) 31 c[k++] = b[j++]; 32 }

那麽,如何合並兩個有序的按升序排列的單鏈表呢? 方法有三:

  • 方法一: 將鏈表a和鏈表b的每一個結點的地址都dump出來,轉化為順序存儲處理(設存入 A[]和B[]),然後使用上面的merge0()算法,設合並後的存儲數組為C[], 最後將C[]的結點地址重新組織為一個單鏈表。這個方法的實現起來比較容易,但是時間復雜度和空間復雜度都比較高。
  • 方法二: 使用鏈式插入排序,假設鏈表a的頭結點的數據域較小,那麽可以遍歷鏈表b的每一個結點,將結點逐個插入到鏈表a中。這一方法實現起來不是很容易,因為鏈式插入排序的實現相對復雜。另外,這一方法的時間復雜度也不是很高。
  • 方法三: 仿照順序存儲列表的合並方法對單鏈表a和b進行合並,時間復雜度很不錯,只是實現起來不是很直觀,也不是很容易。

方法一

  1 static int
  2 get_list_length(list_t *head)
  3 {
  4         int len = 0;
  5         for (list_t *p = head; p != NULL; p = p->next)
  6                 len++;
  7         return len;
  8 }
  9 
 10 static void
 11 dump_list_node_addr(list_t *head, uintptr_t **saveto, int *saveto_sz)
 12 {
 13         int len = get_list_length(head);
 14 
 15         uintptr_t *aux = (uintptr_t *)malloc(sizeof (uintptr_t) * len);
 16         if (aux == NULL) {
 17                 *saveto = NULL;
 18                 *saveto_sz = 0;
 19                 return;
 20         }
 21 
 22         int index = 0;
 23         for (list_t *p = head; p != NULL; p = p->next)
 24                 aux[index++] = (uintptr_t)p;
 25 
 26         *saveto = aux;
 27         *saveto_sz = len;
 28 }
 29 
 30 static void
 31 merge0(uintptr_t *c, int nc, uintptr_t *a, int na, uintptr_t *b, int nb)
 32 {
 33         int i = 0;
 34         int j = 0;
 35         int k = 0;
 36 
 37         while (i < na && j < nb) {
 38                 if (((list_t *)a[i])->data < ((list_t *)b[j])->data)
 39                         c[k++] = a[i++];
 40                 else
 41                         c[k++] = b[j++];
 42         }
 43 
 44         while (i < na)
 45                 c[k++] = a[i++];
 46 
 47         while (j < nb)
 48                 c[k++] = b[j++];
 49 }
 50 
 51 /**
 52  * Merge two sorted single linked lists (dst and src).
 53  */
 54 list_t *
 55 merge1(list_t *head1, list_t *head2)
 56 {
 57         if (head1 == NULL)
 58                 return head2;
 59 
 60         if (head2 == NULL)
 61                 return head1;
 62 
 63         list_t *out = NULL;
 64 
 65         uintptr_t *a = NULL;
 66         uintptr_t *b = NULL;
 67         uintptr_t *c = NULL;
 68         int na = 0;
 69         int nb = 0;
 70         int nc = 0;
 71 
 72         /* 1. dump the address of per node of list 1 to a[] */
 73         dump_list_node_addr(head1, &a, &na);
 74         if (a == NULL)
 75                 goto done;
 76 
 77         /* 2. dump the address of per node of list 2 to a[] */
 78         dump_list_node_addr(head2, &b, &nb);
 79         if (b == NULL)
 80                 goto done;
 81 
 82         /* 3. alloc memory for c[] */
 83         nc = na + nb;
 84         c = (uintptr_t *)malloc(sizeof (uintptr_t) * nc);
 85         if (c == NULL)
 86                 goto done;
 87         memset(c, 0, nc);
 88 
 89         /* 4. merge a[] and b[] to c[] */
 90         merge0(c, nc, a, na, b, nb);
 91 
 92         /* 5. rebuild dst single linked list according to c[] */
 93         for (int i = 0; i < nc - 1; i++)
 94                 ((list_t *)c[i])->next = (list_t *)c[i+1];
 95         ((list_t *)c[nc-1])->next = NULL;
 96         out = (list_t *)c[0];
 97 
 98 done:
 99         if (c != NULL) free(c);
100         if (b != NULL) free(b);
101         if (a != NULL) free(a);
102 
103         return out;
104 }

在上面的方法中,假設鏈表a的長度為na, 鏈表b的長度為nb, 一個指針的大小為8個字節(64位處理器上),那麽我們使用的輔助存儲為 8 * (na + nb) * 2。而時間復雜度,大約是O(4*(na+nb))。方法雖然比較笨,但是要寫出上面的代碼,需要對指針的本質有深刻的理解。

方法二

 1 /**
 2  * Insertion Sort on a Single Linked List : insert a node to the sorted list
 3  */
 4 static void
 5 list_insert(list_t **head, list_t *node)
 6 {
 7         if (*head == NULL) {
 8                 *head = node;
 9                 return;
10         }
11 
12         /* get both prev and next of the node to insert */
13         list_t *node_prev = *head;
14         list_t *node_next = NULL;
15         for (list_t *p = *head; p != NULL; p = p->next) {
16                 if (p->data <= node->data) {
17                         node_prev = p;
18                         continue;
19                 }
20 
21                 node_next = p;
22                 break;
23         }
24 
25         if (node_next == NULL) { /* append node to the tail */
26                 node_prev->next = node;
27         } else {
28                 if (node_next == node_prev) { /* == *head */
29                         node->next = *head;
30                         *head = node;
31                         return;
32                 }
33 
34                 /* node_prev -> node -> node_next */
35                 node_prev->next = node;
36                 node->next = node_next;
37         }
38 }
39 
40 /**
41  * Merge two sorted single linked lists (dst and src).
42  */
43 list_t *
44 merge2(list_t *head1, list_t *head2)
45 {
46         if (head1 == NULL)
47                 return head2;
48 
49         if (head2 == NULL)
50                 return head1;
51 
52         /* now merge the two lists */
53         list_t *out = NULL;
54         list_t *p = NULL;
55         if (head1->data < head2->data) {
56                 out = head1;
57                 p = head2;
58         } else {
59                 out = head2;
60                 p = head1;
61         }
62 
63         /*
64          * insert per node of list ‘p‘ to the dst list one by one, and always
65          * pick up the previous node inserted as the new head for getting good
66          * time complexity once list_insert() is called
67          */
68         list_t *head = out;
69         while (p != NULL) {
70                 list_t *this = p;
71                 p = p->next;
72                 this->next = NULL;
73                 list_insert(&head, this);
74                 head = this;
75         }
76 
77         return out;
78 }

本方法最關鍵的是需要實現鏈式插入排序的核心函數list_insert()。 時間復雜度大約在O(na+nb),但實現的主題函數merge2()非常容易理解。

方法三

 1 static void
 2 list_insert_node_tail(list_t **head, list_t *node)
 3 {
 4         static list_t *tail = NULL;
 5 
 6         if (*head == NULL) {
 7                 *head = tail = node;
 8                 return;
 9         }
10 
11         tail->next = node;
12         tail = node;
13 }
14 
15 list_t *
16 merge3(list_t *head1, list_t *head2)
17 {
18         list_t *out = NULL;
19         list_t *p1 = head1;
20         list_t *p2 = head2;
21 
22         while (p1 != NULL && p2 != NULL) {
23                 list_t *node = NULL;
24 
25                 if (p1->data < p2->data) {
26                         node = p1;                 /* 1. save p1 to node */
27                         p1 = p1->next;             /* 2. move p1 forward */
28                 } else {
29                         node = p2;                 /* 1. save p2 to node */
30                         p2 = p2->next;             /* 2. move p2 forward */
31                 }
32 
33                 node->next = NULL;                 /* 3. cut node‘s next off */
34                 list_insert_node_tail(&out, node); /* 4. append node to out */
35         }
36 
37         if (p1 != NULL) /* link the left of list 1 to the tail of out */
38                 list_insert_node_tail(&out, p1);
39 
40         if (p2 != NULL) /* link the left of list 2 to the tail of out */
41                 list_insert_node_tail(&out, p2);
42 
43         return out;
44 }

這才是一個高效的實現方法,因為時間復雜度為O(na+nb), 空間復雜度為O(1)。

小結: 鏈表操作體現的是工程師的編程功底,如果你在面試中遇到這樣的問題,方法三通常是面試官所期待的。但是,實在沒有辦法在短時間內想明白的話,方法一和方法二也是可以的,至少表明你是有想法的程序員。完整代碼實現戳這裏。

合並兩個有序單鏈表