我们什么时候应该使用链表而不是数组,反之亦然?

时间:2018-04-22 12:49:58

标签: c arrays vector linked-list

我从我教授的一个代码中得到了这些结构声明,我实际上想知道为什么我们应该使用链表而不是数组。我不知道这是一个愚蠢的问题,我只是对SO社区对此的看法感到好奇。

typedef struct Booking
{
  char restaurant[32];
  char customer[32];
  int  seats;
  int  period; // DAY o NIGHT
} TBooking;

typedef struct NodeBooking
{
  TBooking            booking;
  struct NodeBooking* next;
} TNodeBooking;

typedef TNodeBooking* PNodeBooking;

typedef struct BookingQueue
{
  PNodeBooking first;
  PNodeBooking last;
} TBookingQueue;

typedef struct Restaurant
{
  char              restaurant[32];
  int               n_booking_lunch;
  int               n_booking_dinner;
  TBookingsQueue bookings;
} TRestaurant;

typedef struct NodeRestaurant
{
  TRestaurant            restaurant;
  struct NodeRestaurant* next;
} TNodeRestaurant;

 typedef TNodeRestaurant* PNodeRestaurant;

2 个答案:

答案 0 :(得分:-3)

它有许多使用链表的功能,其中一些列在下面:

  • 遍历列表中的结构
  • 使用链接列表功能的好处,如圆形等。
  • 逃离数组索引绑定错误
  • 引用传递参数到方法,大数据可以用一个指针引用
  • 使用搜索算法
  • 插入并删除列表中的某个节点

等等。

当然你可以使用数组完成上述所有事情,但链接列表在结构化应用程序中是更好的方法。

答案 1 :(得分:-3)

如果您不经常修改数组(即删除和插入),并且您需要索引访问,则数组通常很好。然后,正如评论中所提到的,由于缓存局部性(并且没有指针间接),由于它们在内存中的顺序布局,数组在读取它们时会表现得更好,而链接列表在内存中被分段并且每个节点都有一个指针间接。考虑到这一点,每个数据结构在复杂性和用例方面都有其优缺点。

至于数组的一些缺点,让我们假设您有以下数组:

struct user_s *user_list[] = { &user1, &user2, &user3, &user4 };

想象一下这个列表包含更多元素。

  1. 如果要删除&user2会怎样?您必须在删除的元素之后移动所有内容以填充已删除的空间,这意味着复制内存中最差的N-1元素。

  2. 如果我们用完阵列的初始保留大小会怎样?你必须在内存中增加realloc个空间,并将所有内容移动到新分配的内存空间(realloc如何工作)。这也很贵。

  3. 虽然根据元素的类型,使用数组或链表可能更优,并且可以针对用例进行优化。

    当您拥有如下链接列表时,您不会遇到其中一些问题:

    struct user_s {
      char *name;
    
      struct user_s *next;
    }
    

    现在,您可以在最后添加新元素,而无需在容量已满时重新分配整个数组:

    struct user_s *user_add(struct user_s *tail, struct user_s *user)
    {
        assert(tail != NULL);
        assert(user != NULL);
    
        tail->next = user;
    
        return tail->next;
    }
    

    这有助于解决数组的2.问题。您可以无限期地附加到列表尾部而无需点击阵列容量,这需要为其重新分配更多缓冲区。但是这种重新分配可以通过提前缓冲内存空间来优化。

    从中间删除元素的1.问题更加容易:

    void user_remove(struct user_s *head, struct user_s *user)
    {
        struct user_s *it, *prev;
    
        assert(head != NULL);
        assert(user != NULL);
    
        for (prev = head, it = head->next; it != NULL; prev = it, it = it->next) {
            if (it == user) {
                prev->next = it->next;
                free(it);
                return;
            }
        }
    }
    

    这样,您可以从列表中间删除元素,并将上一个和下一个元素相互绑定。

    在数组中,为了删除元素,您必须创建一个新数组,复制元素直到要删除的元素,然后将元素复制到新分配的数组,然后返回。如果您必须经常删除元素,这是一个非常昂贵的操作。见https://ideone.com/bOSvpg

    在您的示例中,在结构中使用*next的另一个原因是,在实现涉及诸如树的大量其他数据结构时,您几乎总是需要指针。