怎样实现链表的归并排序
2014-01-27 01:06
281 查看
#返回上一级
@Author: 张海拔
@Update: 2014-01-27
@Link: http://www.cnblogs.com/zhanghaiba/p/3534521.html
链表不像数组通过计算来随机存取,高效的排序算法如快速排序、堆排序都比较难实现,而归并排序就适合给链表排序。
在"有序单链表的合并 link(public)"问题中,我对带头结点和不带头结点的 有序单链表的合并算法做了比较全面的介绍。
知道了 链表的就地合并算法 比数组的合并算法时间效率要好一些,而空间复杂度则完胜,是O(1)(不然怎么叫就地)。
我们已经熟悉数组的“归并排序 link(public) ”,链表的归并排序用到的归并算法肯定是一样的,是典型的分治(二分)过程。
还是先给出伪代码:
merge_sort()
{
if 元素只有一个(已经有序)
return;
划分为左右两段
对左段进行merge_sort()
对右段进行merge_sort()
//此时左段和右端已经有序
对左段和右段进行sorted_merge()
}
sorted_list_merge()我们已经实现过了,merge_sort的思路也清晰了,剩下的问题是,怎样把一段链表划分为两段?
链表的追及问题其实挺经典的,比如“链表是否有环”和“确定链表倒数第K个元素”,这样的问题都是用到追击(相对速度)的思路。
这里也一样,使用一个slow指针和一个fast指针,让fast指针相对slow指针的移动速度是单位1。这样fast走到尽头时,slow就在“中间”位置了。
这个时候需要考虑一些边界问题。
只有一个元素时,merge_sort()直接返回(已经有序,不用再处理),不再划分。
那么只有两个元素的情况对应划分的最小子问题——
首先,直接让slow初始指向first,而fast初始指向first->next,
然后每次前进让fast先后一步再验证是否到边界,若没有则fast和slow都走一步。
这样初始化后,fast走一步后发现到了边界,slow便不走,还是指向first。
将左段链表的首节点指针left = first,右段链表的首节点指针right = slow->next,然后切断原链表,即slow->next = NULL;
对于最小子问题这样划分是正确的(left指向第一个节点,right指向第二个节点)。
由于fast相对slow速度为1,且最小子问题也正确,对于更大的子问题,不管划分边界是[n/2]取上界还是下界问题都不大。
所以,这个算法时间复杂度仍然是O(n*lgn),空间复杂度O(1)
数组的归并排序合并过程需要来回两次循环复制即2*O(n),而链表的归并排序是划分过程仅需要循环一次1*O(n)。
两者递归的深度是一样的(都是二分)。
综合来看,链表归并排序的时间复杂度系数应该更低,即理论上会比数组归并排序更快。
下面给出链表归并排序的完整实现——
其中:
sorted_list_merge_in_place()是递归实现的
sorted_list_mrege_in_place2()是迭代实现的
list_merge_sort()接受带头结点的单链表,返回也是带头结点的单链表
list_merge_sort_core()接受不带头结点的单链表,返回的也是不带头结点的单链表
通过简单包装一下list_merge_sort_core(),即可实现list_merge_sort()
默认支持带头结点的单链表是考虑到问题集的其它链表都是带头结点的,而不带头结点适用性更强,实现了后者,前者只需简单包装就能实现。
约定:带头结点的单链表的头结点指针命名为head,不带头结点的单链表的第一个(首)结点的指针命名为first。带头结点的单链表的第一个有效元素的指针也叫first。
测试示范:
#返回上一级
@Author: 张海拔
@Update: 2014-01-27
@Link: http://www.cnblogs.com/zhanghaiba/p/3534521.html
链表不像数组通过计算来随机存取,高效的排序算法如快速排序、堆排序都比较难实现,而归并排序就适合给链表排序。
在"有序单链表的合并 link(public)"问题中,我对带头结点和不带头结点的 有序单链表的合并算法做了比较全面的介绍。
知道了 链表的就地合并算法 比数组的合并算法时间效率要好一些,而空间复杂度则完胜,是O(1)(不然怎么叫就地)。
我们已经熟悉数组的“归并排序 link(public) ”,链表的归并排序用到的归并算法肯定是一样的,是典型的分治(二分)过程。
还是先给出伪代码:
merge_sort()
{
if 元素只有一个(已经有序)
return;
划分为左右两段
对左段进行merge_sort()
对右段进行merge_sort()
//此时左段和右端已经有序
对左段和右段进行sorted_merge()
}
sorted_list_merge()我们已经实现过了,merge_sort的思路也清晰了,剩下的问题是,怎样把一段链表划分为两段?
链表的追及问题其实挺经典的,比如“链表是否有环”和“确定链表倒数第K个元素”,这样的问题都是用到追击(相对速度)的思路。
这里也一样,使用一个slow指针和一个fast指针,让fast指针相对slow指针的移动速度是单位1。这样fast走到尽头时,slow就在“中间”位置了。
这个时候需要考虑一些边界问题。
只有一个元素时,merge_sort()直接返回(已经有序,不用再处理),不再划分。
那么只有两个元素的情况对应划分的最小子问题——
首先,直接让slow初始指向first,而fast初始指向first->next,
然后每次前进让fast先后一步再验证是否到边界,若没有则fast和slow都走一步。
这样初始化后,fast走一步后发现到了边界,slow便不走,还是指向first。
将左段链表的首节点指针left = first,右段链表的首节点指针right = slow->next,然后切断原链表,即slow->next = NULL;
对于最小子问题这样划分是正确的(left指向第一个节点,right指向第二个节点)。
由于fast相对slow速度为1,且最小子问题也正确,对于更大的子问题,不管划分边界是[n/2]取上界还是下界问题都不大。
所以,这个算法时间复杂度仍然是O(n*lgn),空间复杂度O(1)
数组的归并排序合并过程需要来回两次循环复制即2*O(n),而链表的归并排序是划分过程仅需要循环一次1*O(n)。
两者递归的深度是一样的(都是二分)。
综合来看,链表归并排序的时间复杂度系数应该更低,即理论上会比数组归并排序更快。
下面给出链表归并排序的完整实现——
其中:
sorted_list_merge_in_place()是递归实现的
sorted_list_mrege_in_place2()是迭代实现的
list_merge_sort()接受带头结点的单链表,返回也是带头结点的单链表
list_merge_sort_core()接受不带头结点的单链表,返回的也是不带头结点的单链表
通过简单包装一下list_merge_sort_core(),即可实现list_merge_sort()
默认支持带头结点的单链表是考虑到问题集的其它链表都是带头结点的,而不带头结点适用性更强,实现了后者,前者只需简单包装就能实现。
约定:带头结点的单链表的头结点指针命名为head,不带头结点的单链表的第一个(首)结点的指针命名为first。带头结点的单链表的第一个有效元素的指针也叫first。
/* *Author: ZhangHaiba *Date: 2014-1-26 *File: merge_sort_for_singly_linked_list.c * *a demo shows merge sort for singly_linked_list */ #include <stdio.h> #include <stdlib.h> #define INF 0x7fffffff typedef struct node * link; typedef struct node { int item; link next; }node; //public /* *recieve a singly linked list with Head Node and *return a singly linked list with Head Node as well */ link list_merge_sort(link list_head); link NODE(int item, link next); link list_create(int n); void list_travel(link head); void list_destroy(link head); //private /*recieve a singly linked list without Head Node and *return a singly linked list without Head Node as well */ link list_merge_sort_core(link list_first); void list_divide(link src_list, link *left_list, link *right_list); link sorted_list_merge_in_place(link src_list_a, link src_list_b); //recusive link sorted_list_merge_in_place2(link src_list_a, link src_list_b); //iterative int main(void) { int n; scanf("%d", &n); link list_a = list_create(n); printf("before merge sort, travel:\n"); list_travel(list_a); list_a = list_merge_sort(list_a); printf("after merge sort, travel:\n"); list_travel(list_a); list_destroy(list_a); return 0; } link list_merge_sort(link head) { if (head == NULL) //if [list with Head Node] have not Head Node return NULL; return NODE( INF, list_merge_sort_core(head->next) ); } link list_merge_sort_core(link first) //first node pointer { link left, right; if (first == NULL || first->next == NULL) return first; list_divide(first, &left, &right); left = list_merge_sort_core(left); right = list_merge_sort_core(right); return sorted_list_merge_in_place2(left, right); } //guarantee first != NULL void list_divide(link first, link *left, link *right) { link slow = first, fast = first->next; while (fast != NULL) { fast = fast->next; if (fast != NULL) { fast = fast->next; slow = slow->next; } } *left = first; *right = slow->next; slow->next = NULL; } link sorted_list_merge_in_place(link left, link right) { if (left == NULL) return right; if (right == NULL) return left; link first = NULL; if (left->item <= right->item) { first = left; first->next = sorted_list_merge_in_place(left->next, right); } else { first = right; first->next = sorted_list_merge_in_place(left, right->next); } return first; } link sorted_list_merge_in_place2(link left, link right) { link tmp_head = NODE(INF, NULL); link first = tmp_head; for (; left != NULL && right != NULL; first = first->next) { if (left->item <= right->item) first->next = left, left = left->next; else first->next = right, right = right->next; } first->next = left != NULL ? left : right; first = tmp_head->next; free(tmp_head); return first; } link NODE(int item, link next) { link born = malloc(sizeof (node)); born->item = item; born->next = next; return born; } //tail insert link list_create(int n) { int i, item; link head = NODE(INF, NULL); link tail = head; for (i = 0; i < n; ++i) { scanf("%d", &item); tail->next = NODE(item, NULL); tail = tail->next; } return head; } void list_travel(link head) { for (head = head->next; head != NULL; head = head->next) printf(head->next == NULL ? "%d\n" : "%d ", head->item); } void list_destroy(link head) { head->next == NULL ? free(head) : list_destroy(head->next); }
测试示范:
ZhangHaiba-MacBook-Pro:code apple$ ./a.out 1 435 before merge sort, travel: 435 after merge sort, travel: 435 ZhangHaiba-MacBook-Pro:code apple$ ./a.out 9 4324 31 515 41 46 43 8 53 3 before merge sort, travel: 4324 31 515 41 46 43 8 53 3 after merge sort, travel: 3 8 31 41 43 46 53 515 4324
#返回上一级
相关文章推荐
- c++实现链表归并排序
- 采用队列实现自底向上链表归并排序
- 单链表的原地归并排序实现
- JavaScript实现链表插入排序和链表归并排序
- 单向链表的原地归并排序实现
- 链表归并排序的递归与非递归实现
- 怎样编写一个程序,把一个有序整数数组放到二叉树中? 编写实现链表排序的一种算法。说明为什么你会选择用这样的方法?
- 自然归并排序和单链表实现的归并排序
- JavaScript实现链表插入排序和链表归并排序
- 单向链表实现归并排序(Java实现)
- LeetCode.23 Merge k Sorted Lists (对数组链表进行合并,归并排序 && 或者使用PriorityQueue实现)
- C++:探究纯虚析构函数以及实现数组的快速排序与链表的归并排序
- 怎样使用递归实现归并排序
- Java实现单向链表的归并排序
- 数组及链表的归并排序(C++实现)
- 单向链表的原地归并排序实现
- 归并排序--数组和链表的实现
- 链表的归并排序(经典实现:递归)
- 单向链表的原地归并排序实现
- C语言实现双向循环链表