链表是C语言中的一种基本数据结构。
对于C程序员来说,了解链表是必要的。
链表是一种动态数据结构,其长度可以在运行时增加或减少。
链表与数组有什么不同?考虑以下几点:
- 数组是一种静态数据结构。这意味着数组的长度在运行时无法改变,而链表是一种动态数据结构。
- 在数组中,所有元素都保持在连续的内存位置上,而在链表中,元素(或节点)可以位于任何位置,但仍然相互连接。
何时优先选择链表而不是数组?在不知道要存储的数据量时,通常首选链表。例如,在员工管理系统中,无法使用数组,因为数组长度是固定的,而任意数量的新员工可以加入。在这种情况下,使用链表(或其他动态数据结构),因为它们的容量可以在运行时根据需要增加(或减少)。
链表在内存中是如何排列的?
链表基本上由位于随机内存位置的内存块组成。现在,有人可能会问它们是如何连接或如何遍历的?嗯,它们是通过指针连接起来的。通常,链表中的一个块通过以下结构表示:
struct test_struct
{
int val;
struct test_struct *next;
};
正如你在这里所看到的,这个结构体包含一个值 val 和一个指向相同类型结构体的指针。值 val 可以是任意值(取决于链表所存储的数据),而指针 next 包含了链表中下一个块的地址。因此,通过这些包含下一个节点地址的 next 指针,实现了链表的遍历。最后一个节点(或者只有一个节点的链表)的 next 指针将包含一个 NULL 值。
节点是如何创建的?
节点是通过以下方式将内存分配给一个结构(如上文所示)来创建的:
struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct));
这个结构体包含一个值 val 和一个指向相同类型结构体的指针。值 val 可以是任意值(取决于链表所存储的数据),而指针 next 包含了链表中下一个块的地址。因此,通过这些包含下一个节点地址的 next 指针,实现了链表的遍历。最后一个节点(或者只有一个节点的链表)的 next 指针将包含一个 NULL 值。
...
...
ptr->val = val;
ptr->next = NULL;
...
...
搜索节点
搜索一个节点意味着查找包含要搜索的值的节点。实际上,如果我们谈论线性搜索的话,这是一个非常简单的任务(请注意,还有许多搜索算法)。只需从第一个节点开始,然后将要搜索的值与该节点中包含的值进行比较。如果值不匹配,则通过“next”指针(它包含下一个节点的地址)访问下一个节点,并在那里进行相同的值比较。搜索会继续进行,直到访问到最后一个节点,或者找到了值等于要搜索的值的节点。以下是一个代码片段示例:
...
...
...
while(ptr != NULL)
{
if(ptr->val == val)
{
found = true;
break;
}
else
{
ptr = ptr->next;
}
}
...
...
...
删除节点
节点的删除通过首先在链表中找到该节点,然后在包含其地址的指针上调用free()函数来实现。如果被删除的节点不是第一个或最后一个节点,那么需要将被删除节点之前的节点的“next”指针指向被删除节点之后的节点的地址。就像如果一个人从人链中脱离出来,那么这个人之前的两个人需要重新握手来保持链条的完整。
一个实际的C链表示例
这里有一个实际的示例,它创建一个链表,向其添加一些节点,然后在链表中搜索和删除节点
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
struct test_struct
{
int val;
struct test_struct *next;
};
struct test_struct *head = NULL;
struct test_struct *curr = NULL;
struct test_struct* create_list(int val)
{
printf("\n creating list with headnode as [%d]\n",val);
struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct));
if(NULL == ptr)
{
printf("\n Node creation failed \n");
return NULL;
}
ptr->val = val;
ptr->next = NULL;
head = curr = ptr;
return ptr;
}
struct test_struct* add_to_list(int val, bool add_to_end)
{
if(NULL == head)
{
return (create_list(val));
}
if(add_to_end)
printf("\n Adding node to end of list with value [%d]\n",val);
else
printf("\n Adding node to beginning of list with value [%d]\n",val);
struct test_struct *ptr = (struct test_struct*)malloc(sizeof(struct test_struct));
if(NULL == ptr)
{
printf("\n Node creation failed \n");
return NULL;
}
ptr->val = val;
ptr->next = NULL;
if(add_to_end)
{
curr->next = ptr;
curr = ptr;
}
else
{
ptr->next = head;
head = ptr;
}
return ptr;
}
struct test_struct* search_in_list(int val, struct test_struct **prev)
{
struct test_struct *ptr = head;
struct test_struct *tmp = NULL;
bool found = false;
printf("\n Searching the list for value [%d] \n",val);
while(ptr != NULL)
{
if(ptr->val == val)
{
found = true;
break;
}
else
{
tmp = ptr;
ptr = ptr->next;
}
}
if(true == found)
{
if(prev)
*prev = tmp;
return ptr;
}
else
{
return NULL;
}
}
int delete_from_list(int val)
{
struct test_struct *prev = NULL;
struct test_struct *del = NULL;
printf("\n Deleting value [%d] from list\n",val);
del = search_in_list(val,&prev);
if(del == NULL)
{
return -1;
}
else
{
if(prev != NULL)
prev->next = del->next;
if(del == curr)
{
curr = prev;
}
else if(del == head)
{
head = del->next;
}
}
free(del);
del = NULL;
return 0;
}
void print_list(void)
{
struct test_struct *ptr = head;
printf("\n -------Printing list Start------- \n");
while(ptr != NULL)
{
printf("\n [%d] \n",ptr->val);
ptr = ptr->next;
}
printf("\n -------Printing list End------- \n");
return;
}
int main(void)
{
int i = 0, ret = 0;
struct test_struct *ptr = NULL;
print_list();
for(i = 5; i<10; i++)
add_to_list(i,true);
print_list();
for(i = 4; i>0; i--)
add_to_list(i,false);
print_list();
for(i = 1; i<10; i += 4)
{
ptr = search_in_list(i, NULL);
if(NULL == ptr)
{
printf("\n Search [val = %d] failed, no such element found\n",i);
}
else
{
printf("\n Search passed [val = %d]\n",ptr->val);
}
print_list();
ret = delete_from_list(i);
if(ret != 0)
{
printf("\n delete [val = %d] failed, no such element found\n",i);
}
else
{
printf("\n delete [val = %d] passed \n",i);
}
print_list();
}
return 0;
}
在上面的代码中:
第一个节点始终通过一个全局的“head”指针来访问。当第一个节点被删除时,该指针会被调整。
类似地,还有一个“curr”指针,它包含链表中的最后一个节点。当最后一个节点被删除时,该指针也会被调整。
每当向链表中添加一个节点时,都会检查链表是否为空,如果为空,则将其作为第一个节点添加。