【问题标题】:How to insert a new node in a singly linked list without using a temporary node?如何在不使用临时节点的情况下在单链表中插入新节点?
【发布时间】:2018-06-19 16:22:20
【问题描述】:

我刚开始学习数据结构,一直在尝试用 C 逐步实现一个简单的单链表。我一直在关注一个在线教程,在展示如何将节点插入到列表的开头时,它在函数参数中使用了双星号,我无法理解,并且教程没有详细解释。

然后我解决了这个问题并在没有双指针的情况下实现了函数(通过使用临时节点),但我很想了解和了解我看到的实现是如何工作的以及我自己是如何实现的。

我最近自学了指针,所以我不能说我对它们感到满意。实际上,这就是我开始学习数据结构并进行更多练习的原因之一。我通常了解 取消引用 指针的工作原理,但在这种情况下,我不确定它在函数头中的使用方式和用途。

如果您能解释以下函数的工作方式,我将不胜感激。

这是我不太明白的功能。 (source)

void push(node_t ** head, int val) {
    node_t * new_node;
    new_node = malloc(sizeof(node_t));

    new_node->val = val;
    new_node->next = *head;
    *head = new_node;
}

这是我的实现:

/*
// Singly Linked List Implementation
// 2018/01/10
*/

#include <stdio.h>
#include <stdlib.h>

typedef struct node {
        int val;
        struct node *next;
    } node_t;

void addNodeBegin (node_t *head, int val);
void addNodeEnd (node_t *head, int val);
void printLinkedList();
void freeList(struct node* head);

int main(void) {

    node_t *head = NULL;
    head = malloc(sizeof(node_t));

    if (head == NULL) { return 1; }

    head->val = 1;
    head->next = NULL;

    head->next = malloc(sizeof(node_t));
    head->next->val = 2;
    head->next->next = NULL;

    addNodeEnd(head, 5);
    addNodeBegin(head, 10);
    printLinkedList(head);

    freeList(head);

}

// Insert a node to the beginning of the linked list
void addNodeBegin (node_t *head, int val) {
    node_t *newNode = NULL;
    newNode = malloc(sizeof(node_t));
    newNode->val = val;

    newNode->next = head->next;
    head->next = newNode;

    node_t *tmp = NULL;
    tmp = malloc(sizeof(node_t));

    tmp->val = head->val;
    head->val = newNode->val;
    head->next->val = tmp->val;

    free(tmp);

}

// Insert a node to the end of the linked list
void addNodeEnd (node_t *head, int val) {
    node_t *current = head;

    while (current->next != NULL) {
        current = current->next;
    }

    current->next = malloc(sizeof(node_t));
    current->next->val = val;
    current->next->next = NULL;

}

// Print the linked list
void printLinkedList(node_t *head) {
    node_t *current = head;

    while (current != NULL) {
        printf("%d\n", current->val);
        current = current->next;
    }
}

// Remove the list (and free the occupied memory)
void freeList(struct node* head) {
   struct node* tmp;

   while (head != NULL) {
       tmp = head;
       head = head->next;
       free(tmp);
    }
}

【问题讨论】:

  • 你基本上要求一个关于指针的教程。 StackOverflow 不是这样的地方。通过您最喜欢的搜索引擎查找教程。选择您个人也以最容易理解的方式编写的一种。解决它。非常重要:拿一张纸和一支笔。将变量的值表示为矩形内的数字/字符串。将指针的值表示为矩形,其中箭头指向其他矩形。尤其是指向指针的指针。扮演计算机并执行程序。
  • 在“C”语言中,使用指针至少有两个常见的原因:分配内存时和函数修改参数值时。在push 函数中,你有这两个原因:head 指向一些分配的内存,函数修改head 值。这就是它使用双指针的原因。

标签: c pointers data-structures singly-linked-list


【解决方案1】:

将双星号视为双指针或对指针的引用......问题基本上是,在 C 中,您不能将变量参数(或引用参数)传递给函数,因此,唯一的方法是显式传递要修改的指针的地址。这就是第二个星号的原因,使对引用指针的修改对应用程序可用。两个级别的引用具有不同的含义...您不能将要更新的变量作为参数传递给函数,但可以传递其地址。传递双指针使这成为可能,您可以使用以下内容更新指针:

