链表拷贝的一种算法

最近看到一道有关链表复制的算法题,解法很是奇妙。

题目:已知一链表,每个节点除了有一个指向下一节点的指针外,还有一随机指针指向链表中的任意节点(可能为空,也有可能为自身),请复制一个链表,要求节点的顺序以及节点上的随机指针指向的节点位置和原链表一致。

这个题目有个很巧妙的解法,可以达到O(n)的效率,其中心思想是把原始链表和复制链表先合并为一个有固定顺序的链表然后给复制链表中每个节点的随机指针复制,最后再打断链表恢复原样。

/**********节点信息*********/
typedef struct __Node {
	int nData;
	__Node* pNext;
    __Node* pRandom;
} Node;
Node* DuplicateList(Node* pSrcListHead)
{
	if (pSrcListHead == NULL)
		return NULL;
	
	Node* pNode = pSrcListHead;
        // 将新节点插入老的链表项中
	while (pNode != NULL)
	{
		Node* pNewNode = new Node;
		pNewNode->nData = pNode->nData;
		pNewNode->pNext = pNode->pNext;
		//pNewNode->pRandom = pNode->pRandom;
		pNode->pNext = pNewNode;
		pNode = pNewNode->pNext;
    }
	
	Node* pDestListHead = pSrcListHead->pNext;
    pNode = pSrcListHead;
    // 赋给节点随机信息
    while (pNode != NULL)
    {
        pNode->pNext->pRandom = pNode->pRandom->pNext;
        pNode = pNode->pNext->pNext;
	}
    pNode = pSrcListHead;
    Node* pNode2 = pNode->pNext;
    // 将新产生的链表解链
    while (pNode != NULL)
    {
		pNode->pNext = pNode2->pNext;
		pNode = pNode->pNext;
		if (pNode)
			pNode2->pNext = pNode->pNext;
		else
			pNode2->pNext = NULL;
        pNode2 = pNode2->pNext;
    }
    return pDestListHead;
}


