合並兩個有序單鏈表
阿新 • • 發佈:2017-10-22
理解 你在 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 intk = 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)。
小結: 鏈表操作體現的是工程師的編程功底,如果你在面試中遇到這樣的問題,方法三通常是面試官所期待的。但是,實在沒有辦法在短時間內想明白的話,方法一和方法二也是可以的,至少表明你是有想法的程序員。完整代碼實現戳這裏。
合並兩個有序單鏈表