您已经组合和/或混淆了列表和列表元素结构。列表通常具有 head 和 next 元素,而不是 next 和 prev。后者通常用于元素 [而不是列表]。
而且,除非你真的想要一个“列表列表”,否则列表结构中的指针必须是指向元素的指针,而 不是 列表。而且,您不希望将该列表结构中的 head/tail 指针初始化为指向同一个列表。
另外,仅仅因为一个列表有两个指针,不使它成为一个双重链表。如果元素only 有一个next 指针,它可能是一个singly 链表。为了使它成为一个双重链表,每个元素都需要一个next和prev指针。
还有更多内容。我已经为您的程序创建了一个经过清理和注释的版本。请注意其他更改:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// element within list
typedef struct element {
int value; // data value
struct element *next; // pointer to next element in list
struct element *prev; // pointer to previous element in list
} element_t;
// list of elements
typedef struct list {
element_t *head; // pointer to first element in list
element_t *tail; // pointer to last element in list
} list_t;
// new_element -- get new element
element_t *
new_element(int value)
{
element_t *newptr;
newptr = calloc(1,sizeof(element_t));
if (newptr == NULL) {
printf("new_element: malloc failure\n");
exit(1);
}
newptr->value = value;
return newptr;
}
// list_insert -- insert before list head
void
list_insert(list_t *list,element_t *newptr)
{
element_t *head;
head = list->head;
newptr->prev = NULL;
newptr->next = head;
// insert at head of empty list
if (head == NULL)
list->tail = newptr;
// insert at head of non-empty list
else {
newptr->next = head;
head->prev = newptr;
}
// make new element the head of the list
list->head = newptr;
}
// list_find_value -- find existing list element by value
element_t *
list_find_value(list_t *list,int value)
{
element_t *curptr;
// search for pre-existing element with the same value
for (curptr = list->head; curptr != NULL; curptr = curptr->next) {
if (curptr->value == value)
break;
}
return curptr;
}
// list_insert_unique -- insert before list head
void
list_insert_unique(list_t *list,element_t *newptr)
{
element_t *curptr;
// search for pre-existing element with the same value
curptr = list_find_value(list,newptr->value);
// only insert if we did _not_ find one
if (curptr == NULL)
list_insert(list,newptr);
else
free(newptr);
}
// list_print -- print the list
void
list_print(list_t *list)
{
element_t *curptr;
printf("list_print:");
// search for pre-existing element with the same value
for (curptr = list->head; curptr != NULL; curptr = curptr->next)
printf(" %d",curptr->value);
printf("\n");
}
// list_print -- print the list in reverse order
void
list_rprint(list_t *list)
{
element_t *curptr;
printf("list_rprint:");
// search for pre-existing element with the same value
for (curptr = list->tail; curptr != NULL; curptr = curptr->prev)
printf(" %d",curptr->value);
printf("\n");
}
// main -- main program
int
main(void)
{
list_t mylist;
element_t *e;
// initialize list
mylist.head = NULL;
mylist.tail = NULL;
// Add unique element to list
e = new_element(42);
list_insert_unique(&mylist,e);
e = new_element(23);
list_insert_unique(&mylist,e);
e = new_element(17);
list_insert_unique(&mylist,e);
// try to insert a duplicate
e = new_element(17);
list_insert_unique(&mylist,e);
// try to insert a duplicate
e = new_element(42);
list_insert_unique(&mylist,e);
// print list in order [checks the head/next pointers]
list_print(&mylist);
// print list in reverse order [checks the tail/prev pointers]
list_rprint(&mylist);
return 0;
}
更新:
但问题是要求改进给定列表实现的插入逻辑。
你已经做了足够长的时间知道不做一些你在这个问题中做过的事情。
您发布的代码已损坏,甚至无法编译。如果它没有错误,则应该改为在 CodeReview 上询问。
因此,其余代码变得可疑。现在,在查看它之后,我猜你想要一个双向链接的循环列表。但是,对双向链表指针使用list 具有欺骗性。如果将其命名为link 会更清楚。
您询问了关于防止重复的问题 [in O(1)]:
对于无意中将e 插入mylist 两次的可能性似乎并不可靠。
这对于标准(即指针匹配、值匹配或两者)并不完全清楚。
您没有提供任何代码来检查重复项,只是 list_insert 在列表头部插入。
什么是处理这个问题的计算有效的方法,最好在 O(1) 时间内?
在列表头部插入 O(1),但任何检查重复项都需要完整的列表扫描,即 O(n)强>。这在我看来是基本的。不一定要弄清楚O(n),但至少知道它可能不是O(1)
因此,鉴于所有这些,如果您的代码被重构,您不应该感到惊讶。
即使 [现在] 知道链接的循环列表,我也不建议您按照您的方式进行操作,因此我进行了重构。使用您的方法,您“失去”对 value 的访问权限,因此基于值的重复扫描变得更加成问题。
我所做的是将链接嵌入到元素中。这使得几乎所有的代码都更简单。按照你的做法,element_t 是一个从link_t(nee list_t)“继承”的子类。这对于 C++ 来说是可以的,但它会使 C 中的代码有点混乱。我在下面创建了一个这样做的版本,只是为了显示代码变得多么复杂。
也许最令人震惊的是,您编辑了原始代码以“修复”损坏的部分。您应该知道不这样做,但将修复附加到您的帖子底部,因为它会使答案不再与问题匹配。
此外,Wayland 代码/链接似乎与您发布的代码几乎没有匹配。您甚至没有尝试以任何特定方式将两者联系起来。
这里有一个更好的list_insert_unique函数:
// list_insert_unique -- insert before list head
void
list_insert_unique(list_t *list,element_t *newptr)
{
int dupflg;
element_t *curptr;
// search for pre-existing element
dupflg = 0;
for (curptr = list->head; curptr != NULL; curptr = curptr->next) {
// prevent list corruption
if (curptr == newptr) {
dupflg = -1;
break;
}
// don't add duplicate values (we must continue to search for a
// pointer match)
if (curptr->value == value)
dupflg = 1;
}
do {
// we're trying to re-add the _same_ node
if (dupflg < 0)
break;
// we're trying to add a different node but with an existing value
if (dupflg) {
free(newptr);
break;
}
// only insert if we did _not_ find one
list_insert(list,newptr);
} while (0);
}
这是一个保留更多原始结构的变体。它是非-循环的,但这种差异相对较小。请注意,它需要在超类和子类之间进行更多的“向上转型”和/或“向下转型”。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
// element within list
typedef struct link link_t;
struct link {
link_t *next; // pointer to next link in list
link_t *prev; // pointer to previous link in list
};
// list of elements
typedef struct list {
link_t *head; // pointer to first element in list
link_t *tail; // pointer to last element in list
} list_t;
// value types
typedef enum {
VTYPE_INT = 1,
VTYPE_FLT = 2
} vtype_t;
// value within list
typedef struct value {
link_t link; // list linkage
vtype_t type; // value type
union {
int int_value; // data value
double flt_value; // data value
};
} element_t;
#define ELEMENTOF(_eptr) ((element_t *) (_eptr))
#define LINKOF(_vptr) ((link_t *) (_vptr))
// new_element -- get new element
element_t *
new_element(vtype_t type,...)
{
va_list ap;
element_t *newptr;
newptr = calloc(1,sizeof(element_t));
if (newptr == NULL) {
printf("new_element: malloc failure\n");
exit(1);
}
newptr->type = type;
va_start(ap,type);
switch (type) {
case VTYPE_INT:
newptr->int_value = va_arg(ap,int);
break;
case VTYPE_FLT:
newptr->flt_value = va_arg(ap,double);
break;
}
va_end(ap);
return newptr;
}
// list_insert -- insert before list head [O(1)]
void
list_insert(list_t *list,link_t *newptr)
{
link_t *head;
head = list->head;
newptr->prev = NULL;
newptr->next = head;
// insert at head of empty list
if (head == NULL)
list->tail = newptr;
// insert at head of non-empty list
else {
newptr->next = head;
head->prev = newptr;
}
// make new element the head of the list
list->head = newptr;
}
// list_find_value -- find existing list element by value [O(n)]
link_t *
list_find_value(list_t *list,element_t *newval,int *matchptr)
{
int dupflg;
element_t *curval;
link_t *curptr;
// search for pre-existing element with the same value
dupflg = 0;
for (curptr = list->head; curptr != NULL; curptr = curptr->next) {
curval = ELEMENTOF(curptr);
// don't allow the list to become corrupted
if (curval == newval) {
dupflg = -1;
break;
}
// only compare values of similar type
if (curval->type != newval->type)
continue;
switch (newval->type) {
case VTYPE_INT:
if (curval->int_value == newval->int_value)
dupflg = 1;
break;
case VTYPE_FLT:
if (curval->flt_value == newval->flt_value);
dupflg = 1;
break;
}
}
if (matchptr != NULL)
*matchptr = dupflg;
return curptr;
}
// list_insert_unique -- insert before list head
void
list_insert_unique(list_t *list,element_t *newval)
{
link_t *curptr;
int dupflg;
// search for pre-existing element with the same value
curptr = list_find_value(list,newval,&dupflg);
// only insert if we did _not_ find one
do {
// prevent double free
if (dupflg < 0)
break;
if (dupflg) {
free(newval);
break;
}
list_insert(list,LINKOF(newval));
} while (0);
}
// value_print -- print a single value
void
value_print(link_t *curptr)
{
element_t *valptr;
valptr = ELEMENTOF(curptr);
switch (valptr->type) {
case VTYPE_INT:
printf(" I:%d",valptr->int_value);
break;
case VTYPE_FLT:
printf(" F:%g",valptr->flt_value);
break;
}
}
// list_print -- print the list
void
list_print(list_t *list)
{
link_t *curptr;
printf("list_print:");
for (curptr = list->head; curptr != NULL; curptr = curptr->next)
value_print(curptr);
printf("\n");
}
// list_rprint -- print the list in reverse order
void
list_rprint(list_t *list)
{
link_t *curptr;
printf("list_rprint:");
for (curptr = list->tail; curptr != NULL; curptr = curptr->prev)
value_print(curptr);
printf("\n");
}
// main -- main program
int
main(void)
{
list_t mylist;
element_t *e;
// initialize list
mylist.head = NULL;
mylist.tail = NULL;
// Add unique element to list
e = new_element(VTYPE_INT,42);
list_insert_unique(&mylist,e);
e = new_element(VTYPE_INT,23);
list_insert_unique(&mylist,e);
e = new_element(VTYPE_INT,17);
list_insert_unique(&mylist,e);
// try to insert a duplicate
e = new_element(VTYPE_INT,17);
list_insert_unique(&mylist,e);
// try to insert a duplicate
e = new_element(VTYPE_INT,42);
list_insert_unique(&mylist,e);
e = new_element(VTYPE_INT,67);
list_insert_unique(&mylist,e);
list_insert_unique(&mylist,e);
// change the value [which also changes the list's value] and try to
// insert
e->int_value = 66;
list_insert_unique(&mylist,e);
// this is _not_ a duplicate because the type is different
e = new_element(VTYPE_FLT,42.0);
list_insert_unique(&mylist,e);
// print list in order [checks the head/next pointers]
list_print(&mylist);
// print list in reverse order [checks the tail/prev pointers]
list_rprint(&mylist);
return 0;
}
更新 #2:
超级跟进,谢谢。是的,我知道原始帖子的错误并对其进行了编辑以确保正确性;以及所提供实现的缺陷。
好吧,我认为我已经找到了 [我们的 :-)] 两个世界中最好的。
首先,这取决于重复的含义。最初,我认为这是一个重复的值。我的 updated list_insert_unique 必须完整扫描到最后,即使它在中途找到 value 匹配,因为它必须完成扫描才能找到重复的 指针匹配。这迫使 O(n)
但是,如果您不/不介意重复值,而只是想检查列表中是否已有新节点(即允许重复值但防止列表损坏),则 可以 em> 使用不同的检测方法在 O(1) 内完成。
我已将我的 更新 list_insert_unique 拆分为两个函数:
list_insert_unique_value 非常相似。这是 O(n) 最坏 的情况,但平均为 O(n/2)。这是因为值扫描可以现在在找到值匹配后立即停止。
list_insert_unique_pointer 只检查新节点是否已经在列表中,并且总是 O(1)
这是可行的,因为它假设类似于我的 new_element 函数,它执行 calloc 将链接指针设置为 NULL。在调用list_insert 之前,如果不执行e.next = NULL; e.prev = NULL;,它不会在您原来的main 中工作。
以下是包含这些更改的原始版本。我仍然建议将链接嵌入到element_t 中的简单性,但这可以[轻松]适应我的第二个示例的超类/派生类模型[类似于您的原始]。
旁注:这些年来我已经完成了很多双向链接的实现,使用 both 样式,并且我更喜欢尽可能嵌入。实际上,我有多个独立的代码库,并且在一个中使用了“继承”模型,在另一个中使用了“嵌入”模型。我这样做是为了试验速度、易用性、检测列表损坏或链接损坏等。虽然 linux 内核使用循环列表,但我从来没有,主要是因为我找不到使它们循环的用例在我自己的代码中——YMMV。
下面的代码[仍然]是非循环的,但也可以适应循环列表。 for 循环必须更改为:
for (curptr = list->head->next; curptr != head; curptr = curptr->next)
对于我自己的代码,我 [would] 通常会在 CPP 宏中隐藏这样的丑陋,例如:
for (FORALL_FORWARD(list,curptr))
不管怎样,这里是:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// element within list
typedef struct element {
int value; // data value
struct element *next; // pointer to next element in list
struct element *prev; // pointer to previous element in list
} element_t;
// list of elements
typedef struct list {
element_t *head; // pointer to first element in list
element_t *tail; // pointer to last element in list
} list_t;
// new_element -- get new element
element_t *
new_element(int value)
{
element_t *newptr;
newptr = calloc(1,sizeof(element_t));
if (newptr == NULL) {
printf("new_element: malloc failure\n");
exit(1);
}
newptr->value = value;
return newptr;
}
// list_insert -- insert before list head
void
list_insert(list_t *list,element_t *newptr)
{
element_t *head;
head = list->head;
newptr->prev = NULL;
newptr->next = head;
// insert at head of empty list
if (head == NULL)
list->tail = newptr;
// insert at head of non-empty list
else {
newptr->next = head;
head->prev = newptr;
}
// make new element the head of the list
list->head = newptr;
}
// list_insert_unique_pointer -- insert before list head [O(1)]
void
list_insert_unique_pointer(list_t *list,element_t *newptr)
{
int dupflg;
// check for being on the list -- works for both circular/non-circular
// NOTE: for circular, this can be reduced to:
// dupflg = (newptr->prev != NULL)
dupflg = (newptr == list->head) || (newptr->prev != NULL);
//dupflg = -dupflg;
do {
// we're trying to re-add the _same_ node -- we'd corrupt the list
if (dupflg)
break;
// only insert if we did _not_ find one
list_insert(list,newptr);
} while (0);
}
// list_insert_unique_value -- insert before list head [O(n)]
void
list_insert_unique_value(list_t *list,element_t *newptr)
{
int dupflg;
element_t *curptr;
int value;
curptr = list->head;
// check for being on the list -- works for both circular/non-circular
dupflg = (newptr == curptr) || (newptr->prev != NULL);
dupflg = -dupflg;
// search for pre-existing element with the same value
value = newptr->value;
for (; curptr != NULL; curptr = curptr->next) {
if (dupflg)
break;
dupflg = (curptr->value == value);
}
do {
// we're trying to re-add the _same_ node -- we'd corrupt the list
if (dupflg < 0)
break;
// we're trying to add a different node but with an existing value
if (dupflg) {
free(newptr);
break;
}
// only insert if we did _not_ find one
list_insert(list,newptr);
} while (0);
}
// list_print -- print the list
void
list_print(list_t *list)
{
element_t *curptr;
printf("list_print:");
for (curptr = list->head; curptr != NULL; curptr = curptr->next)
printf(" %d",curptr->value);
printf("\n");
}
// list_print -- print the list in reverse order
void
list_rprint(list_t *list)
{
element_t *curptr;
printf("list_rprint:");
for (curptr = list->tail; curptr != NULL; curptr = curptr->prev)
printf(" %d",curptr->value);
printf("\n");
}
// main -- main program
int
main(void)
{
list_t mylist;
element_t *e;
// initialize list
mylist.head = NULL;
mylist.tail = NULL;
// Add unique element to list
e = new_element(42);
list_insert_unique_value(&mylist,e);
// try to corrupt the list
list_insert_unique_pointer(&mylist,e);
e = new_element(23);
list_insert_unique_value(&mylist,e);
// try to corrupt the list
list_insert_unique_pointer(&mylist,e);
e = new_element(17);
list_insert_unique_value(&mylist,e);
// try to insert a duplicate
e = new_element(17);
list_insert_unique_value(&mylist,e);
// try to insert a duplicate
e = new_element(42);
list_insert_unique_value(&mylist,e);
// print list in order [checks the head/next pointers]
list_print(&mylist);
// print list in reverse order [checks the tail/prev pointers]
list_rprint(&mylist);
return 0;
}