跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C++实现双向循环链表排序 _ 归并排序算法与指针重定向【源码】

双向循环链表归并排序需先断开成两个线性双向链表:在中点mid与mid->next间切断,即设mid->next=nullptr且mid->next->prev=nullptr;合并时须同步更新next和prev指针,避免漏改导致链接断裂或环路;递归前必须确保split返回有效子段,防止无限递归。 为什么不能直接用
std::list
的
sort()
? 因为题目明确要求「手动实现双向循环链表」+「归并排序」+「指针重定向」,本质是考察对链表结构、归并拆分逻辑、以及节点指针(
next
/
prev
)在环状结构中如何安全切断与重连的掌握。标准库
std::list::sort()
虽然也是归并,但内部不暴露指针操作细节,无法满足训练目的。 如何安全地把双向循环链表「断开」成两个子链? 双向循环链表没有头尾,所有节点构成一个环。归并排序第一步是找中点并断开——但「断开」不是简单设
mid->next = nullptr
,那会破坏循环结构且丢失
prev
关系。必须同时调整四条指针: 设
left_tail = mid
,
right_head = mid->next
执行:
left_tail->next = right_head
→ 不对!这是维持循环,我们要的是「断开」 正确做法:
left_tail->next = right_head
和
right_head->prev = left_tail
先保留;再把环「剪开」:让
left_tail->next = nullptr
且
right_head->prev = nullptr
—— 但这样左右两段就不再是循环链表了 所以更稳妥的策略是:**不真正破坏循环性,而是在归并过程中临时视其为线性链处理**。即:找到中点后,将
mid->next->prev = nullptr
,
mid->next = nullptr
,把原环从
mid
和
mid->next
之间切开,得到两个线性双向链表(首尾不再相连),归并完再重新闭环 关键判断:是否要保持「循环」属性贯穿全程?题目未强制要求排序后仍为循环,但若要求,则最终需补上头尾连接:设新头为
new_head
,新尾为
new_tail
,则
new_head->prev = new_tail
,
new_tail->next = new_head
。
merge()
函数里最容易写错的指针操作有哪些? 双向链表归并比单向复杂在每步都要维护
next
和
prev
两对关系,且容易漏掉「前驱反向连接」。典型错误包括: 立即学习 “ C++免费学习笔记(深入) ”; C函数速查手册(CHM版) C函数速查手册(CHM版) 下载 只改了
cur->next = smaller
,忘了
smaller->prev = cur
合并后未更新新链表头节点的
prev
(应为
nullptr
或指向尾,取决于是否维持循环) 移动游标时写成
left = left->next
,但此时
left->next
可能已被重定向,应先缓存:
auto next_left = left->next
当某段链表耗尽,直接把另一段整个接上,却没修复该段首节点的
prev
指针(例如把
right
整体接到
tail
后,需
right->prev = tail
) 示例片段(非完整函数,仅示意关键点):
Node* merge(Node* left, Node* right) { Node dummy; Node* tail = &dummy; while (left && right) { if (left->val <= right->val) { tail->next = left; left->prev = tail; // ← 容易遗漏 tail = left; auto next = left->next; left = next; } else { tail->next = right; right->prev = tail; tail = right; auto next = right->next; right = next; } } // 处理剩余部分 Node* remaining = left ? left : right; if (remaining) { tail->next = remaining; remaining->prev = tail; // ← 同样容易漏 } return dummy.next; }
递归归并时,怎么避免栈溢出或无限递归? 双向循环链表无天然终点,若找中点逻辑出错(比如
slow
/
fast
指针没正确终止),会导致
split()
返回相同地址甚至空指针,进而使递归永不收敛。必须确保: 找中点用「快慢指针」,但需适配循环结构:初始
fast = slow->next
,循环条件为
fast != head && fast->next != head && fast->next->next != head
(假设以
head
为参考点);更可靠做法是先转成线性视图(计数长度
n
),再走
n/2
步 递归基必须严格:当链表长度 ≤ 1 时返回原节点。判断长度不能依赖
next == nullptr
,而应提前计算或传入长度参数 对于大链表(>10⁵ 节点),建议改用自底向上归并(迭代版),避免深递归。此时需用栈或数组暂存各段头指针,但指针重定向逻辑不变 实际编码中,最常被忽略的是:**没在每次
split
后显式断开前后连接,导致后续遍历时绕圈进入死循环**——这比栈溢出更隐蔽,调试时表现为程序卡死或访问非法内存。

相关文章