【问题标题】:Linked Lists (C/C++). What are the advantages/disadvantages of creating both a list struct and a node struct?链表 (C/C++)。同时创建列表结构和节点结构的优点/缺点是什么?
【发布时间】:2021-01-18 22:45:48
【问题描述】:

我在网上看到了各种实现,有些只为其链表创建一个节点结构,而另一些则同时创建一个列表结构和一个节点结构,因此我想知道什么是最佳实践。

list 和 node_struct 实现示例:

typedef struct listnode listnode_t;
struct listnode {
    listnode_t *next;
    listnode_t *prev;
    void *elem;
};

struct list {
    listnode_t *head;
    listnode_t *tail;
    int size;
};

仅节点结构实现示例:

typedef struct node node_t;
struct node { 
    int data; 
    node_t *next; 
};
node_t *head;
node_t *tail;

【问题讨论】:

  • 这不是很明显吗?如果使用第二种方法,则很难在列表 API 中隐藏 headtailsize 之类的内容。
  • 好的。有两个结构有什么缺点吗?喜欢更多的内存或开销?
  • @DoeJ 不,使用 2 个结构没有内存开销。同样在这里选择一种语言,c 或 c++ 的答案可能会非常不同,因为它们是完全不同的语言。
  • 两个structs 本身并不多于或少于一个编译时效果——通常不太需要关心。如果您创建struct 然后 的实例(变量),它将成为运行时效果(可能)。因此,如果您不需要列表管理,请不要为列表创建变量。但是,您的应用程序中没有列表管理... ;-)
  • 为了公平比较,您应该展示具有相同功能的示例。您的带有列表结构的示例有一个头指针、一个尾指针和一个大小。这三件事在仅节点实现中是如何实现的? (如果在后一种实现中只实现了一个头指针,那么你已经有了答案。)

标签: c++ c list linked-list


【解决方案1】:

我想知道什么是最佳做法。

c++ 的惯用用法是在列表 class 内创建私有节点结构类型:

class list {
    struct listnode {
        listnode *next;
        listnode *prev;
        void *elem;
    };
    listnode *head;
    listnode *tail;
    int size;

public:
    // Some public operations here. 
    // listnode instances must be referred by clients using the auto keyword
    list::listnode* insert_after(list::listnode* prevnode, void* elem) {
        list:listnode newNode = new list:listnode();
        newNode->elem = elem;
        newNode->prev = prevnode;
        newNode->next = prevnode->next;
        newNode->next->prev = newNode;
        prevnode->next = newNode;
        return newNode;
    }
};

C++ 中真正的最佳实践就是使用标准库中的std::list<T>

【讨论】:

  • 你忽略了这个问题的 C 部分... ;-)
  • @Scheff 故意的,是的。
【解决方案2】:

我们在这里比较苹果和橙子。您展示的第一个示例是双链接列表。第二个例子是链表的节点表示。

但是,如果我们忽略这种不协调,最好将关注点分开。一方面,您尝试确保正确表示节点。另一方面,您尝试将这些节点用作列表。

如果您没有为列表实现结构并在某个问题空间解决您的问题,那么列表应该如何工作的一般性将与该问题的特殊性混淆,因此您将无法当您需要一个列表来解决其他问题时重用您的代码,并且您将一遍又一遍地重复您的列表实现。

此外,您将无法隐藏头部,并且不丢失头部的担忧将升级到使用您的节点的代码。

这会导致代码混乱,难以维护。您的目标应该是有一个节点结构和另一个列表。

【讨论】:

    【解决方案3】:

    对此的一些意见:

    1. 如果你有尾巴,你可以在 Θ(1) 的末尾插入。可能有用,具体取决于您想做什么。

    2. 如果你有尾巴,你可以继续写一个迭代器,允许你使用基于范围的循环。 (迭代器需要一个方法end())。

    3. 如果列表与元素的类型不同,则您具有内置的安全性。例如,假设您编写了一个排序算法。如果你只有节点,签名就像void sort(node* list)。这允许用户插入一个节点元素,该节点元素 不是 列表的头部,而是来自中间的元素。单独的类型将保证它始终是一个完整的列表。

    4. 您可以进一步使其更安全。现在,人们可以用下一个指针创建一个循环,因为一切都是空的。通常你会想要保证一个列表实际上是一个列表。为此,您将封装事物,其中一部分是为元素和列表提供单独的类型。这样做的好处是你可以说“如果你使用这个类,它总是一个正确的列表”。您不能在其他地方破坏它,这会产生一个可能难以修复的错误。

    5. 在您的示例中,第一个结构也是双链表。双链表有一些明显的附加属性,比如它需要更多内存但允许反向迭代和在另一个之前插入元素。

    【讨论】:

    • 对于循环单链表,只需要一个尾指针,因为tail->next是指向链表头的指针。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    相关资源
    最近更新 更多