#c #pointers #linked-list
#c #указатели #связанный список
Вопрос:
typedef struct node node;
struct node {
int data;
node *next;
};
int insert_asc(node **phead, int data) {
node **traser;
node *newnode = malloc(sizeof(node));
if (newnode == 0)
return 0;
newnode->data = data;
for (traser = phead; *traser != 0; traser = amp;(*traser)->next)
if (data <= (*traser)->data)
break;
newnode->next = *traser;
*traser = newnode;
return 1;
}
Меня смущает то, что вы разыменовываете двойной указатель traser
.
почему (*traser)->next
содержит адрес следующего узла?
и что именно *traser
здесь?
Комментарии:
1. Опубликуйте минимальный, компилируемый код.
2.
traser
это способ отслеживатьnext
указатель предыдущего узла. Чтобы вставить в середину списка, вам нужно изменить ссылку с предыдущего узла на ваш новый узел.3. Я предлагаю вам провести некоторую отладку резиновой утки в коде. Это также может помочь, если вы попытаетесь выполнить все операции, используя простой «связанный список» на бумаге.
4. Общаясь с уткой, также смотрите на приоритет оператора C
Ответ №1:
Двойные указатели используются в опубликованном коде для двух отдельных целей:
-
node **phead
: заголовок списка передается по ссылке, поэтому он может быть обновленinsert_asc
, если новый узел должен быть вставлен в начало списка. Передача по ссылке невозможна в C, идиоматический способ добиться этого — передать указатель на значение, которое будет обновлено функцией, следовательно, двойной указательphead
. -
node **traser
: Чтобы избежать создания особого случая для пустого списка и вставки в начало списка, программист использует указатель для отслеживания места для вставки нового узла.traser
сначала указывает на начало списка, которое в данном случае является значениемphead
и обновляется, чтобы указывать на связь между узлами,next
членом текущего узла, когда определено, что новый узел должен быть вставлен после текущего. Это элегантный способ реализовать вставку без специального регистра. C позволяет благодаря указателям, это невозможно ни в Java, ни в javascript, потому что эти языки не имеют обобщенных указателей.
Обратите внимание, однако, что код можно было бы сделать более читаемым при использовании NULL
вместо 0
при сравнении указателей:
typedef struct node node;
struct node {
int data;
node *next;
};
int insert_asc(node **phead, int data) {
node **traser;
node *newnode = malloc(sizeof(node));
if (newnode == NULL)
return 0;
newnode->data = data;
for (traser = phead; *traser != NULL; traser = amp;(*traser)->next) {
if (data <= (*traser)->data)
break;
}
newnode->next = *traser;
*traser = newnode;
return 1;
}
Обратите также внимание, что новые узлы с заданным значением data
вставляются перед узлами с тем же data
значением. В данном случае это не имеет значения и может быть немного быстрее для списков с большим количеством дубликатов, но если полезная нагрузка была более сложной, этот метод вставки реализовал бы нестабильную сортировку, тогда как использование <
вместо <=
сделало бы сортировку стабильной.
Для иллюстрации вот альтернативная реализация, которая не использует двойной указатель для точки вставки и требует дополнительных тестов для особых случаев:
int insert_asc(node **phead, int data) {
node *cur;
node *newnode = malloc(sizeof(node));
if (newnode == NULL)
return 0;
newnode->data = data;
cur = *phead;
if (cur == NULL || cur->next == NULL) {
newnode->next = cur;
*phead = newnode;
} else {
while (cur->next != NULL amp;amp; data < cur->next->data)
cur = cur->next;
newnode->next = cur->next;
cur->next = newnode;
}
return 1;
}
Ответ №2:
Вы используете двойной указатель здесь, чтобы отслеживать начало вашего списка.
Если бы вы использовали простой указатель здесь и поменяли узлы, вы рисковали бы потерять адрес некоторых узлов вашего списка.
Это потому, что если бы вы передавали простой указатель на заголовок вашего списка, вы бы затем манипулировали копией вашего адреса head в своей функции, поэтому, когда вы меняете позиции в своей функции, адрес вашего head все равно был бы тем же, если вы поменяли заголовок с другим узлом, тогда адреса всех узлов перед старым заголовком будут потеряны после того, как ваша функция изменит ваш список.
Редактировать:pythontutor.com это инструмент, который помог мне довольно легко понять поведение связанного списка благодаря его превосходному инструменту визуализации, я бы настоятельно рекомендовал вам использовать его.
Комментарии:
1. Вопрос на самом деле не в заголовке списка. Это можно легко сделать с помощью одного указателя,
node *prev
который содержит адрес предыдущего узла вместо.next
элемента предыдущего узла.2. Справедливо, он отвечает на вопрос, заданный в заголовке, который, похоже, отличается от вопроса, заданного в конце вопроса. Однако вы передаете адрес указателя списка, чтобы его можно было изменить в функции, и изменение видно обратно в вызывающем. Если бы вы просто передали сам указатель, функция получила бы копию, и любые внесенные изменения были бы потеряны при возврате функции, пока обновленный указатель списка не был возвращен и назначен обратно в вызывающем. В итоге вы не рискуете потерять «какой-то узел», вы рискуете потерять свой «адрес списка» .