程序员必知:单链表排序(快速排序、归并排序)

简介: 程序员必知:单链表排序(快速排序、归并排序)

本题目来源于LeetCode,具体如下:

Sort a linked list in O(n log n) time using constant space complexity.

题目要求复杂度O(nlogn),因此我们很自然考虑使用快速排序或者归并排序,但是后来经过实践证明,使用快速排序总是AC超时,归并排序则可以正确AC。

分析一下原因,个人认为是与测试数据有关,因为快速排序不能保证算法复杂度一定是O(nlogn),当数据比较集中时,即使做随机选取key值,算法的复杂度也非常接近O(N^2),因此会出现超时,所以考虑使用归并排序。

下面是采用归并排序的思路已经AC代码:

主要考察3个知识点,

知识点1:归并排序的整体思想

知识点2:找到一个链表的中间节点的方法

知识点3:合并两个已排好序的链表为一个新的有序链表

归并排序的基本思想是:找到链表的middle节点,然后递归对前半部分和后半部分分别进行归并排序,最后对两个以排好序的链表进行Merge。

【cpp】 view plain copy

#include

#include

#include

#include

#include

#include

using namespace std;

struct ListNode {

int val;

ListNode next;

ListNode(int x) : val(x), next(NULL) {}

};

class Solution {

public:

ListNode mergeLists(ListNode a, ListNode b) //合并两个已经排序的链表

{

if (a == NULL) return b ;

if (b == NULL) return a ;

ListNode ret = NULL ;

ListNode tail = NULL ;

ret = new ListNode(-1) ;

tail = ret ;

while (a b)

if (a->val val)

{

tail->next = a ;

tail = tail->next ;

a = a->next ;

}

else

{

tail->next = b ;

tail = tail->next ;

b = b->next ;

}

if (a)

tail->next = a ;

if (b)

tail->next = b ;

ListNode del = ret ;

ret = ret->next ;

delete del ;

return ret ;

}

ListNode getMid(ListNode head) //得到中间节点

{

if (!head) return NULL ;

if (!head->next) return head ;

ListNode slow = head ;

ListNode fast = head->next ;

while (fast fast->next)

{

slow = slow->next ;

fast = fast->next->next ;

}

return slow ;

}

ListNode sortList(ListNode head) { //合并排序

if (!head) //代码效果参考:http://www.zidongmutanji.com/bxxx/437098.html

return NULL ;

if (!head->next) return head ;

ListNode mid = getMid(head) ;

ListNode nextPart = NULL ;

if (mid)

{

nextPart = mid->next ;

mid->next = NULL ;

}

return mergeLists(

sortList(head) ,

sortList(nextPart)

) ;

}

};

void insertBack(ListNode head, ListNode tail, ListNode n) //从尾部插入

{

if (n)

{

if (head == NULL)

{

head = n ;

tail = n ;

}

else

{

(tail)->next = n ;

tail = n ;

}

}

}

int main(int argc, char** argv)

{

ifstream in("data.txt") ;

ListNode head = NULL ;

ListNode //代码效果参考:http://www.zidongmutanji.com/zsjx/395573.html

tail = NULL ;

int val ;

Solution s ;

while(in ] val)

{

ListNodetmp = new ListNode(val) ;

insertBack(head, tail, tmp) ;

}

head = s.sortList(head) ;

while(head)

