C语言单链表核心操作:从创建到增删改查的完整实现
C语言的单链表是动态数据结构的基石,其核心在于灵活使用指针进行节点的连接与断开。本文将系统梳理单链表的关键操作,包括初始化、遍历统计、节点查询、删除、逆置、排序、去重以及有序插入。通过完整的模块化代码示例,展示如何构建一个具备基础交互功能的单链表程序,解决指针管理中的常见逻辑问题。
1. 准备工作:单链表结构定义
在实现具体操作前,首先需要定义单链表的结构体。通常包含两个部分:
data:用于存储数据元素,这里以int类型为例。next:指向下一个节点的指针。
#include <stdio.h>
#include <stdlib.h>
// 定义数据类型和节点结构
typedef int ElemType;
typedef struct Node {
ElemType data;
struct Node *next;
}LNode, *LinkList;
代码中使用了双重typedef,LNode代表节点类型,而LinkList代表指向头节点的指针类型,这在后续函数参数传递中能更直观地表达意图。
2. 初始化与创建链表
创建一个带头结点的链表有利于统一处理第一个节点和其他节点的操作(如插入和删除)。创建过程需要从输入中获取元素个数及数值,并依次申请内存建立链接。
void Build(LinkList L) {
int n;
LinkList p, q;
p = L; // p始终指向当前最后一个节点
printf("请输入n和n个元素:\n");
scanf("%d", &n);
while(n--) {
q = (LinkList)malloc(sizeof(LNode));
scanf("%d", &q->data);
q->next = NULL;
p->next = q; // 将新节点挂到末尾
p = q; // 移动尾指针
}
}
关键点:初始时p指向头节点,每次分配新节点q后,将其连接到p之后,然后让p移动到q的位置,从而保持p永远指向尾部。
3. 核心操作实现
第1步:遍历并输出链表长度
目的:验证链表是否正确建立并计算节点数量。注意需从头节点的下一个节点开始遍历,跳过头结点本身。
void LinkLength(LinkList L) {
int num = 0;
LinkList p;
p = L->next; // 跳过哨兵/头节点
while(p) {
num++;
printf("%3d", p->data);
p = p->next;
}
printf("长度为:%d", num);
}
第2步:查找指定值的前驱节点
目的:查找值为x的节点的前一个节点,常用于删除或插入前的定位。
void Find(LinkList L, int x) {
LinkList p;
p = L;
// 循环条件:还有后继节点 且 后继节点的值不是x
while(p->next && p->next->data != x)
p = p->next;
if(p->next)
printf("%d的前驱结点为%d", x, p->data);
else
printf("没有找到");
}
第3步:删除值为x的节点
目的:根据节点内容而非指针地址删除元素。同样需要找到目标节点的前驱才能安全释放内存。
void Delete(LinkList L, int x) {
LinkList p, q;
p = L;
while(p->next && p->next->data != x)
p = p->next;
if(p->next) {
q = p->next; // q为待删除节点
p->next = q->next; // 绕过q,直接连接前后
free(q); // 释放内存,防止泄漏
}
printf("删除成功!!");
}
第4步:单链表逆置
目的:将链表中的元素顺序反转。常用方法是将原链表断开,采用“头插法”重新插入一个新头节点后。
void Reverse(LinkList L) {
LinkList p, q;
p = q = L->next; // p,q均指向第一个实际节点
L->next = NULL; // 头节点指向空,准备重新挂载
while(p) {
q = q->next; // 暂存下一节点
p->next = L->next; // 将p插入到头节点之后
L->next = p; // 更新头节点指向
p = q; // 移动p继续处理
}
printf("逆置成功!!");
}
第5步:对链表进行排序
目的:将无序链表按从小到大排列。这里演示简单的冒泡排序思想,通过交换节点数据域来实现(无需改变指针连接,简化了操作)。
void sort(LinkList L) {
LinkList p, q;
int temp;
for(p = L->next; p->next != NULL; p = p->next) {
for(q = p->next; q != NULL; q = q->next) {
if(p->data > q->data) {
temp = p->data;
p->data = q->data;
q->data = temp;
}
}
}
printf("排序成功!");
}
第6步:删除重复元素
前提:此算法假设链表已经是升序排列的(若未排序,需先调用sort函数),只有相邻节点值相同时才判定为重复。
void Deletesameelem(LinkList L) {
LinkList p, q, s;
p = L;
q = L->next;
while(q->next) { // 注意判断条件是q->next存在
if(q->data == q->next->data) {
p->next = q->next; // 跳过q,连接到下下个
s = q;
q = q->next;
free(s); // 删除冗余节点
} else {
p = p->next;
q = q->next;
}
}
printf("删除成功!!");
}
第7步:有序插入新元素
目的:在一个已经有序的链表中插入一个新节点,并保持链表有序性。需要找到合适的插入位置(即第一个大于该值的节点之前)。
void Insert(LinkList L, LinkList p) {
LinkList s;
s = L;
// 寻找插入点:直到下一个节点值大于等于p的值,或到达末尾
while(s->next && s->next->data < p->data)
s = s->next;
p->next = s->next; // 新节点指向后继
s->next = p; // 前驱指向新节点
}
4. 程序交互与测试
为了便于测试上述功能,编写了一个基于控制台菜单的主函数,允许用户动态选择执行不同的操作。
// 菜单提示
void Tips() {
printf("\n根据按键选择相应的操作:\n");
printf("<1>输出单链表及其长度:\n");
printf("<2>查找值为x的直接前驱结点:\n");
printf("<3>删除值为x的结点:\n");
printf("<4>将表中的元素逆置:\n");
printf("<5>将单链表由小到大进行排序:\n");
printf("<6>删除表中相同的元素:\n");
printf("<7>在升序链表中插入元素x:\n");
printf("<0>退出:\n");
}
void main() {
int op, x;
LinkList L, p;
// 1. 初始化带头节点的空链表
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
L->data = -1;
// 2. 构建链表
Build(L);
Tips();
scanf("%d", &op);
while(op) {
switch(op) {
case 1: LinkLength(L); break;
case 2:
printf("请输入要查找的元素x:\n");
scanf("%d", &x);
Find(L, x); break;
case 3:
printf("请输入要删除的元素x:\n");
scanf("%d", &x);
Delete(L, x); break;
case 4: Reverse(L); break;
case 5: sort(L); break;
case 6: Deletesameelem(L); break;
case 7:
printf("请输入要插入的元素X:\n");
scanf("%d", &x);
p = (LinkList)malloc(sizeof(LNode));
p->data = x;
Insert(L, p);
printf("插入成功!!!\n\n"); break;
default:
break;
}
scanf("%d", &op); // 循环读取下一次操作
}
}
5. 总结与注意事项
单链表的实现难点主要集中在指针的指向关系上。在执行删除、插入等操作时,务必确保在断开指针前保存好必要的信息,特别是free()的使用时机,以避免内存泄漏。此外,带头节点的设计能显著简化边界条件的处理逻辑,如在对空表进行操作或首节点被修改时,无需特殊判断。以上就是C语言单链表常见操作的详细内容,更多关于数据结构与C编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













