删除链表中的节点

Delete node in the linked list

提问人:iskander 提问时间:9/19/2023 最后编辑:iskander 更新时间:9/19/2023 访问量:96

问:

给定链表的头部和一个整数,删除链表中所有具有 的节点,并返回新的头部。valNode.val == val

这是我的尝试

struct ListNode *removeElements(struct ListNode *head, int val) {
    struct ListNode *tmp, *prev;

    if (head == NULL)
        return head;

    for (tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->next)
        if (tmp->val == val) {
            prev->next = tmp->next;
            free(tmp);
        }

    return head;
}

但我遇到了分段错误。问题似乎很容易,但我不明白我的错误在哪里。

c for-loop linked-list singlely-linked-list free

评论

2赞 tadman 9/19/2023
🔎🐞是否在调试器中运行代码以查看该错误发生的位置,然后在该故障附近使用断点再次运行它,以便您可以仔细前进⏯️并观察导致该点发生的情况?
3赞 tadman 9/19/2023
您正在调用,然后有一个 Use-After-Free 错误。free(tmp)tmp = tmp->next
2赞 Ted Lyngmo 9/19/2023
如果第一个节点是您要删除的节点之一,那么这样做也会很糟糕,因为然后是 .prev->next = ...prevNULL

答:

2赞 chqrlie 9/19/2023 #1

如果第一个节点的值为 ,则该语句将导致分段错误,因为该指针为 null。使用您的方法,您必须对列表的开头进行特殊情况:valprev->next = tmp->next;prev

struct ListNode *removeElements(struct ListNode *head, int val) {
    while (head != NULL && head->val == val) {
        struct ListNode *tmp = head;
        head = head->next;
        free(tmp);
    }
    if (head != NULL) {
        for (struct ListNode *prev = head, *cur = head->next; cur != NULL;) {
            if (cur->val == val) {
                struct ListNode *tmp = cur;
                prev->next = cur = cur->next;
                free(tmp);
            } else {
                prev = cur;
                cur = cur->next;
            }
        }
    }
    return head;
}

下面是一个使用双指针的更简单的解决方案:

struct ListNode *removeElements(struct ListNode *head, int val) {
    struct ListNode **pp = &head;
    struct ListNode *tmp;
    while ((tmp = *pp) != NULL) {
        if (tmp->val == val) {
            *pp = tmp->next;
            free(tmp);
        } else {
            pp = &tmp->next;
        }
    }
    return head;
}
1赞 Vlad from Moscow 9/19/2023 #2

这个 for 循环

for (tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->next)
    if (tmp->val == val) {
        prev->next = tmp->next;
        free(tmp);
    }

有三个问题。

主要问题是循环不会改变指针(变量)本身。head

因此,如果头节点的数据成员等于变量的值,则指针的值不会更改。valvalhead

第二个问题是,最初指针等于 。因此,如果指针指向的节点将被删除,则在此语句中使用空 ponter 来访问内存prevNULLhead

prev->next = tmp->next;

这会导致未定义的行为。

最后第三个问题是可以使用已经删除的节点

free(tmp);

在 for 循环的表达式中

tmp = tmp->next

此外,在这种情况下,指针被设置为指向已删除节点的指针prevtmp

prev = tmp

可以实施两种方法。

第一种是首先检查指针所指向的节点。head

给你。

struct ListNode * removeElements( struct ListNode *head, int val ) 
{
    if ( head != NULL )
    {
        while ( head->val == val )
        {
            struct ListNode *tmp = head;
            head = head->next;
            free( tmp );
        }

        if ( head != NULL )
        {
            for ( ListNode *current = head; current->next != NULL; )
            {
                if ( current->next->val == val )
                {
                    ListNode *tmp = current->next;
                    current->next = current->next->next;
                    free( tmp );
                }
                else
                {
                    current = current->next;
                }
            }
        }
    }

    return head;
}

第二种方法是通过指向指针的指针来引用列表中的指针。

struct ListNode * removeElements( struct ListNode *head, int val ) 
{
    for ( struct ListNode **current = &head; *current != NULL; )
    {
        if ( ( *current )->val == val )
        {
            struct ListNode *tmp = *current;
            *current = ( *current )->next;
            free( tmp );
        }
        else
        {
            current = &( *current )->next;
        }
    }

    return head;
}

请注意,应始终在使用变量的最小范围内声明变量。