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

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

本题目来源于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月前
|
人工智能 数据可视化 API
零成本解锁AI算力!OpenClaw阿里云及本地部署与GLM-4.7-Flash免费调用实战保姆级教程
在使用OpenClaw(别名“大龙虾”)时,很多用户会遇到Token消耗过快的问题。2026年,智谱AI开放平台推出的GLM-4.7-Flash模型提供完全免费调用服务,该模型作为30B级SOTA模型,强化了编码能力、长程任务规划与工具协同,上下文窗口达200K,最大输出Tokens为128K,适配OpenClaw的复杂智能体任务执行需求。
6684 0
|
存储 SQL 大数据
一篇文章搞懂数据仓库:三种事实表(设计原则,设计方法、对比)
一篇文章搞懂数据仓库:三种事实表(设计原则,设计方法、对比)
一篇文章搞懂数据仓库:三种事实表(设计原则,设计方法、对比)
|
存储 安全 Java
Spring Security 入门与详解
Spring Security 是 Spring 框架中的核心安全模块,提供认证、授权及防护功能。本文详解其核心概念,包括认证(Authentication)、授权(Authorization)和过滤器链(Security Filter Chain)。同时,通过代码示例介绍基本配置,如 PasswordEncoder、UserDetailsService 和自定义登录页面等。最后总结常见问题与解决方法,助你快速掌握 Spring Security 的使用与优化。
2882 0
|
网络协议 Linux Go
用 Go 基于 epoll 实现一个最小化的IO库
Go 语言社区中存在多个异步网络框架,如 evio、nbio、gnet 和 netpoll 等。这些框架旨在解决标准库 netpoll 的低效问题,如一个连接占用一个 goroutine 导致的资源浪费。easyio 是一个最小化的 IO 框架,核心代码不超过 500 行,仅实现 Linux 下的 epoll 和 TCP 协议。它通过 Worker Pool、Buffer 等优化提高了性能,并提供了简单的事件处理机制。
254 0
|
算法 数据处理 C语言
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
【数据结构与算法】快速排序(详解:快排的Hoare原版,挖坑法和双指针法|避免快排最坏时间复杂度的两种解决方案|小区间优化|非递归的快排)
|
存储 C语言
C语言中的动态数组技术详解
C语言中的动态数组技术详解
834 0
|
域名解析 Web App开发 存储
DNS域名解析详解
DNS(Domain Name System,域名系统)是互联网中一种用于将域名解析为IP地址的分布式命名系统。它负责将人类可读的域名(如:www.example.com)转换为计算机可识别的IP地址(如:192.168.1.1),以便浏览器能够连接到相应的服务器并获取网页内容。本文主要简单说说关于DNS域名解析的一些常用知识,包括概念,域名的层级结构,域名服务器,DNS的查询方式等等。
3341 2
|
算法 程序员
【算法训练-链表 七】【链表排序】:链表排序、链表的奇偶重排、重排链表
【算法训练-链表 七】【链表排序】:链表排序、链表的奇偶重排、重排链表
456 1
|
算法 Java
数据结构-构造哈夫曼树【详解+代码+图示】一文解惑!
数据结构-构造哈夫曼树【详解+代码+图示】一文解惑!
8321 1