C实现 LeetCode->Merge Two Sorted Lists (双指针大法)

本文详细介绍了如何使用双指针法在O(n log n)的时间复杂度下对单链表进行排序,同时保持常空间复杂度。通过将链表分为两部分并分别排序,最终合并排序后的链表,实现高效且内存占用小的排序过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


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

 *   将单链表 排序   O(n lg n)

  



//
//  SortList.c
//  Algorithms
//
//  Created by TTc on 15/6/18.
//  Copyright (c) 2015年 TTc. All rights reserved.
//

/**
 *  Sort a linked list in O(n log n) time using constant space complexity.
 *   将单链表 排序
*/
#include "SortList.h"


struct ListNode {
    int val;
    struct ListNode *next;
};

/**
 *   双指针大法,创建两个 指针(首尾指针 向中间扫描)
 */
struct ListNode*
sortList(struct ListNode* head) {
    if(!head)  return NULL;
    
    struct ListNode *start = head;
    struct ListNode *end = head;
    
    
    int length = 0;
    while(start)  {
        length++;
        start = start->next;
    }
    
    if(length == 1)   return head;
    
    int mid = length/2;    //计算结点个数,将链表分开。
    
    //将链表分开
    struct ListNode *tmpNode = NULL;
    
    for(int i = 0; i < mid && end != NULL; i++)  {
        if(i == mid - 1) tmpNode = end;
        end = end->next;
    }
    
    if(tmpNode != NULL) tmpNode->next = NULL;
    
    //分别对两段排序
    start = head;
    start = sortList(start);
    end   = sortList(end);
    
    struct ListNode *newhead = NULL;//新创建的list头节点
    struct ListNode *newtemp = NULL;//中间遍历接收变量
    
    while(start && end) {  //合并
        if(start->val <= end->val)  {
            if(!newhead)  newtemp = newhead = start;
            else{
                newtemp->next = start;
                newtemp = start;
            }
            start = start->next;
        }else{
            if(!newhead) newhead = newtemp = end;
            else{
                newtemp->next = end;
                newtemp = end;
            }
            end = end->next;
        }
    }
    if(start) newtemp->next = start;
    if(end) newtemp->next = end;
    return newhead;
}



