C语言 随机链表的深拷贝

随机链表和深拷贝

随机链表:

如图,这里我们有一个单向链表,但是链表中每个结点都还有一个指针random,这个指针可以指向随意位置,若没有指向链表中的某一个结点那必然指向NULL。

深拷贝:在编程语言中有两种拷贝一种是深拷贝一种是浅拷贝,浅拷贝就是对拷贝对象的一种引用,若是拷贝对象发生变化浅拷贝出来的也会被改变。深拷贝就是正常的拷贝,另外开辟一块将拷贝对象复制进去。

随机链表的深拷贝

若我们只是拷贝一个单链表,只需要不断开辟空间再遍历原链表将其值复制过去,相当于从原链表中提取数据,将数据赋给新链表,新链表只需要不断尾插即可。但是随机链表里面还多了一个指针,这个指针是指向随机结点的,即时我们通过原链表拿到原链表的结点地址也无法进行赋值,因为原链表的地址并不是经过深拷贝后的新结点的地址。

思路一:我们先不考虑对随机指针的赋值,先将原链表深拷贝下来,通过在原链表中同一位置的结点寻找随机指针指向结点的值,再通过这个值再新链表中遍历找到后将地址赋给随机指针。这是暴力实现深拷贝,这种方法的时间复制度会在O(N^2),先是遍历了一遍原数据,对随机指针进行拷贝时再遍历一次,这次遍历中每次循环还得再遍历一遍数组找到对应的值,拿到值后我们还得遍历一遍新数组拿到对应值所在的结点地址进行赋值,思路一的时间复制度会很高。

思路二:因为这个随机指针只能通过原链表去找到对应关系,我们先不进行完全的深拷贝而再原链表中进行一些改变。我们先从第一个结点开始,将这个结点深拷贝一份,这时我们的随机指针指向的结点可能还没出来所以还不能给随机指针赋值,这个结点我们也不另外作为一个链表了而是就接在原链表中合并成一条新链表

我们将其插再原结点的后面,当我们完成全部结点的第一次拷贝时会变成如下图的单链表。

当我们拿到这个链表的时候就可以进行随机指针的赋值了,我们定义两个指针,一个指针指向原链表的结点,一个指向新链表的结点。我们每一个原链表结点的next指针指向的都是自己的拷贝结点,那将新链表结点的随机指针指向我们原链表结点的随机指针指向的结点的next就完成随机指针的赋值了,然我我们再将新结链接在一起便完成了随机链表的深拷贝了。

随机链表深拷贝代码实现

struct Node 
{
    int val;
    struct Node* next;
    struct Node* random;
};
struct Node* copyRandomList(struct Node* head)//随机链表的深拷贝
{
    if (head == NULL)
        return NULL;
    struct Node* cpy = head;
    struct Node* cur = NULL;
    struct Node* newhead = NULL;
    while (cpy)
    {
        cur = (struct Node*)malloc(sizeof(struct Node));
        assert(cur);
        cur->val = cpy->val;
        cur->next = cpy->next;
        cpy->next = cur;
        cpy = cpy->next->next;
    }
    cur = head;
    cpy = head->next;
    while (cur)
    {
        if (cur->random == NULL)
            cpy->random = 0;
        else
        {
            cpy->random = cur->random->next;
        }
        if (cpy->next == NULL)
            break;
        cpy = cpy->next->next;
        cur = cur->next->next;
    }
    newhead = head->next;
    cur = head->next;
    while (cur->next != NULL)
    {
        cur->next = cur->next->next;
        cur = cur->next;
    }
    return newhead;
}

这里时第一个循环,我们在第一个循环中,进行的是第一步的拷贝,需要两个指针,一个用于遍历原链表,一种用于创建新结点并进行拼接。我们首先是原链表的指针指向第一个结点,在创建一个新结点(cur)将原结点的值赋给新结点,然后将新结点的next指针指向原结点的next再将原结点的next指针指向新结点完成拼接,然后原结点指针跳过新结点(走两步)到原链表的下一个结点继续进行拷贝。直到原链表指针(cur)走完整个链表到达NULL的时候退出循环。

第二层循环我们给随机指针赋值,定义两个指针,一个指向原链表结点(cur)一个指向拷贝链表的结点(cpy)判断,若原链表结点是指向NULL我们也将拷贝链表结点指向NULL(上面代码赋了0值)效果是一样的。若不是指向NULL我们将随机指针赋值为原链表的随机指针指向的结点的next即拷贝结点的地址,然后两指针各走两步到新旧链表的下一个结点,注意这里我们因为是各走两步,当我们对最后一个结点进行完赋值后新链表结点的指针继续走会成为野指针,所以我们加一个判断在赋值完之后,若新结点的next指针指向NULL就退出循环。

我们来到最后一层循环将新结点链接起来,我们先将新结点的头结点保存起来即newhead指针,然后定义一个指针(cur)指向新链表头结点,进入循环,条件是:到达最后一个结点,新结点的最后一个结点的next指针是指向NULL的,所以到达最后一个结点我们就不需要进行后续链接可以跳出循环了。进入循环后我们将cur指向的结点的next指针指向跳过原链表结点的地址,我们也可以再定义一个指针记录下原结点地址来释放空间。然后因为这时的cur指向的结点的next指针已经指向下一个新结点了所以我们只需要前走一步就可以,循环结束完成新链表的拼接返回新链表的指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值