目录
第二节:单链表
链式存储结构
逻辑上相邻接的数据元素的存储地址不一定相邻接, 相邻关系通过指针来描述, 这种存储结构称为链式存储结构。
单链表结点结构
单链表每个节点包含两部分:数据域和指针域。
数据域用于存储实际的数据,指针域用于指向下一个节点,通过这种方式将各个节点连接成一个线性序列。
一、创建单链表
代码示例:
//创建链表
typedef struct Node
{
//数据域
ElementType data;
//指针域
struct Node* next;
}Node;
二、 单链表初始化
在单链表中,头节点的数据域通常不存储实际的数据。
代码示例:
//链表初始化
Node* InitList()
{
//头节点堆内存分配
Node* head = (Node*)malloc(sizeof(Node));
//头节点数据域初始化为0
head->data = 0;
//头节点指针域初始化为空
head->next = NULL;
//返回头节点
return head;
}
(Node*)
是强制类型转换,将 malloc
返回的 void*
类型指针转换为 Node*
类型指针。
三、头插法
头插法是指在链表的头节点之后插入一个新节点,使得新节点成为链表的第一个有效节点。
代码示例:
/头插法
int insertHead(Node* head, ElementType e)
{
//新节点申请堆内存分配
Node* p = (Node*)malloc(sizeof(Node));
//新节点数据域初始化为e
p->data = e;
//新节点指针域初始化为头节点的下一个节点
p->next = head->next;
//头节点指向新节点
head->next = p;
return 1;
}
四、尾插法
尾插法是指将新节点插入到链表的尾部。与头插法不同,尾插法插入节点后,链表中节点的顺序与插入顺序一致。在进行尾插操作时,需要先找到链表的尾节点,然后将新节点连接到尾节点之后,并更新尾节点为新插入的节点。
代码示例:
//尾插法
int insertTail(Node* head, ElementType e)
{
//遍历链表,找到最后一个节点
Node* p = head;
while (p->next != NULL)
{
p = p->next;
}
//新节点申请堆内存分配
Node* p_new = (Node*)malloc(sizeof(Node));
//新节点数据域初始化为e
p_new->data = e;
//新节点指针域初始化为空
p_new->next = NULL;
//当前最后一个节点指向新节点
p->next = p_new;
return 1;
}
五、单链表遍历
代码功能概述:接收一个指向单链表头节点的指针作为参数,然后根据链表的状态进行不同处理,如果链表为空,会输出提示信息,如果链表不为空,则依次输出链表中每个节点的数据域。
代码示例:
//单链表遍历
void TraverseList(Node* head)
{
//头节点的下一个节点为空,则链表为空
if (head->next == NULL)
{
printf("链表为空\n");
return;
}
//头节点的下一个节点不为空,则链表不为空
else
{
//当前节点为头节点的下一个节点
Node* p = head->next;
//遍历链表
while (p != NULL)
{
//输出当前节点的数据域
printf("%d ", p->data);
//当前节点指向下一个节点
p = p->next;
}
printf("\n");
}
}
六、指定位置插入新节点
遍历链表,找到指定位置的前一个节点。将数据存入新节点中,上一节点指向新节点,新节点指向上一节点指向的下一节点。
过程示意:
代码示例:
//指定位置插入节点
int insertNode(Node* head, int pos, ElementType e)
{
if (pos < 1)
{
printf("插入位置错误\n");
return 0;
}
else if (pos == 1)
{
//头插法
insertHead(head, e);
return 1;
}
else
{
//遍历链表,找到指定位置的前一个节点
Node* p = head;
for (int i = 1; i < pos - 1; i++)
{
if (p->next == NULL)
{
printf("插入位置错误\n");
return 0;
}
p = p->next;
}
//新节点申请堆内存分配
Node* p_new = (Node*)malloc(sizeof(Node));
//新节点数据域初始化为e
p_new->data = e;
//新节点指针域初始化为当前位置的下一个节点
p_new->next = p->next;
//当前位置的下一个节点指向新节点
p->next = p_new;
return 1;
}
}
七、删除指定位置的节点
思想:第一步,找到指定位置的前一个节点:从链表头节点开始遍历,找到要删除节点的前一个节点。第二步,将前一个节点的 next
指针指向要删除节点的下一个节点,然后释放要删除节点的内存。
代码示例:
//删除指定位置节点
int deleteNode(Node* head, int pos)
{
if (pos < 1)
{
printf("删除位置错误\n");
return 0;
}
else if (pos == 1)
{
//头节点指向下一个节点
head->next = head->next->next;
return 1;
}
else
{
//遍历链表,找到指定位置的前一个节点
Node* p = head;
for (int i = 1; i < pos - 1; i++)
{
if (p->next == NULL)
{
printf("删除位置错误\n");
return 0;
}
p = p->next;
}
if (p->next == NULL)
{
printf("删除位置错误\n");
return 0;
}
p->next = p->next->next;
return 1;
printf("删除成功\n");
return 1;
}
}
八、获取表长的方法
遍历到尾节点即可
代码示例:
//获取单链表的长度
int getLength(Node* head)
{
//头节点的下一个节点为空,则链表为空
if (head->next == NULL)
{
return 0;
}
//头节点的下一个节点不为空,则链表不为空
else
{
//当前节点为头节点的下一个节点
Node* p = head->next;
//链表长度初始化为0
int length = 0;
//遍历链表
while (p != NULL)
{
//链表长度加1
length++;
//当前节点指向下一个节点
p = p->next;
}
return length;
}
}
九、释放链表
代码示例:
//释放链表
void FreeList(Node* head)
{
//头节点的下一个节点为空,则链表为空
if (head->next == NULL)
printf("链表为空\n");
//头节点的下一个节点不为空,则链表不为空
else
{
//当前节点为头节点的下一个节点
Node* p = head->next;
//遍历链表
while (p != NULL)
{
//当前节点指向下一个节点
Node* q = p->next;
//释放当前节点
free(p);
//当前节点指向下一个节点
p = q;
}
//头节点指向空
head->next = NULL;
printf("链表释放成功\n");
}
}
十、调用演示示例
代码示例:
int main()
{
//初始化链表
Node* list = InitList();
printf("链表初始化成功\n");
printf("链表长度为%d\n", getLength(list));
//头插法
insertHead(list, 1);
insertHead(list, 2);
insertHead(list, 3);
//遍历链表
TraverseList(list);
printf("链表长度为%d\n", getLength(list));
//尾插法
insertTail(list, 5);
insertTail(list, 6);
insertTail(list, 7);
//遍历链表
TraverseList(list);
printf("链表长度为%d\n", getLength(list));
//指定位置插入节点
insertNode(list, 2, 3);
//遍历链表
TraverseList(list);
printf("链表长度为%d\n", getLength(list));
//删除指定位置节点
deleteNode(list, 2);
//遍历链表
TraverseList(list);
printf("链表长度为%d\n", getLength(list));
return 0;
}
运行结果: