复制数据结构 - 单链表及图

单链表的复制 #include using namespace std; typedef struct Node { double val; Node *next; }node; node* mycreat(double n) { node *head=NULL,*p=NULL,*sec=NULL; head=(node*)malloc(sizeof(node)); p=hea 阅读详情

这次依然是两道算法题,复制单链表,及复制图。先后对其加以分析,最后会给出一些粗浅总结。

题目一,复制一个单链表(SLL - single linked list), 其节点除了一个基本的后向指针(next), 还有一个指向链表中任意一个节点的随机指针(random)。

struct ranSLLNode{
    char* cValue;
    ranSLLNode* pNext;
    ranSLLNode* pRandom;
    ranSLLNode(): pNext(0), pRandom(0){
        cValue = new char[CSIZE];
        memset(cValue, 0, CSIZE);
    }
    ~ranSLLNode(){
        delete cValue;
        cValue=0;
        pNext=0;
        pRandom=0;
    }
};

如果是普通单链表,很简单:遍历原始链表,同时逐个创建节点拷贝,总共一次遍历;现在每个节点多了一个随机指针,假设对于原节点pnode, 其复制拷贝节点cpnode, 问题在于如果pnode中的random指向的是之后的节点px, 则由于px的拷贝节点尚未创建,cpnode的random目前无法赋值。

解法一:利用map, 存放每个原节点和其复制节点的关联。这样,当遍历走到某个节点时,如果随机指针指向的节点还未创建,那就直接创建它!反正在map中,每个节点和其拷贝只会存在一份。恩,是个办法。

ranSLLNode* clone_01(ranSLLNode* phead){
    map<ranSLLNode*, ranSLLNode*> mnodes;
    ranSLLNode *curr = phead;
    while(1){
        if(mnodes.find(curr) == mnodes.end()){
            ranSLLNode *p = new ranSLLNode;
            p->cValue = curr->cValue;
            mnodes[curr] = p;
        }
        
       if(mnodes.find(curr->pRandom) == mnodes.end()){
            ranSLLNode *p = new ranSLLNode;
            p->cValue = curr->pRandom->cValue;
            mnodes[curr->pRandom] = p;
            mnodes[curr]->pRandom = p;
        }else{
            mnodes[curr]->pRandom = mnodes[curr->pRandom];
        }
        
        if(curr->pNext != 0){
            if(mnodes.find(curr->pNext) == mnodes.end()){
                ranSLLNode *p = new ranSLLNode();
                p->cValue = curr->pNext->cValue;
                mnodes[curr->pNext] = p;
                mnodes[curr]->pNext = p;
            }else{
                mnodes[curr]->pNext = mnodes[curr->pNext];
            }
        }else{ //reach tail, exit
            mnodes[curr]->pNext = 0;
            break;
        }
    }
    return mnodes[phead];
}

遍历到每一个节点时,分别检查本节点、next指向节点、random指向节点等三个节点的拷贝,如果尚无,创建它,保证在这一步结束前,本节点和其完整拷贝在map里有存在。

空间上,使用一个map;时间上,总计只有一次遍历链表。

另:如果要求不使用额外空间map,怎么办?

本着时间和空间互换的思想,显然我们只能设法利用原单链表,在本题中,可选项是两个指针:random指针无序,没法用;next指针有序并支持遍历,可以使用。

解法二:我们在原链表中每一个原始节点后,插入其相应的拷贝节点,这时,对random指针不赋值;然后重新遍历整个链表,将每个新拷贝节点的random指针,赋值为其前任节点(也就是其原节点)random指针指向节点的新拷贝节点;最后,解开原节点和新拷贝节点,得到原单链表和复制后的拷贝链表。