``` class Solution { // 21. 合并两个有序链表 ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode dummy{}; // 用哨兵节点简化代码逻辑 auto cur = &dummy; // cur 指向新链表的末尾 while (list1 && list2) { if (list1->val < list2->val) { cur->next = list1; // 把 list1 加到新链表中 list1 = list1->next; } else { // 注:相等的情况加哪个节点都是可以的 cur->next = list2; // 把 list2 加到新链表中 list2 = list2->next; } cur = cur->next; } cur->next = list1 ? list1 : list2; // 拼接剩余链表 return dummy.next; } // 合并从 lists[i] 到 lists[j-1] 的链表 ListNode* mergeKLists(vector<ListNode*>& lists, int i, int j) { int m = j - i; if (m == 0) { return nullptr; // 注意输入的 lists 可能是空的 } if (m == 1) { return lists[i]; // 无需合并,直接返回 } auto left = mergeKLists(lists, i, i + m / 2); // 合并左半部分 auto right = mergeKLists(lists, i + m / 2, j); // 合并右半部分 return mergeTwoLists(left, right); // 最后把左半和右半合并 } public: ListNode* mergeKLists(vector<ListNode*>& lists) { return mergeKLists(lists, 0, lists.size()); } }; 作者:灵茶山艾府 链接:https://leetcode.cn/problems/merge-k-sorted-lists/solutions/2384305/liang-chong-fang-fa-zui-xiao-dui-fen-zhi-zbzx/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。```将这段代码实现的算法功能,用C语言重新实现,要求算法思路一致,仅仅换了一种语言先设计算法思路,然后再把输入输出头文件详细完整的写出来,给出必要的注释,要求输入输出可以在控制台控制自由输入输出
最新发布
04-06
<think>嗯,用户让我把一段C++的合并K个有序链表的代码转成C语言,还要保持算法思路一致。首先我需要理解原代码的逻辑,然后考虑如何在C语言实现同样的功能,同时处理输入输出。 原代码用了分治法和合并两个有序链表的方法。分治部分是通过递归将链表数组分成两半,分别合并后再合并结果。合并两个链表的部分用了哨兵节点简化操作。 首先,C语言没有类的概念,所以需要将类的方法转换成函数。比如,mergeTwoLists和mergeKLists这两个成员函数需要变成独立的函数。另外,C中的链表节点结构体需要自己定义,比如struct ListNode。 然后,输入输出部分,用户要求能在控制台自由输入输出。所以需要写函数来读取用户输入的链表,并转换成结构体链表。同时,输出部分需要将合并后的链表打印出来。 接下来,处理递归函数mergeKLists的参数。原代码中的vector<ListNode*>在C中可以用指针数组来表示,比如ListNode** lists,然后传递起始和结束索引。不过要注意内存管理,C中没有vector,所以数组的大小需要自己处理。 在合并两个链表时,C语言需要手动分配和释放内存吗?原代码中只是改变指针指向,并没有创建新节点,所以C语言实现也应该保持这一点,直接调整指针,而不是创建新节点。这样可以避免内存泄漏的问题。 输入处理部分,用户可能需要输入多个链表,每个链表由一组数字组成。比如,先输入链表个数,然后每个链表的元素。例如: 输入: 3 1 4 5 1 3 4 2 6 然后程序合并这三个链表,输出1 1 2 3 4 4 5 6。 为了实现这个,需要编写一个函数来读取一行数字,创建链表,并返回头节点。可以使用scanf读取输入,但要注意处理换行和结束符。 另外,C语言中没有布尔类型,判断指针是否为空可以用NULL来代替。递归的终止条件也需要正确翻译,比如当i >= j时返回NULL,当只有一个链表时直接返回它。 在内存管理方面,用户输入的链表需要在使用后释放,避免内存泄漏。不过题目可能不要求释放,但作为完整程序,应该考虑这一点。不过用户可能更关注算法实现,所以或许可以暂时忽略,但最好在注释中说明。 测试部分,需要确保输入输出正确。例如,输入多个链表,合并后的结果正确。对于空链表的情况,比如输入的链表数组为空,或者某个链表为空,程序应该正确处理。 现在,整合所有部分。首先定义结构体ListNode。然后实现mergeTwoLists函数,接着实现递归的mergeKLists函数。最后,编写main函数处理输入输出。 在C语言中,函数参数需要明确传递数组的大小,比如在main函数中,用户输入链表个数k,然后为每个链表输入元素,创建k个链表头,存储在数组中,然后调用mergeKLists处理。 需要注意的细节: 1. C语言中结构体指针的使用,比如创建哨兵节点时,需要使用malloc分配内存,并在最后释放。 2. 输入函数中,如何读取不定长的数字输入。可以用循环读取直到遇到换行或EOF。 3. 处理空指针的情况,例如当某个链表为空时,合并函数应该跳过它。 4. 递归的基准情况是否正确处理,比如当区间大小为0或1时返回正确的值。 可能遇到的问题: - 在输入处理时,如何判断一行输入的结束。可以使用scanf的返回值,或者逐字符读取。 - 链表创建时,节点的next指针需要正确初始化。 - 分治合并时,数组的划分是否正确,比如i + m/2可能导致整数除法的问题,但原代码已经处理了,所以C语言中同样适用。 现在,把这些思路转化为具体的代码步骤: 1. 定义结构体ListNode。 2. 实现mergeTwoLists函数,参数是两个ListNode指针,返回合并后的头节点。 3. 实现mergeKLists递归函数,参数是指针数组、起始和结束索引。 4. 编写输入函数,读取用户输入的数字并生成链表。 5. 编写输出函数,打印链表。 6. main函数中处理输入,调用合并函数,输出结果,并释放内存。 测试案例: 输入3个链表: 链表1:1 ->4 ->5 链表2:1 ->3 ->4 链表3:2 ->6 合并后应为1->1->2->3->4->4->5->6 或者空输入,应返回NULL。 另外,处理每个链表可能为空的情况,比如其中一个链表没有元素。 最后,确保代码注释清晰,结构符合要求,输入输出可控制。</think>我将分步骤为您实现C语言版本的合并K个有序链表算法,保持原有分治策略不变。 ### 算法思路分析 1. **问题分解**:采用分治法,将k个链表两两配对合并 2. **基础操作**:先实现合并两个有序链表的函数`mergeTwoLists` 3. **递归合并**:通过递归将链表数组不断二分,最终合并所有结果 4. **时间复杂度**:$O(N\log k)$,其中N是总节点数,k是链表个数 ### 完整C语言实现 ```c #include <stdio.h> #include <stdlib.h> // 链表节点结构体 struct ListNode { int val; struct ListNode *next; }; // 合并两个有序链表(核心函数) struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) { struct ListNode dummy = {0, NULL}; // 哨兵节点 struct ListNode *cur = &dummy; while (l1 && l2) { if (l1->val < l2->val) { cur->next = l1; l1 = l1->next; } else { cur->next = l2; l2 = l2->next; } cur = cur->next; } cur->next = l1 ? l1 : l2; // 连接剩余链表 return dummy.next; } // 递归合并链表数组(分治核心) struct ListNode* mergeKLists(struct ListNode** lists, int start, int end) { int size = end - start; if (size == 0) return NULL; // 空数组处理 if (size == 1) return lists[start]; // 递归终止条件 int mid = start + size / 2; struct ListNode* left = mergeKLists(lists, start, mid); struct ListNode* right = mergeKLists(lists, mid, end); return mergeTwoLists(left, right); } // 创建链表辅助函数 struct ListNode* createList(int* nums, int size) { struct ListNode dummy = {0, NULL}; struct ListNode *cur = &dummy; for (int i = 0; i < size; ++i) { struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode)); node->val = nums[i]; node->next = NULL; cur->next = node; cur = cur->next; } return dummy.next; } // 打印链表辅助函数 void printList(struct ListNode* head) { while (head) { printf("%d", head->val); if (head->next) printf("->"); head = head->next; } printf("\n"); } // 释放内存辅助函数 void freeList(struct ListNode* head) { while (head) { struct ListNode* temp = head; head = head->next; free(temp); } } int main() { int k; printf("请输入链表个数: "); scanf("%d", &k); // 创建链表数组 struct ListNode** lists = (struct ListNode**)malloc(k * sizeof(struct ListNode*)); for (int i = 0; i < k; ++i) { printf("请输入第%d个链表的数字(空格分隔,回车结束): ", i+1); int num, count = 0; int* buffer = (int*)malloc(100 * sizeof(int)); // 假设最多100个数字 while (scanf("%d", &num) == 1) { buffer[count++] = num; if (getchar() == '\n') break; // 检测回车结束输入 } lists[i] = createList(buffer, count); free(buffer); } // 执行合并操作 struct ListNode* result = mergeKLists(lists, 0, k); // 输出结果 printf("\n合并结果: "); printList(result); // 释放内存 for (int i = 0; i < k; ++i) freeList(lists[i]); free(lists); freeList(result); return 0; } ``` ### 关键功能说明 1. **输入控制**: - 先输入链表个数 - 逐个输入每个链表的数字序列 - 示例输入: ``` 3 1 4 5 1 3 4 2 6 ``` 2. **输出控制**: - 合并结果以`1->1->2->3->4->4->5->6`格式输出 - 自动处理空链表情况 3. **内存管理**: - 使用`malloc`动态分配内存 - 提供`freeList`函数进行内存回收 - 严格防止内存泄漏 ### 复杂度分析 1. **时间复杂度**:$O(N\log k)$ - 每次合并操作时间复杂度为$O(n)$ - 分治过程产生$\log k$层递归 2. **空间复杂度**:$O(\log k)$ - 递归调用栈的深度 该实现完整保留了原始算法的分治策略,通过递归实现高效合并,同时提供了完整的控制台交互功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值