复杂链表的定义:复杂链表的每个节点包括数据,指向下一个节点的指针和一个随机指针(既可指向自己,也可指向NULL,还可指向其它任意节点)。
//复杂链表的节点定义
class RandomListNode {
int label;//节点对应的值
RandomListNode next = null;//指向下一个节点的指针
RandomListNode random = null;//随机指针(可指向自己,也可指向其它节点,还可指向空节点)
RandomListNode(int label) {//含参的构造方法
this.label = label;
}
}
什么叫复杂链表的复制:复杂链表的复制就是将所给的复杂链表复制一份,得到一个新的复杂链表。
现以下图所示的一个复杂链表为例,实现复杂链表的复制:
要实现一个复杂链表的复制,需包含以下3个步骤:
第一步:将复制出来的新节点放在对应老节点(原来节点)的后边
第二步:给新节点的随机指针赋值
第三步:将一个链表拆分成两个链表
了解了大体分为几个步骤之后,现在分别详细介绍一下每一步:
第一步:将复制出的新结点放在原有结点的后边
注意一定要先将新节点的next指向老节点的next,再将老节点的pNext更新为新节点。如果先让老节点的next指向新节点的话,我们就找不到老节点next原来的指向了!!!
RandomListNode node = pHead;//用来遍历链表节点
RandomListNode newNode;//用来存放每次要复制的新的节点
//1. 复制新节点,跟在对应老节点的后面
while (node != null) {
newNode = new RandomListNode(node.label);
newNode.next = node.next;//先让新节点的next指向老节点的next
node.next = newNode;//再让老节点的next指向新节点
node = newNode.next;//相当于上一个老节点往后走两步到下一个老节点
}
第二步:给新结点的随机指针赋值
因为此时已完成了新节点的复制,而每个新节点都是跟在对应老节点的后边,所以新节点的随机指针一定是老节点的随机指针的下一个节点。当老节点的随机指针指向空节点时,新节点的随机指针直接为空指针。
问题:为什么不能在复制新节点的时候同时给新节点的random指针赋值呢?
因为可能有的老节点的random指针指向后边的节点,而此时复制新节点时还没复制到那个节点!!!
//2. 给新节点的random指针赋值
node = pHead;//重新让node从第一个老节点开始往后走
while (node != null) {
newNode = node.next;//新节点
if (node.random != null) {//在老节点random指针不为空的情况下给新节点的random指针赋值
newNode.random = node.random.next;//因为新节点跟在对应老节点的后边,所以新节点的random指针指向的就是老节点random指针的下一个节点(对应老节点的新节点)
}
node = newNode.next;//相当于上一个老节点往后走两步到下一个老节点
}
第三步:将一个链表拆分成两个链表
先将新链表的第一个节点(原来链表第一个节点的下一个节点)保存起来;再用两个指针从链表的第一个节点和第二个节点开始往后走,第一个指针的next指向第二个指针。每次将第二个指针的next赋给第一个指针的next,然后两个指针同时每次往后走一步,直到第一个指针的next为空。
问题:为什么拆分到第一个节点指针的next为空就可以停下了呢?
当走到链表倒数第二个节点的时候,此时由于newNode(也就是最后一个节点)的next指向空,而我们在拆分的时候是将newNode的next赋给node的next的,所以没必要将newNode的next的空指向赋给node的next(此步是没有必要的)。
//3. 拆分链表(将新节点拆分出来)
RandomListNode newHead = pHead.next;//复制出来的新节点链表的第一个节点为原来链表第一个节点的下一个节点
node = pHead;//从链表第一个节点开始往后走
newNode = newHead;//从链表第二个节点开始往后走
while (node.next != null) {//当走到链表倒数第二个节点的时候,此时由于newNode(也就是最后一个节点)的next指向空,所以没必要再让两个节点往后各走一步
node.next = newNode.next;//从第一个节点开始往后改变其next指向为此节点下一个节点的next
node = newNode;//节点往后走一步
newNode = newNode.next;//节点往后走一步
}
完整代码如下:
//复杂链表的复制(方法)
public RandomListNode Clone(RandomListNode pHead) {
if (pHead == null) {//如果给定节点为空,则直接返回空
return null;
}
RandomListNode node = pHead;//用来遍历链表节点
RandomListNode newNode;//用来存放每次要复制的新的节点
//1. 复制新节点,跟在对应老节点的后面
while (node != null) {
newNode = new RandomListNode(node.label);
newNode.next = node.next;//先让新节点的next指向老节点的next
node.next = newNode;//再让老节点的next指向新节点
node = newNode.next;//相当于上一个老节点往后走两步到下一个老节点
}
//2. 给新节点的random指针赋值
node = pHead;//重新让node从第一个老节点开始往后走
while (node != null) {
newNode = node.next;//新节点
if (node.random != null) {//在老节点random指针不为空的情况下给新节点的random指针赋值
newNode.random = node.random.next;//因为新节点跟在对应老节点的后边,所以新节点的random指针指向的就是老节点random指针的下一个节点(对应老节点的新节点)
}
node = newNode.next;//相当于上一个老节点往后走两步到下一个老节点
}
//3. 拆分链表(将新节点拆分出来)
RandomListNode newHead = pHead.next;//复制出来的新节点链表的第一个节点为原来链表第一个节点的下一个节点
node = pHead;//从链表第一个节点开始往后走
newNode = newHead;//从链表第二个节点开始往后走
while (node.next != null) {//当走到链表倒数第二个节点的时候,此时由于newNode(也就是最后一个节点)的next指向空,所以没必要再让两个节点往后各走一步
node.next = newNode.next;//从第一个节点开始往后改变其next指向为此节点下一个节点的next
node = newNode;//节点往后走一步
newNode = newNode.next;//节点往后走一步
}
return newHead;//返回新节点所在链表的第一个节点
}