ranSLLNode* clone_02(ranSLLNode* srcHeader){
    ranSLLNode* curr = srcHeader;
    while(curr != 0){ //create new node one after each source node
        ranSLLNode* nNode = new ranSLLNode();
        strcpy(nNode->cValue, curr->cValue);
        nNode->cValue[strlen(curr->cValue)]='\0';
        nNode->pRandom = curr->pRandom;
        nNode->pNext = curr->pNext;
        curr->pNext = nNode;
        curr = nNode->pNext;
        nNode = 0;
    }

    curr=srcHeader;
    while(curr != 0){ //set pRandom of new node be new node
        curr->pNext->pRandom = curr->pRandom->pNext;
        curr = curr->pNext->pNext;
    }
    
    curr = srcHeader;
    ranSLLNode* next = curr->pNext;
    ranSLLNode* nHeader = next;
    while(next != 0){ //unplug new node and source node
        curr->pNext = next->pNext;
        curr = next;
        next = curr->pNext;
    }

    curr=0;
    next = 0;
    return nHeader;
}
该解法空间上没有使用额外结构,时间上总共遍历三次链表。

-------------------------------------------------我是分隔线-----------------------------------------------------

题目二,复制一个图(graph),图中的顶点由以下结构表示,输入为一个顶点,输出其相应的拷贝顶点。(来自leetcode)

struct Node{
    int val;
    vector<Node*> neighbors;
};
解法一:该解法来自leetcode上原题的作者,这里作个简单转载。

首先提示读者注意这个图是有向还是无向?根据该图的数据结构,只有顶点的数据结构,每条边由一个顶点P和其一个相邻顶点中Q表示,Q会出现在P的neighbors中,这符合有向图的特征。如果是无向图,每条边的两个顶点应该是地位平等的,但我们这道题中,顶点相邻关系显然仅仅取决于某个顶点的neighbours。所以,这是个有向图。

其次,直观的方法是对所有节点作有序遍历,那么选择队列queue, 使用广度优先BFS进行遍历。需要注意的是,如果图中存在回路,我们应力求避免回路中的某个节点重复进入队列,即程序出现死循环!!对策是使用一个map存放原节点和相应拷贝节点的对应,保证每个原节点只被复制一次。

Node* clonegraph_02(Node *graph){
    if(!graph)
      return NULL;
    map<Node*, Node*> gmap; //[initial, copy]
    queue<Node*> q;
    q.push(graph);

    Node *graphCopy = new Node;
    graphCopy->val = graph->val;
    gmap[graph] = graphCopy;
    
    while(!q.empty()){
        Node *node = q.front();
        q.pop();
        int n = node->neighbors.size();
        for(int i=0;i<n;++i){
            Node *neighbor = node->neighbors[i];
            if(gmap.find(neighbor) == gmap.end()){ //no copy exist in map
                Node *p = new Node;
                p->val = neighbor->val;
                gmap[node]->neighbors.push_back(p);
                gmap[neighbor] = p;
            }else{
                gmap[node]->neighbors.push_back(gmap[neighbor]);
            }
        }
    }
    return graphCopy;
}

与题目一类似,如果不用额外空间map,怎么做?

解法二:这个解法是我受题目一的解法二启发而得。既然不能使用map记录原顶点和拷贝顶点的对应,那么只能在原顶点上做文章。

首先,遍历每个顶点V[i],将V[i]的拷贝顶点存入V[i]的neighbors列尾;然后,再次遍历每个顶点V[i], 为它的拷贝顶点的neighbors都填充上相应的拷贝顶点。

在代码实现中,由于要作遍历(BFS),queue的使用不可避免。另外同样需要避免在回路存在时,顶点重复进入队列的问题,这里额外使用一个set来处理。以下是一份实现代码:

    Node* clonegraph_01(Node* pnode){  
        set<Node*> snodes;  
        queue<Node*> qnodes;  
        qnodes.push(pnode);  
        snodes.insert(pnode);  
        while(!qnodes.empty()){ //1st iteration to append cloned A' to neighbors of A  
            Node* curr = qnodes.front();  
            qnodes.pop();  
            vector<Node*>::const_iterator iter = curr->neighbors.begin();  
            for(;iter != curr->neighbors.end();++iter){  
                if(snodes.find(*iter) == snodes.end()){ //Node* not pushed yet  
                    qnodes.push(*iter);  
                    snodes.insert(*iter);  
                }  
            }  
            Node* clone = new Node;  
            clone->val = curr->val;  
            curr->neighbors.push_back(clone); //append clone at tail  
        }  
        snodes.clear();  
        qnodes.push(pnode);  
        snodes.insert(pnode);  
        Node* npnode = pnode->neighbors.back(); //pointer to return  
        while(!qnodes.empty()){  
            Node* curr = qnodes.front();  
            qnodes.pop();  
            Node* clone = curr->neighbors.back();  
            vector<Node*>::iterator iter = curr->neighbors.begin();  
            for(;iter != curr->neighbors.end()-1;++iter){  
                if(snodes.find(*iter) == snodes.end()){  
                    qnodes.push(*iter);  
                    snodes.insert(*iter);  
                }  
                clone->neighbors.push_back((*iter)->neighbors.back()); //push clone to neighbors of clone   
            }  
            curr->neighbors.erase(iter); //now iter points to A' of A  
        }  
        snodes.clear();  
        return npnode;  
    }  

