我从我教授的一个代码中得到了这些结构声明,我实际上想知道为什么我们应该使用链表而不是数组。我不知道这是一个愚蠢的问题,我只是对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;
答案 0 :(得分:-3)
它有许多使用链表的功能,其中一些列在下面:
等等。
当然你可以使用数组完成上述所有事情,但链接列表在结构化应用程序中是更好的方法。
答案 1 :(得分:-3)
如果您不经常修改数组(即删除和插入),并且您需要索引访问,则数组通常很好。然后,正如评论中所提到的,由于缓存局部性(并且没有指针间接),由于它们在内存中的顺序布局,数组在读取它们时会表现得更好,而链接列表在内存中被分段并且每个节点都有一个指针间接。考虑到这一点,每个数据结构在复杂性和用例方面都有其优缺点。
至于数组的一些缺点,让我们假设您有以下数组:
struct user_s *user_list[] = { &user1, &user2, &user3, &user4 };
想象一下这个列表包含更多元素。
如果要删除&user2
会怎样?您必须在删除的元素之后移动所有内容以填充已删除的空间,这意味着复制内存中最差的N-1
元素。
如果我们用完阵列的初始保留大小会怎样?你必须在内存中增加realloc
个空间,并将所有内容移动到新分配的内存空间(realloc
如何工作)。这也很贵。
虽然根据元素的类型,使用数组或链表可能更优,并且可以针对用例进行优化。
当您拥有如下链接列表时,您不会遇到其中一些问题:
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
的另一个原因是,在实现涉及诸如树的大量其他数据结构时,您几乎总是需要指针。