C 没有自指结构的帮助,是否可以建立一个链表?

C 没有自指结构的帮助,是否可以建立一个链表?,c,data-structures,singly-linked-list,C,Data Structures,Singly Linked List,没有自指结构的帮助,是否可以建立一个链表?也就是说,像这样使用指针: struct list{ int data; int *nxt; }; 而不是 struct list{ int data; struct list *nxt; }; 您可以了解一下链表是如何在Linux内核中实现的 与经典链表相比: struct my_list{ void *myitem; struct my_list *next; struct my_list

没有自指结构的帮助,是否可以建立一个链表?也就是说,像这样使用指针:

struct list{
    int data;
    int *nxt;
};
而不是

struct list{
    int data;
    struct list *nxt;
};

您可以了解一下链表是如何在Linux内核中实现的

与经典链表相比:

struct my_list{
    void *myitem;
    struct my_list *next;
    struct my_list *prev;
};
在linux内核中使用链表如下所示:

struct my_cool_list{
    struct list_head list; /* kernel's list structure */
    int my_cool_data;
};
是的,这是可能的


您的建议是,在大多数平台上的大多数编译器中,您可能都不会受到影响,但首先没有一个好的理由这么做,也有很多好的理由不这么做。

当然。您可以使用
void*
而不是
struct list*
作为
nxt
成员。例如:

struct list {
    int data;
    void *nxt;
};
或者,如果您准备构建一个保证列表的节点将被存储到一个数组中(这对于缓存位置和插入速度来说是非常好的),那么您可以使用
大小\u t

struct list {
     int data;
     size_t nxt;
};
如果您的实现提供了
uintpttr\t
(来自
),则可以将这两种类型合并为一种:

struct list {
    int data;
    uintptr_t nxt;
};

另一种方法是使用并行数组作为“堆”1,一个数组包含数据(无论是标量还是结构),另一个数组存储列表中下一个“节点”的索引,如:

int data[N];
int next[N];
您需要两个“指针”——一个指向列表中的第一个元素,另一个指向“堆”中第一个可用的“节点”

将“next”数组中的所有元素初始化为“指向”下一个元素,但最后一个指向“null”的元素除外:

这种方法的优点:

  • 没有动态内存管理
  • 没有自指结构
  • 数据项在内存中连续存储,这可能会导致更好的缓存性能
  • 缺点:

  • 在这种简单的方法中,列表大小是固定的
  • 任何有用的大型数组都必须声明为
    静态
    ,这意味着您的二进制文件可能占用了大量内存
  • -1
    用作“null”的约定意味着您必须为索引使用有符号类型,这会减少给定数组类型的可用元素数 您当然可以使用
    0
    作为“null”,并从
    1
    中计算所有索引;这允许您在索引中使用无符号类型,如
    size\t
    ,这意味着您可以使数组尽可能大。只是
    0
    是一个有效的数组索引,而
    -1
    (通常)不是,这就是我在本例中选择它的原因



    1.欢迎来到我的数据结构课程,约1987年,该课程使用Fortran 77进行教学。并行数组几乎可以解决所有问题(列表、树、队列等)。

    nxt
    必须指向列表元素,而不是
    int
    。没有可移植的方法使
    int*nxt
    声明工作。有很多方法可以做到这一点,但它们都是黑客行为,会限制代码的可移植性、可读性和可靠性,而没有任何好处。这样做有什么意义?也许如果你解释了你真正想要达到的目标,那么某人可能会有一个好的建议。我认为关键部分是
    ,没有自我参照结构的帮助。可能,通过
    int*nxt
    OP的意思是
    *nxt@chatraed我知道OP想要什么。问题不是“什么”,而是“为什么”。@chatraed这是一个理由,也很公平。但这种情况下的原因应该来自OP,而不是其他人。重点是试图理解OP想要实现什么,以便给出一个好的答案。你给出的原因可能是,也可能不是OP的意图。但列表头不是一个自我参照结构吗?您所做的只是在头文件中隐藏循环。@rici:
    struct list\u head
    struct my\u cool\u list
    完全无关。我不明白,连接在哪里。自我指涉意味着它指的是它自己。看起来,结构列表头包含一个指向结构列表头的成员指针,使其具有自引用性。@rici:是的,显然各种
    结构列表头
    是相互关联的。然而,保存有用数据的结构与其成员之间失去了循环性和自引用性。从技术上讲,这不是自引用的,因此这是对这个问题的恰当回答+1,以抵消愚蠢的-1,它似乎没有任何理由。如果您将OP锁定为单一形式的存储持续时间,我不确定我是否同意“无动态内存管理”是一种优势。@undefinedbehaviour:这个示例是故意简化的。显然,您可以动态分配数组,甚至根据需要扩展它们(只需调整空闲列表以适应新元素)。但您不需要管理单个节点的内存。如果你知道你的列表会很小(大约几千字节),为什么还要用动态内存呢?为什么要把列表的节点存储在数组中,因为每个列表节点都知道它的邻居呢?除了更好的缓存位置,你可以通过分配(或以其他方式声明)一次有多个节点。需要指向
    nxt
    项的指针,以便在不移动每个项的情况下重新排列列表。是否有一个开源软件将其付诸实践?我不知道有什么现成的,但我确信有……毕竟,这是我从一本算法书中学到的一个概念。
    int head;
    int free;
    
    for ( int i = 1; i < N-1; i++ )
      next[i-1] = i;
    next[N-1] = -1;
    free = 0;  // first available "node" in the heap
    head = -1; // list is empty, head is "null" 
    
    if ( free != -1 )
    {
      int idx = free;    // get next available "node" index;
      free = next[free]; // update the free pointer 
      data[idx] = newValue;
      next[idx] = -1;
    
      if ( head == -1 )
      {
        head = idx;
      }
      else if ( data[idx] < data[head] )
      {
        next[idx] = head;
        head = idx;
      }
      else
      {
        int cur = head;    
        while ( next[cur] != -1 && data[idx] >= data[next[cur]] )
          cur = next[cur];
        next[idx] = next[cur];
        next[cur] = idx;
      }
    }
    
    int cur = head;
    while ( next[cur] != idx && next[cur] != -1 )
      cur = next[cur];
    
    if ( next[cur] == idx )
    {
      next[cur] = next[idx];  // point to the element following idx
      next[idx] = free;       // add idx to the head of the free list
      free = idx;             
    }