相对于解法一,该解法空间上少使用一个map,不过多了一个set,扯平了;时间上多了一次全图遍历(BFS),所以似乎没什么优势。。。  
      
总结:
      

1. 对于这类复制容器的问题,时间复杂度一般均为O(n), 提升余地表现在遍历的次数; 空间上的优化一般包括关联容器的使用(map)与否。  
      
2. 算法的基本哲学“空间和时间可以互换”在这里的两种不同思路中得到了很好的展示。

3. 对于图的处理,一定要小心回路的情况,因为如果存在回路,普通的遍历会出现死循环!

TCN+iTransformer时间序列预测实战:从风电功率预测到源码解析(附完整代码) 本文深入探讨了TCN与iTransformer融合模型在时间序列预测,特别是风电功率预测中的应用。通过解析iTransformer‘倒置’视角的创新架构与TCN强大的局部时序捕捉能力,文章提供了完整的融合方案设计、源码实现及实战调优经验,旨在构建更强大的下一代预测引擎。 阅读详情

相关推荐

Cadence 17.4 PSpice仿真555定时器输出方波

这里利用555定时器组成多谐振荡器产生1Hz的方波并用PSpice仿真其输出的方波波形。555定时器是一种广泛应用的中规模集成电路。根据其内部组成的不同,可分为双极型(555B)和CMOS型(555C)两类。两种类型的定时器各有所长。双极型定时器具有较大的驱动能力,其输出电流可达200mA,可直接驱动发光二级管、扬声器、继电器等负载。而CMOS型定时器的输入阻抗高、功耗低。

tj_nonstoper的博客 1万+

单链表(Java)

java单链表的固定套路

m0_55875421的博客 853

【交易系统系列27】带你看懂订单的“奇幻漂流”

作为一个在交易所后台“厨房”里摸爬滚打了十多年的老兵,我将用一杯手冲咖啡的时间,带你亲历一次完整的订单“奇幻漂流”。本文将用一个“奶爸”的生活视角,硬核拆解**撮合(Matching)**、**清算(Clearing)**、**结算(Settlement)**和**出入金**这四大核心环节。这不仅是一次对**交易系统优化**和**撮合引擎架构**的深度剖析,更是一场关于传统金融与数字金融两种不同“上菜”哲学的真实故事。

weixin_43988453的博客 406

【数据结构】:单链表OJ(随机链表的复制)

给你一个长度为n的链表,每个节点包含一个额外增加的随机指针random,该指针可以指向链表中的任何节点或空节点。构造这个链表的。深拷贝应该正好由n个节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的next指针和random指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。。例如,如果原链表中有X和Y两个节点,其中。那么在复制链表中对应的两个节点x和y,同样有。返回复制链表的头节点。用一个由n个节点组成的链表来表示输入/输出中的链表。

2201_75755162的博客 168

多智能体记忆架构设计:从分层模型到一致性实践

多智能体记忆架构本质上是一个有状态、分布式、支持遗忘的知识系统。从分层模型入手,结合RBAC隔离、版本向量一致性以及混合存储,可以构建出既灵活又健壮的生产级方案。可微分记忆:让记忆的读写操作嵌入端到端训练。基于大模型的记忆压缩:用LLM自动生成情景摘要,取代规则式遗忘。联邦记忆:不同组织之间的多智能体系统在不共享原始数据的情况下协同记忆。希望本文能为你的多智能体项目提供一块坚实的基石。欢迎在评论区分享你在实践中遇到的记忆架构难题。

