我在理解链表的流程时遇到了一些麻烦。
我的列表及其节点有这些类型定义。
typedef struct node node_t;
struct node{
data_t data;
node_t *next;
};
typedef struct {
node_t *head;
node_t *foot;
} list_t;
list_t *insert_at_foot(list_t *list, data_t value) {
node_t *new;
new = malloc(sizeof(*new));
assert(list!=NULL && new!=NULL);
new->data = value;
new->next = NULL;
if(list->foot==NULL){
//this is the first insertion into the list
list->head = list->foot = new;
}else{
list->foot->next = new;
list->foot = new;
}
return list;
}
特别是以下代码
if(list->foot==NULL){
//this is the first insertion into the list
list->head = list->foot = new;
}else{
list->foot->next = new;
list->foot = new;
}
我知道我们将头部和脚部分配给节点“new”,因为它是第一个节点,但我不理解以下几行。在我看来,如果我们将这个新节点分配到列表的末尾(到脚),
list->foot->next = new;
应该是,
list->foot->next = NULL;
我只是没有明确指定脚指针及其指向同一节点的下一个指针(新)
这一直困扰着我,因为这个概念很容易理解。
答案 0 :(得分:1)
您所看到的是简单的链接列表与称为循环链接列表的细化之间的区别。正如模糊描述的那样,在一个简单的列表中,你通常分别保留一个或两个指针(HEAD
)或(HEAD, TAIL
),分别用于保存起始节点(HEAD
),以及列表的当前结束节点(TAIL
)。有HEAD/TAIL
的重点是什么?
简单的答案是双重的。 (1)它允许在列表的开头或结尾添加节点的永久引用,以及(2)它们提供迭代列表的开始和结束点。但是你必须让他们实现一个链表吗? 否强>
循环链接列表通过让HEAD,TAIL
指向end->next
(因此名称<),无需保留任何first
指针strong>循环链接列表。我使用了两者并迅速放弃了HEAD,TAIL
简单列表,转而使用循环链接列表。为了获得两者的好处,你可以添加和附加指针node->prev
并使列表成为循环双链表,它保留了无需迭代即可访问HEAD & TAIL
节点的功能。
圆形列表比简单列表更难实现 - &gt; 否,它只需要一个不同的add_node
功能。我们来看看吧。显示简单和循环列表之间的关系和差异的图表有助于(显示双链表):
Tail Current Head
(node->prev) node (node->next)
+------------+ +------------+ +------------+
| payload | | payload | | payload |
+------------+ +------------+ +------------+
+<------| prev |<-------| prev |<-------| prev |<------+
| +------------+ +------------+ +------------+ |
| +--->| next |------->| next |------->| next |--->+ |
| | +------------+ +------------+ +------------+ | |
| | | |
| +<--------------------<---------------------<----------------------+ |
| |
+------------------------>--------------------->------------------------>+
如果仔细观察,您会看到简单和循环列表在所有实际用途中完全相同,但是,在的情况下简单的列表,你必须跟踪您的HEAD,TAIL
指针,但对于循环列表,实现的逻辑会跟踪它们为了你。这就是区别,这就是为什么问题的答案:分配HEAD,TAIL
指针的重点?只是提供插入新节点以及迭代的起点和终点。如果您在实现方面很聪明,那么您永远不必担心分配它们,您的列表逻辑会为您跟踪它们。考虑到这一点,这里是一个实现循环列表而不需要跟踪HEAD,TAIL
的示例。
对于您的数据结构,您通常会:
typedef struct list {
char *data;
struct list *prev;
struct list *next;
} list;
然后,您的功能最后为create node
和insert node
。 注意: insert node
函数调用create node
,但如果您选择,则可以在insert node
中完成。
list *create_node (char *str) {
list *node = NULL;
node = malloc (sizeof (struct list)); /* allocate memory for new node */
node-> data = strdup (str); /* allocate and save payload data */
return node; /* return node poiter to add to list */
}
list *insert_at_end (list **ll, char *str) {
/* create the node and allocate memory for node and payload
if no list, then create and assign as list address
else, insert new node at end of list */
list *node = NULL; // create a new node pointer for list
node = create_node (str); // allocate new node and fill payload
/* now just Wire/Rewire list pointers to accept node */
if (!*ll) { // this is the first node
node-> next = node; // circular linked-list
node-> prev = node; // set next, prev = node
*list = node; // set *list = node (adding node to list)
} else { // add - insert new node at end
node->next = *ll; // set node->next to list
node->prev = (*ll)->prev; // set node->prev to list->prev
node-> prev-> next = node; // set (old end)->next to this node
(*ll)-> prev = node; // set list->prev to node
}
return node; // return pointer to current node for convenience
// (immediate reference) and to test success
}
在main()
中,您只需提供类似的内容:
list *mylist = NULL;
int i = 0;
// add data to your list (example using argv entries)
for (i = 0; i < argc; i++)
insert_at_end (&mylist, argv[i]);
...
希望这有助于您理解。无论您使用简单或循环,单还是双链接列表,只需确保您了解逻辑以及为什么要进行每项任务。这只是一个指针游戏。它们都是简单的数据结构,但它们确实需要陡峭但短暂的学习曲线。花点时间学习它,从那时起它们将为您提供良好的服务。对于简单和循环列表,Web上有许多教程和方法。现在知道差异,它将使你更容易找到你需要的东西。
答案 1 :(得分:0)
如果我们没有列表的尾部,则在链接列表的末尾插入是O(n)。如果我们只有列表的头部,我们应该遍历列表并找到结束并在列表的末尾插入项目(如果我们想保留插入顺序)。为了避免这种情况,人们通常会保持列表的尾部。例如,如果您的列表是1-> 2&gt; 3,并且您想在列表中添加4。在这种情况下,头部为1,尾部为3。所以
list->foot->next = 4
表示我们的列表将是1-> 2-> 3-> 4,下一行list->foot = new;
将尾部(脚)分配给4,以确保对于另一个插入我们已更新尾(脚)。