<think>好的,我现在需要帮用户找到力扣257题的C语言另一种实现方法。用户之前已经得到了DFS递归的解决方案,现在想要不同的算法,比如非递归的方法。首先,我得回忆一下二叉树遍历的其他方法,常见的非递归方式有使用栈的迭代法,或者广度优先搜索(BFS)。DFS通常用栈,而BFS用队列。题目是收集所有路径,所以可能需要调整遍历方式的同时记录路径。 先看看用户提供的引用内容,里面提到了拓扑排序、反转链表、双指针等,但这些可能不太直接相关。不过引用4提到BFS和DFS都可以用于拓扑排序,或许BFS可以应用在这里?因为BFS通常用于层序遍历,但如何记录路径呢?可能需要每个节点保存从根到该节点的路径。 用户之前的DFS递归实现使用了回溯,而迭代方法可能需要显式地维护路径信息。例如,使用栈来保存节点和对应的路径字符串。每次弹出节点时,如果它是叶子节点,就将路径添加到结果中;否则,将其子节点和更新后的路径压入栈中。这样就能模拟递归的过程,但不需要递归调用。 另外,引用5提到双指针,不过接雨水的问题可能不太相关。但动态规划或栈的方法可能有启发。比如,栈可以用来保存遍历过程中的状态,这可能适用于迭代DFS。 现在,考虑用BFS实现的话,需要队列来保存节点和路径。每次处理一个节点时,检查是否为叶子节点,如果是,则记录路径;否则,将子节点和当前路径加上子节点值入队。这种方法可以保证层级遍历,同时构建路径。 但用户想要的是另一种算法,所以BFS可能是一个好的选择,因为之前是DFS递归,现在用BFS迭代就是不同的方法。需要确保路径的正确记录,每个节点在队列中保存自己的路径,避免路径被修改影响其他分支。 接下来,思考如何用C语言实现BFS。由于C没有内置队列,需要手动实现队列结构。每个队列元素需要包含当前节点和对应的路径字符串。路径的拼接需要注意内存管理,每次入队时复制新的字符串,避免指针被后续操作覆盖。 可能的步骤: 1. 创建队列结构,存储节点和路径。 2. 初始化队列,将根节点和初始路径(根的值)加入队列。 3. 循环处理队列中的每个元素: a. 弹出队首元素。 b. 如果是叶子节点,将路径加入结果数组。 c. 否则,将左右子节点加入队列,并更新路径为当前路径+"->子节点值"。 4. 注意内存分配和释放,避免泄漏。 需要考虑路径字符串的动态分配,每次拼接新路径时使用malloc,并在处理后适当释放。C语言中字符串操作比较繁琐,需要小心缓冲区溢出和内存泄漏。 此外,结果数组需要动态扩展,使用realloc来调整大小,每个路径字符串也需要单独分配内存。 可能会遇到的问题:队列的大小管理,路径拼接的效率,内存管理是否正确。例如,在BFS中,队列的大小可能随着树的深度指数级增长,但题目中二叉树可能较大,需要合理处理。 最后,编写代码时测试用例,比如空树、单节点树、左右子树都存在的情况,确保覆盖所有情况。 总结:使用BFS迭代方法,队列保存节点和路径,逐个处理,记录叶子节点的路径。这种方法与之前的DFS递归不同,满足用户需求。</think>### LeetCode 257题C语言实现:二叉树的所有路径(BFS迭代法) 以下是使用**广度优先搜索(BFS)迭代实现**的C语言解决方案,采用显式队列管理节点路径: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; // 队列节点结构:存储树节点和对应路径 typedef struct QueueNode { struct TreeNode* node; char* path; struct QueueNode* next; } QueueNode; // 队列结构 typedef struct { QueueNode* front; QueueNode* rear; } Queue; // 初始化队列 Queue* createQueue() { Queue* q = (Queue*)malloc(sizeof(Queue)); q->front = q->rear = NULL; return q; } // 入队操作(携带路径) void enqueue(Queue* q, struct TreeNode* node, const char* currentPath) { QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode)); newNode->node = node; // 动态分配路径内存并复制内容 newNode->path = (char*)malloc(strlen(currentPath) + 20); // 预分配空间 sprintf(newNode->path, "%s%s%d", currentPath, (strlen(currentPath) > 0) ? "->" : "", node->val); newNode->next = NULL; if (!q->front) { q->front = q->rear = newNode; } else { q->rear->next = newNode; q->rear = newNode; } } // 出队操作 QueueNode* dequeue(Queue* q) { if (!q->front) return NULL; QueueNode* temp = q->front; q->front = q->front->next; if (!q->front) q->rear = NULL; return temp; } char** binaryTreePaths(struct TreeNode* root, int* returnSize) { char** result = NULL; *returnSize = 0; if (!root) return result; Queue* q = createQueue(); enqueue(q, root, ""); // 初始路径为空 while (q->front) { QueueNode* curr = dequeue(q); struct TreeNode* node = curr->node; char* path = curr->path; // 叶子节点处理 if (!node->left && !node->right) { result = realloc(result, (*returnSize + 1) * sizeof(char*)); result[*returnSize] = path; // 直接复用已分配内存 (*returnSize)++; } else { // 非叶子节点处理子节点 if (node->left) enqueue(q, node->left, path); if (node->right) enqueue(q, node->right, path); } free(curr); // 释放队列节点(注意:不释放path,已被结果数组引用) } free(q); return result; } ``` ### 算法解析 1. **队列设计**:通过自定义队列节点`QueueNode`,同时保存树节点和**完整路径字符串**,避免路径回溯操作[^4]。 2. **路径动态生成**:每次入队时根据父节点路径生成新路径,使用`sprintf`直接拼接字符串,减少内存碎片。 3. **内存管理优化**: - 路径字符串内存预分配`strlen(currentPath) + 20`,避免频繁`realloc` - 结果数组直接复用队列节点中的路径字符串指针,减少拷贝开销 4. **时间复杂度**:$O(N)$,每个节点入队出队各一次,路径字符串操作总复杂度为$O(N)$[^1]。 ### 对比DFS递归方案 | 特性 | DFS递归方案 | BFS迭代方案 | |--------------------|--------------------------|--------------------------| | **路径记录方式** | 回溯法修改共享缓冲区 | 独立路径字符串预生成 | | **内存占用** | 栈空间$O(H)$,H为树高度 | 队列空间$O(N)$ | | **适用场景** | 深度优先遍历 | 广度优先遍历 | | **代码复杂度** | 需手动回溯 | 显式队列管理 | --相关问题-- 1. 如何验证BFS实现的内存安全性? 2. 迭代法如何处理超长路径导致的缓冲区溢出? 3. 两种算法在不同树结构下的性能差异? 4. 如何用双向链表优化队列操作?[^2] [^1]: 基于LeetCode对树遍历时间复杂度的通用分析 [^2]: 参考代码随想录的链表操作规范 [^4]: 拓扑排序中的队列管理思想迁移应用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值