全文目录
引言
前面我们了解了关于单链表与带头双向循环链表,相信大家对于链表的知识已经有所掌握了,在本篇文章中将继续介绍一道题目:复制带随机指针的链表。
复制带随机指针的链表OJ连接
复制带随机指针的链表
题目描述与思路
这道题目要求我们深度拷贝一种带随机指针的链表,该链表的结点由3个成员,分别是:数据val、指向下一个结点的指针next、指向某个随机结点的指针random:
struct Node
{
int val;
struct Node* next;
struct Node* random;
};
要求我们深度拷贝该链表,即在拷贝的链表中,不仅要拷贝每个链表中的数据然后用next将每个结点按顺序连接起来,还要实现拷贝的链表每个结点中的random指针指向的结点也与原链表相同。
比如有带随机指针的链表:
其中有5个结点,每个结点中有一个指针next指向下一个结点。同时有第1个结点的random指针指向NULL;第2个结点的random指针指向第1个结点;第3个结点的random指针指向第5个结点;第4个结点的random指针指向第3个结点;第5个结点的random指针指向第1个结点。
在我们复制出的链表中,也要具有这样的关系。
函数只有1个参数,即链表的头节点。结构体变量与主函数部分已经定义,我们只需要实现接口即可。并返回复制后的头结点。
对于这道题的实现,在这篇文章中将提供一种较为简单的思路:
先将链表每个结点复制一份放到原结点的后面。此时,每个原来的结点的next指针指向的都是复制后的它本身,每一个复制的结点的next指针指向的都是原该节点的下一个结点(最后一个指向的是NULL);
然后,我们通过遍历原链表,找到原链表每个结点的random指针指向的原结点,该节点的next成员指向的就是它的复制结点。将它与对应的新结点连接即可;
最后,将已经拷贝好的新结点从原链表上剥离再依次连接,并且复原原链表,返回值即可。
实现
为了使代码更简洁,我们可以对结构体名称重命名:
typedef struct Node Node;
为实现这个算法,我们首先创建3个指针变量:cur用于遍历原链表、next用于指向原链表中cur的下一个结点、copy用于指向复制的新链表的结点:
Node* cur = head;
Node* next = head;
Node* copy = NULL;
有了这些指针后,首先实现遍历原链表,拷贝每一个结点并将其放在原结点后:
while循环,当cur为空时循环结束。
每次循环中,首先将next改为cur->next使其指向原链表中next的下一个元素;然后动态开辟一块空间,用copy指向这块空间,并判断其是否成功开辟;然后将copy->val 改为 cur->val,即拷贝该原结点的值到新结点;然后将copy->next 改为 next,即连接新结点与该原结点的下一个结点;然后将cur->next 改为 copy,即连接该原结点与新结点;最后cur改为next,cur指向该原结点在原链表中的下一个结点:
再遍历原链表,新结点的random指针指向的元素就是原结点的random的next指向的结点。当然,在遍历之前要先将cur的值改回头节点:
首先,将copy 改为 cur->next,即使copy指向由cur复制而来的结点;然后将next 改为 copy->next,即让next指向原链表中cur的下一个结点;然后判断cur->random是否为空,若为空就说明该原结点的random指针指向的是NULL,此时让对应新结点的random指针也指向NULL即可,若不为空,则新结点的random指针指向的元素就是原结点的random的next指向的结点,此时将copy->random 改为 cur->random->next即可,即连接copy与其random指向的结点;最后将cur改为next,即将cur向后移动到原链表的下一个结点:
最后,再遍历一遍链表,将新结点尾插到一个哨兵位的头结点上,然后恢复原链表:
我们可以动态开辟这个哨兵位的头结点,验证并初始化:
Node* copyguard = (Node*)malloc(sizeof(Node));
assert(copyguard);
copyguard->val = -1;
copyguard->random = NULL;
copyguard->next = NULL;
并且创建一个copytail变量用来始终指向链表的尾结点,初始化为头节点copyguard:
Node* copytail = copyguard;
在将cur的值改回头节点后就可以开始遍历了:
首先将copy 改为 cur->next,即使copy指向由cur复制而来的结点;然后将next 改为 copy->next,即让next指向原链表中cur的下一个结点后;然后将copytail->next 改为 copy,即将copy尾插到尾结点后;然后将copytail 改为 copy,即使copytail始终指向尾结点;然后将cur->next 改为 next,即恢复原链表中cur与其下一个结点的连接;然后将cur改为next,即将cur向后移动到原链表的下一个结点:
最后,将copytail->next改为NULL,然后free释放哨兵位的头结点的空间后就可返回copyguard->next的值了。但是由于释放后就不能返回值,所以先用一个ret指针记录low->next的值,等释放low与above指向的空间后,返回ret即可:
struct Node* copyRandomList(struct Node* head)
{
typedef struct Node Node;
Node* cur = head;
Node* next = head;
Node* copy = NULL;
while (cur)//把链表的每一个结点拷贝一份放在原结点的后面
{
next = cur->next;
copy = (Node*)malloc(sizeof(Node));
assert(copy);
copy->val = cur->val;
copy->next = next;
cur->next = copy;
cur = next;
}
cur = head;
while (cur)//原结点的random指向的是原链表的任意指针指向的结点,该结点的next指向的就是该新结点的random的指向
{
copy = cur->next;
next = copy->next;
if (cur->random)
{
copy->random = cur->random->next;
}
else
{
copy->random = NULL;
}
cur = next;
}
//将新链表独立并将原链表连接
Node* copyguard = (Node*)malloc(sizeof(Node));
assert(copyguard);
copyguard->val = -1;
copyguard->random = NULL;
copyguard->next = NULL;
Node* copytail = copyguard;
cur = head;
while (cur)
{
copy = cur->next;
next = copy->next;
copytail->next = copy;
copytail = copy;
cur->next = next;
cur = next;
}
copytail->next = NULL;
Node* ret = copyguard->next;
free(copyguard);
return ret;
}
总结
到此,关于复制带随机指针的链表的一种解法就介绍完了。当然会有其他的算法解决,欢迎大家在评论区讨论
如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出
如果本文对你有帮助,希望一键三连哦
希望与大家共同进步哦