如何在不使用临时节点的情况下在单链表中插入新节点?
How to insert a new node in a singly linked list without using a temporary node?
我刚开始学习数据结构,一直在尝试用 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);
}
}
嗯,如果我理解你的问题,那么:
在push函数中你实际上替换了内存中原来的节点指针。
例如,如果您有以下列表:
1->2->3->5,并且您有指向 1 的指针,在带有 val 6 的 push 函数之后,列表将包含:6->1->2->3->5。但是你的头节点会指向6(指针的实际地址会改变,头指针会指向内存中的另一个地方)。
在第二个选项中,你不改变头部的指针。您只需更改结构的值(实际上是下一个)。与前面的例子一样,在插入之后,头指针仍然指向内存中的相同位置。
嗯,不做某事最简单的方法就是不去做。
通过您的实施追踪事物的价值:
// 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 );
我个人只是 return 指向新节点的指针:
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 的缩写,以这种方式构造单链表的传统名称。
将双星号视为双指针或对指针的引用....问题基本上是,在 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);
(看指针的地址是怎么用来引用要修改的指针的)
我刚开始学习数据结构,一直在尝试用 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);
}
}
嗯,如果我理解你的问题,那么:
在push函数中你实际上替换了内存中原来的节点指针。 例如,如果您有以下列表: 1->2->3->5,并且您有指向 1 的指针,在带有 val 6 的 push 函数之后,列表将包含:6->1->2->3->5。但是你的头节点会指向6(指针的实际地址会改变,头指针会指向内存中的另一个地方)。
在第二个选项中,你不改变头部的指针。您只需更改结构的值(实际上是下一个)。与前面的例子一样,在插入之后,头指针仍然指向内存中的相同位置。
嗯,不做某事最简单的方法就是不去做。
通过您的实施追踪事物的价值:
// 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 );
我个人只是 return 指向新节点的指针:
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 的缩写,以这种方式构造单链表的传统名称。
将双星号视为双指针或对指针的引用....问题基本上是,在 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);
(看指针的地址是怎么用来引用要修改的指针的)