Ven%的博客 169

开源模型应用落地-qwen模型小试-Qwen3-8B-快速体验(一)

阿里云推出国内首个集成“快思考”与“慢思考”能力的 Qwen3-8B 大语言模型,以80亿参数和 128K 上下文支持,重塑AI应用边界并助力开发者高效部署

以微薄之力,予他人些许温暖. 2万+

Leetcode 133. 克隆图 将图数据结构深拷贝一份

使用HashMap当作新的点和老的点之间的映射,先将点复制一份,再根据hashMap来连接边 class Solution { public: unordered_map<Node*,Node*> hashmap; Node* cloneGraph(Node* node) { if(node==NULL) return node; dfs(node); // 先复制所有的点 ...

wwxy1995的博客 285

EtherCAT主站SOEM -- 1 -- SOEM之ethercattype.h文件分析

“ethercattype.h” 文件用于支持 EtherCAT 网络通信,提供了各种类型定义和宏,以便开发者更轻松地开发 EtherCAT 相关应用程序。这些定义包括数据类型、命令类型、状态类型和错误类型,以支持 EtherCAT 通信。根据具体应用需求,还可以调整一些超时和帧长度设置。这些定义用于处理函数调用的结果,如 EC_NOFRAME(没有帧返回)、EC_ERROR(一般错误)、EC_TIMEOUT(请求超时)等。这些定义用于处理 EtherCAT 命令,例如 EC_CMD_APRD(自动递增读取

常驻客栈的博客--嵌入式EtherCAT 2714

单链表之复杂链表的复制(C语言版)

复杂链表的复制,综合性较强,学习其中的思路是最重要的。三步走思路,时间复杂度O(n)。

m0_54469145的博客 1278

搞定ESD(五):静电放电问题分析与调试

前面深入分析了静电放电的干扰机理、耦合路径,从现在开始将进入下一个大家非常关注的环节:静电放电问题分析与调试,掌握静电放电问题的分析方法,快速锁定敏感设备或者耦合路径,可以达到事半功倍的效果。

记得诚 2793

Backtrader官方中文文档:第四章Concepts基本概念

官方Concepts章节:https://backtrader.com/docu/concepts/

汇渊科技 2601

C/C++ 数据结构 单链表

#include<stdio.h> #define flag '#' typedef int datatype; typedef struct node { datatype data; struct node *next; }LNODE,*Linklist; //定义单链表的结构体 Linklist Creath_Linklist...

Forward的博客 393

损失函数大汇总(八)(IoU Loss附公式推导与代码)

本文详细介绍了IoU Loss及其在图像分割中的应用。IoU(交并比)是衡量预测区域与真实区域重叠程度的指标,IoU Loss定义为1-IoU,直接优化区域重叠质量。文章推导了IoU的数学表达式和梯度计算,分析了其函数特性:全局耦合梯度、对漏检误检的双重惩罚、适合类别不平衡任务。与Dice Loss相比,IoU更强调覆盖质量而非相似度。实际应用中IoU Loss常与BCE组合使用,前者保证训练稳定性,后者优化整体区域质量。文章还提供了IoU Loss的代码实现,适用于二值分割、小目标提取等场景,特别当任务评

悦学共鸣,温柔以待,汇聚光芒,共同成长 413

数据结构-代码C++——基础练习/考研

【代码】数据结构-代码C++——基础练习/考研。

guigui_xxx的博客 174

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

链表数据结构全解析:从核心原理到实战应用与避坑指南

数据结构是计算机科学中组织与管理数据的核心基础,其设计直接决定了数据操作的效率与复杂度。链表作为一种基础且重要的线性数据结构,其核心原理在于通过节点与指针(或引用)的链接,实现数据在物理内存上的非连续存储。这种结构赋予了链表在频繁进行插入与删除操作时近乎常数时间复杂度(O(1))的技术优势,尤其适用于数据集合动态变化剧烈的场景,例如实现LRU缓存淘汰算法、操作系统内存管理中的空闲链表,以及作为栈、队列等抽象数据类型的底层实现。相较于数组,链表牺牲了随机访问的效率,但换来了动态调整的灵活性。本文将以单链表、双

weixin_30431445的博客 344

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

leetcode || 138、Copy List with Random Pointer

problem: A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. Return a deep copy of the list. Hide Tags

hustyangju的足迹 858

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

单链表的复制——C++实现

#include #include using namespace std; typedef int DataType; /*定义链表数据结构*/ typedef struct node{ DataType data; struct node * next; }LNode,* LinkList; int main(){ /*函数声明*/ LinkList Tail_Create_Li

liurry爱是不是不开口才珍贵 4533

数据结构——单链表的定义

用链式存储的线性表统称为链表 链表:单链表、双链表、循环链表、静态链表 思考 单链表的定义 1.什么是单链表 2.用代码定义一个单链表 3.两种实现方式 1).带头结点 2).不带头结点 一、顺序表和单链表的对比 顺序表 单链表 存储方式 顺序存储 链式存储 优点 随机存储 对连续空间无要求 缺点 要求大片连续空间...

晓风岚月 1万+

单链表的插入删除(数据结构与算法)

单链表是一种常见的线性数据结构,由一个个节点组成,每个节点包含两个部分:数据部分和指针部分。单链表的特点是每个节点只能指向下一个节点,而最后一个节点指向一个空指针。这个空指针常用来表示链表的结尾,一般命名为 nullptr。

AII_IIA的博客 8737

数据结构 C++单链表的复制

数据结构 C++单链表的复制

ausfuture的博客 3606

单链表之链表的逐个结点复制

单链表结点的逐个复制 今天我们来说一下单链表的一些应用,比如说如果你要把一串字母如abcde运用单链表进行逐个的复制使它变成aabbccddee你会怎样使用呢?接下来我会具体说一下如何来进行逐个的复制。  首先我们肯定要对链表进行遍历操作,逐个的遍历逐个的复制。    先在遍历单链表的循环内部新建结点,然后对首个结点的数据域进行复制还有插入工作。  进行插入操作以后有一...

l769440473的博客 1616

【数据结构】单链表--复杂链表的复制

在学习链表的最后,我们接触了复杂链表。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。   首先定义链表结构: typedef struct ComplexNode//复杂链表的结构 { DataType data; struct ComplexNode *_next

LazyFish的博客 1030

【数据结构】单链表

单链表是一种动态线性数据结构,由节点(含数据域和指针域)链式连接而成,支持高效插入/删除操作。本文详细介绍了单链表的节点结构设计(包括辅助节点)、核心功能实现(头插/尾插/按位插入、头删/尾删/按值删除等)、查找判空等辅助操作,并对比了顺序存储的优缺点。特别强调了临时指针的两种移动策略:需前驱节点的操作从辅助节点开始,直接遍历则从首节点开始。单链表适合动态内存场景,但存在查找效率低(O(n))和额外指针开销的问题,常用于实现栈、队列及图的邻接表存储。

2302_76793428的博客 1016

复杂单链表的复制

复杂单链表的复制(图文详解)

rzzrwjh的博客 654

c语言数据结构单链表中随机链表的复制

c语言数据结构单链表中随机链表的复制

2401_84272030的博客 1310

数据结构单链表的实现

摘要:本文详细介绍了单链表的概念、结构及实现方式。单链表由独立申请的节点组成,每个节点包含数据域和指针域,物理上不连续但逻辑上连续。文章对比了带头节点和不带头节点单链表的区别,并提供了完整的C语言实现代码,包括节点创建、插入、删除、查找等基本操作。通过结构体定义和指针操作,展示了单链表动态内存管理的特点及其相比顺序表的优势(插入删除高效、避免空间浪费)。

TH2983443454的博客 1175
上一篇: 判断回文整型数
下一篇: 线性查找-数组最大距离
teaspring
博客等级 码龄19年 350粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值