*ptr_reference = &node_to_be_referenced;

当你调用这个函数时,你将一个指向要修改的指针的引用传递给它,类似于:

function_to_be_called(&pointer_to_first_node, node_to_be_inserted);

或者,如果你想传递一个中间指针:

function_to_be_called(&previous_node_of_inserted->next, node_to_be_inserted);

(看看指针的地址是怎么引用要修改的指针的)

【讨论】:

    【解决方案2】:

    嗯,不做某事最简单的方法就是不做。

    通过你的实现来追踪事物的价值:

    // Insert a node to the beginning of the linked list
    void addNodeBegin (node_t *head, int val) { // assume head = [val1, ...]
        node_t *newNode = NULL;
        newNode = malloc(sizeof(node_t));
        newNode->val = val;                     // newNode=[val, null]
    
        newNode->next = head->next;             // newNode=[val, ...]
        head->next = newNode;                   // head = [val1, [val, ...]]
    
        node_t *tmp = NULL;
        tmp = malloc(sizeof(node_t));
    
        tmp->val = head->val;                   // tmp = [val1, undefined]
        head->val = newNode->val;               // head = [val, [val, ...]]
        head->next->val = tmp->val;             // head = [val, [val1, ...]]
    
        free(tmp);
    
    }
    

    首先,temp唯一要做的就是存储head->val的副本,所以你可以直接存储值,而不是在覆盖head->val之前设置newNode->val:

    // Insert a node to the beginning of the linked list
    void addNodeBegin (node_t *head, int val) { // assume head = [val1, ...]
        node_t *newNode = malloc(sizeof(node_t)); // no need to set to NULL first
        newNode->val = head->val;               // newNode=[val1, undefined]
        newNode->next = head->next;             // newNode=[val1, ...]
        head->next = newNode;                   // head = [val1, [val1, ...]]
        head->val = val;                        // head = [val, [val1, ...]]
    }
    

    其次,这与在列表开头添加节点不同。它在开始之后插入一个节点,然后在第一个和第二个节点之间交换值。这很重要,因为通常单链表有多个头,无论是出于性能原因还是为了实现特定的算法,因此操作在添加到列表的尾部时不应更改它们。 这也意味着您必须进行两种不同的操作 - 追加到空列表 (NULL) 和追加到非空列表。

    push() 的示例实现采用指向链表头的指针的指针,创建一个新节点并改变该指针以指向新节点。这似乎比必要的更难使用,因为它需要将指针的地址指向头部。

    node_t* head = NULL;
    
    push ( &head, 1 );
    push ( &head, 2 );
    push ( &head, 3 );
    push ( &head, 4 );
    
    node_t* head2 = head;
    
    push ( &head2, 5 );
    

    我个人只是将指针返回到新节点:

    node_t* cons (int val, node_t* next) {
        node_t * new_node = malloc(sizeof(node_t));
    
        new_node->val = val;
        new_node->next = next;
    
        return new_node;
    }
    
    node_t* head = cons(4, cons(3, cons(2, cons(1, NULL))));
    node_t* head2 = cons(5, head); 
    

    'Cons' 是construct 的缩写,以这种方式构造单链表的传统名称。

    【讨论】:

      【解决方案3】:

      好吧,如果我理解你的问题,那么:

      在 push 函数中,您实际上替换了内存中的原始节点指针。 例如,如果您有以下列表: 1->2->3->5,并且你有指向 1 的指针,在 val 6 的 push 函数之后,列表将包含:6->1->2->3->5。但是你的头节点会指向6(指针的实际地址会改变。头指针会指向内存中的另一个地方)。

      在第二个选项中,您不会更改头部的指针。您只需更改结构的值(实际上是下一个)。在前面的例子中,插入后头指针仍然指向内存中的相同位置。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-09-26
        • 1970-01-01
        • 2011-06-10
        • 2018-07-28
        • 2012-01-10
        • 2021-06-02
        相关资源
        最近更新 更多