链表是一种常见的数据结构,其特点主要有以下几个方面:
- 动态分配内存:链表不需要预先分配固定大小的内存空间,而是可以在需要时动态地分配和释放。这使得链表在处理大小可变的数据集时非常灵活。
- 非连续性存储:链表中的元素通过指针连接在一起,而不是像数组那样在内存中连续存储。因此,链表中的元素可以分散在内存的任何位置。
- 插入和删除操作的高效性:在链表的任何位置插入或删除元素都只需要修改相邻节点的指针,而不需要移动大量元素。这使得链表在插入和删除操作上的时间复杂度低,特别是在链表的开头或结尾操作时。
- 访问元素的不便性:虽然链表在插入和删除操作上表现出色,但在访问特定元素时却相对不便。为了访问链表中的某个元素,通常需要从头节点开始,沿着指针逐个遍历节点,直到找到目标元素。这使得链表在访问操作上的时间复杂度较高,特别是当链表较长时。
- 空间开销:链表中的每个节点除了存储数据元素外,还需要存储指向下一个节点的指针,这增加了链表的空间开销,特别是在存储小数据元素时。然而,这种开销通常是可以接受的,因为链表提供了更高的灵活性和更高效的插入和删除操作。
- 循环链表和双向链表:链表有多种类型,包括单向链表、双向链表和循环链表等。这些不同类型的链表具有不同的特点和用途。例如,双向链表允许向前和向后遍历节点,而循环链表则允许从从尾节点直接访问头节点。
以下是关于链表的简单使用:
//定义链表结构体
typedef struct _Node{
int data;
struct _Node* next;
}Node;
//创建一个新节点
Node* CreateNode(int data)
{
Node *newNode = (Node *)malloc(sizeof(Node));
if(newNode == NULL)
{
printf(memory allocation failed.\n);
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
//在链表头部插入节点
void InsertAtHead(Node **head, int data)
{
Node *newNode = CreateNode(data);
newNode->next = *head;
*head = newNode;
}
//在链表末尾插入节点
/* 1.检查头结点是否为空,如果为空,新节点成为链表的第一个节点,则头节点指针应该指向这个新节点 */
/* 2.遍历到链表的末尾:如果链表不为空,需要遍历整个链表找到最后一个节点 */
/* 3.更新尾节点的next指针:一旦找到了尾节点,就需要将其next指针指向新节点,这样新节点就成为了链表的最后一个节点 */
/* 4.确保新节点的next指针为NULL */
void InsertAtTail(Node **head, int data)
{
Node *newNode = CreateNode(data);
Node *temp = *head;
if(temp == NULL)
{
temp = newNode;
*head = temp;
}
else
{
while(temp->next != NULL)
{
temp = temp->next;
}
temp->next = newNode;
}
}
//在链表的任意位置插入新节点
void InsertAtPos(Node **head, int data, int position)
{
Node *prev = NULL;
Node *newNode = CreateNode(data);
/* 如果链表为空,或者插入位置为0,则新节点成为头节点 */
if((*head == NULL) || (position == 0))
{
newNode->next = *head;
*head = newNode;
}
else
{
/* 找到要插入位置的前一个节点 */
prev = *head;
for(int i = 0; prev != NULL && i < position - 1; i++)
{
prev = prev->next;
if(prev == NULL)
{
return;
}
}
/* 插入新节点 */
newNode->next = prev->next;
prev->next = newNode;
}
}
//删除指定数据的节点
void DeleteNode(Node **head, int data)
{
Node *temp = *head;
Node *prev = NULL;
/* 判断删除的节点是否为头节点 */
if((temp != NULL) && (temp->data == data))
{
printf("删除的为头节点.\n");
*head = temp->next;
free(temp);
temp = *head;
}
/* 查找要删除的节点,并记录前一个节点 */
while(temp != NULL)
{
/* 如果没有找到要删除的节点,则移动到下一个节点 */
if(temp->data != data)
{
prev = temp;
temp = temp->next;
}
else /* 如果找到了要删除的节点 */
{
printf("已删除对应节点.\n");
/* 更新前一个节点的next指针以跳过当前节点 */
prev->next = temp->next;
/* 释放当前节点的内存 */
free(temp);
/* 移动到下一个节点 */
temp = prev->next;
}
}
}
//遍历链表并打印节点数据
void PrintList(Node *head)
{
Node *temp = head;
while(temp->next != NULL)
{
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
//释放链表内存
void FreeList(Node *head)
{
Node *temp;
whie(head != NULL)
{
temp = head;
head = head->next;
free(temp);
}
}