{

cout [ head->val [ " " ;

head = head->next ;

}

cout [ endl ;

return 0 ;

}

下面再说一下自己AC超时的代码吧,

这里我尝试了两种实现方案:

第一种是:

在找划分点的过程中,维护连个链表Left 和Right 所有不大于key的元素都链到Left上,大于key的链到Right上,最后再将Left, key , Right三部分连接起来。

代码如下:

【cpp】 view plain copy

#include

#include

#include

#include

#include

#include

using namespace std;

struct ListNode {

int val;

ListNode next;

ListNode(int x) : val(x), next(NULL) {}

};

class Solution {

public:

inline void insertBack(ListNode head, ListNode tail, //代码效果参考:http://www.zidongmutanji.com/zsjx/213875.html

ListNode n) //从尾部插入

{

if (n)

{

if (head == NULL)

{

head = n ;

tail = n ;

}

else

{

(tail)->next = n ;

tail = n ;

}

}

}

ListNode sortList(ListNode head) {

if (!head) return NULL ;

if (head->next == NULL) return head ;

//划分

ListNode tmpNode = head ;

head = head->next ;

ListNode sleft = NULL , eleft = NULL ;

ListNode sright = NULL , eright = NULL ;

while (head)

{

ListNode insNode = head ;

head = head->next ;

insNode->next = NULL ;

if (insNode->val > tmpNode->val)

insertBack(sright, eright, insNode) ;

else

insertBack(sleft, eleft, insNode) ;

}

//递归调用

sleft = sortList(sleft) ;

sright = sortList(sright) ;

//下面三句话第一次没有加上,调试了一下午才找到原因

eleft = sleft ;

if (eleft)

{

while(eleft->next)

eleft = eleft->next ;

}

//拼接起来

if (eleft)

{

head = sleft ;

eleft->next = tmpNode ;

}

else

head = tmpNode ;

tmpNode->next = sright ; //连接起来

//返回结果

return head ;

}

};

int main(int argc, char** argv)

{

ifstream in("data.txt") ;

ListNode head = NULL ;

ListNode tail = NULL ;

int val ;

Solution s ;

while(in ] val)

{

ListNodetmp = new ListNode(val) ;

s.insertBack(head, tail, tmp) ;

}

head = s.sortList(head) ;

while(head)

{

cout [ head->val [ " " ;

head = head->next ;

}

cout [ endl ;

return 0 ;

}

第二种方案:使用快排的另一种思路来解答。我们只需要两个指针p和q,这两个指针均往next方向移动,移动的过程中保持p之前的key都小于选定的key,p和q之间的key都大于选定的key,那么当q走到末尾的时候便完成了一次划分点的寻找。如下图所示:

实现代码如下:

【cpp】 view plain copy

#include

#include

#include

#include

#include

#include

using namespace std;

struct ListNode {

int val;

ListNode next;

ListNode(int x) : val(x), next(NULL) {}

};

class Solution {

public:

ListNode getPartation(ListNode start, ListNode end)

{

if (start == end) return start ;

ListNode p1 = start ;

ListNode p2 = p1->next ;

int key = start->val ;

while(p2 != end)

{

if (p2->val < key)

{

p1 = p1->next ;

swap(p1->val, p2->val) ; //找到一个比key小的数字,与p1到p2间的数交换,

} //这之间的数都大于等于key

p2 = p2->next ;

}

swap(start->val, p1->val) ; //找到划分位置

return p1 ;

} ;

void QuickSort(ListNode start, ListNode end)

{

if (start != end)

{

ListNode pt = getPartation(start, end) ;

QuickSort(start, pt) ;

QuickSort(pt->next, end) ;

}

}

ListNode sortList(ListNode head) {

QuickSort(head, NULL) ;

return head ;

}

};

void insertBack(ListNode head, ListNode tail, ListNode n) //从尾部插入

{

if (n)

{

if (head == NULL)

{

head = n ;

*tail = n ;

}

else

{

相关文章
|
3月前
|
存储 搜索推荐 算法
排序(堆排序、快速排序、归并排序)-->深度剖析(二)
排序(堆排序、快速排序、归并排序)-->深度剖析(二)
48 6
|
5月前
|
算法 程序员 C++
程序员必知:单链表排序(快速排序、归并排序)
程序员必知:单链表排序(快速排序、归并排序)
26 0
|
5月前
|
搜索推荐 C语言
【C/排序算法】:快速排序和归并排序的非递归实现
【C/排序算法】:快速排序和归并排序的非递归实现
35 0
|
6月前
|
算法 索引
【数据结构与算法】:非递归实现快速排序、归并排序
上篇文章我们详细讲解了递归版本的快速排序,本篇我们来探究非递归实现快速排序和归并排序
【数据结构与算法】:非递归实现快速排序、归并排序
|
6月前
|
存储 算法 搜索推荐
【数据结构与算法】:选择排序与快速排序
欢迎来到排序的第二个部分:选择排序与快速排序!
【数据结构与算法】:选择排序与快速排序
|
6月前
|
存储 算法 搜索推荐
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
【数据结构与算法】归并排序(详解:递归与非递归的归并排序 | 赠:冒泡排序和选择排序)
|
算法
【数据结构与算法】快速排序的非递归实现方法
【数据结构与算法】快速排序的非递归实现方法
152 0
|
存储 搜索推荐 测试技术
数据结构__<八大排序> __插入排序 |希尔排序 |选择排序 |堆排序 |快速排序 |归并排序(C语言实现)
数据结构__<八大排序> __插入排序 |希尔排序 |选择排序 |堆排序 |快速排序 |归并排序(C语言实现)
272 0
|
存储 搜索推荐 编译器
基础排序算法-快排的非递归和归并的具体实现
基础排序算法-快排的非递归和归并的具体实现
95 0
|
算法
每日一题——单链表排序(归并排序)
每日一题——单链表排序(归并排序)