如果你想创建一个像这样的单链表:
struct Node {
int data;
Node *next;
};
struct List{
Node *head;
// Node *tail; --> necessary?
Node *last;
};
此列表包含方法“append”,“remove”,“printList”和“findElement”。 是否有必要尾巴?因为使用“last”,您可以寻址最后一个节点。
那么当有必要让所有三个节点“头”,“尾”和“最后”?如果要插入已排序到列表中的节点,例如?
答案 0 :(得分:2)
不,没有必要。尾部等于head->next
,因此它将是多余的并添加簿记开销以保持此字段更新。
另请注意,字段last
有点不寻常。在大多数用例中,您可以在单个链接列表的头部添加元素,并在真正需要添加到最后时使用不同的数据结构。
答案 1 :(得分:1)
如果您以类似队列的FIFO方式处理链接列表而不是类似堆栈的LIFO方式或希望能够传输整个链接列表,那么尾部可能非常有用从头到尾的元素列表,不会破坏元素的相对顺序。
请注意,我指的是' tail'作为对列表中最后一个节点的引用,我认为可以安全地假设问题是关于。
许多非常微优化的SLL实现通常都是无尾的,并且像堆栈一样工作,同时由有效的固定分配器支持,用于引用的局部性(缓存友好性)和更快的节点分配/解除分配。 SLL相对于基于可变大小的基于数组的序列的主要好处是能够通过仅更改next
指针/引用的值以及插入/移除元素时无效而开始移动物体。您正在使用涉及指针的本地低级语言。缺少尾部可以通过减少从堆栈中推送和弹出操作所需的分支指令数量来提高性能。
对于您列出的需求,如果您的append
和remove
操作可以通过LIFO堆栈方式从前面严格工作,或者只是增加不必要的复杂性和开销,如果你想能够追加到后面但是以FIFO方式从前面移除而不涉及任何迭代,例如如果你在后一种情况下没有尾巴,那么其中一个操作将从恒定时间复杂度变为线性时间复杂度,你可以通过交换线性时间算法的复杂性来改善用例。维持尾部的微观水平相对较小。
答案 2 :(得分:1)
我认为这取决于您要使用的操作。
假设您要在列表的尾部 插入 和 删除 节点,将列表中的最后一个节点保留在列表中当然是明智的选择。
否则,如果您想在列表的开头执行操作,则不需要 last 节点。
答案 3 :(得分:1)
实际上,你可以实现enqueue(尾部附加),push(前置头部),出列(从头部删除),当然还可以使用一个 -pointer头来查找和打印。诀窍是使列表循环并使标题指向尾部。然后tail->next
是头。
#include <stdio.h>
#include <stdlib.h>
typedef struct node_s {
struct node_s *next;
int data;
} Node;
typedef struct list_s {
Node *tail;
} List;
Node *new_node(int data) {
Node *node = malloc(sizeof *node);
node->data = data;
node->next = node;
return node;
}
void init_list(List *list) {
list->tail = NULL;
}
int is_empty(List *list) {
return list->tail == NULL;
}
void enqueue(List *list, Node *node) {
if (list->tail) {
Node *head = list->tail->next;
node->next = head;
list->tail->next = node;
list->tail = node;
} else list->tail = node->next = node;
}
void push(List *list, Node *node) {
if (list->tail) {
Node *head = list->tail->next;
node->next = head;
list->tail->next = node;
} else list->tail = node->next = node;
}
Node *dequeue(List *list) {
Node *head = list->tail->next;
if (head == list->tail)
list->tail = NULL;
else
list->tail->next = head->next;
return head;
}
void print_list(List *list) {
printf("The list:\n");
if (list->tail) {
Node *head = list->tail->next;
Node *p = head;
do {
printf("%d\n", p->data);
p = p->next;
} while (p != head);
}
}
int main(int argc, char *argv[]) {
List list[1];
init_list(list);
// Build the list in order and print it.
for (int i = 0; i < 4; i++) enqueue(list, new_node(i));
print_list(list);
// Remove elements from head until empty.
printf("Dequeueing:\n");
while (!is_empty(list)) {
Node *node = dequeue(list);
printf("%d\n", node->data);
free(node);
}
// Build the list in reverse order and print it.
for (int i = 0; i < 4; i++) push(list, new_node(i));
print_list(list);
return 0;
}