1.单链表结点的结构定义
typedef struct LNode
{
ElemType data;//结点数据域
struct Node* next;//结点指针域
}LNode, * LinkList;
//注意这里的LinkList为自定义的结构体指针类型,与LNode*等价,最好用来定义头指针
1.注意点:
1. LNode:为这个自定义结构体类型别名,用LNode*来定义链表中的每个结点指针
2. LinkList:为这个自定义结构体指针类型别名,可以用来定义结点指针,也可以用来定义链表(头指针)
LinkList p;
LNode* p;//定义结点指针p:两种写法完全等价,但是推荐使用这种写法,以提高代码可读性;
LinkList L;//定义链表L(头指针):两种写法完全等价,但是推荐使用这种写法,以提高代码可读性;
LNode* L;
1.分析如下:
LinkList p; 和LNode* p; 等价
LinkList L; 和LNode* L; 等价
本质上都是定义一个指向这种结点结构的结构体指针(当然,这里并没有为其赋值)
- 当然为了代码的可读性
- 定义整个链表的时候:需要定义一个指向头结点的头指针L,来表示整个链表; 最好使用LinkList L;
- 定义某个结点指针的时候:最好使用LNode* p;
2.实例: 存储学生学号,姓名,成绩的单链表结点的结构定义
typedef struct {
char num[8];//数据域的学号部分
char name[8];//数据域的姓名部分
int score//数据域的分数部分
}ElemType;
typedef struct LNode
{
ElemType data;//结点数据域
struct Node* next;//结点指针域
}LNode, * LinkList;
2.单链表的初始化(带头结点的单链表)
注意:这里需要修改的是主函数中头指针的值,所以要用二级指针来修改
InitLinkList(LinkList* L)
1.空表:链表中无元素,称为空链表(头结点和头指针仍然存在)
2.算法步骤:
- (1) 生成新结点作头结点 并使 (头指针L)指向头结点
- (2) 将头结点的指针域置空
//2.初始化单链表(实际上在初始化头结点)
//这里修改了主函数中头指针的值,所以要传入二级指针
bool InitLinkList(LinkList* L)
{
//[1](解引用传入的指向头指针L的二级指针)使头指针L指向新开辟出来的头结点空间
*L = (LNode*)malloc(sizeof(LNode));//为头结点开辟一个结点大小的空间
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->data = 0;//这里我存储当前链表的长度为0
//[2]置空头结点的指针域
(*L)->next = NULL;//头结点的指针域为空
return true;
}
int main()
{
LinkList L;//定义头指针L
InitLinkList(&L);//初始化头结点,传入 指向头指针 的指针
return 0;
}
3.判断单链表是否为空
LinkListEmpty(LinkList L)
1.算法思路:判断头结点指针域是否为空
//3.判断单链表是否为空
bool LinkListEmpty(LinkList L)
{
//[0]判断单链表头结点是否存在
//若头结点不存在,也认为是空表
if (L == NULL)
{
return true;
}
//[1]若头结点指针域为空,返回true,证明是空表
if (L->next == NULL)
{
return true;
}
//否则返回false
else
{
return false;
}
}
4.单链表的销毁(销毁后所有元素,头结点,头指针均不存在)
注意:这里在遍历过程中改变了主函数中头指针的值,所以要传入二级指针
DestroyLinkList(LinkList* L)
1.算法思路:从头指针开始,依次释放所有结点
2.算法步骤:
- (1)定义临时指针p,用以销毁结点空间
- (2)分别用临时指针p和 头指针*L先后遍历单链表,并销毁对应的结点
//4.单链表的销毁(链表销毁后不存在;所有元素,头结点,头指针均被销毁)
//这里在遍历过程中改变了主函数中头指针的值,所以要传入二级指针
bool DestroyLinkList(LinkList* L)
{
//[0]判断单链表头结点是否存在
if (*L == NULL)
{
return false;
}
//[1]定义临时指针p,p用来销毁空间的
LNode* p ;
//[2]依次销毁所有结点
while (*L != NULL)
{
p = *L;//开始:p指向头结点 终止:p指向最后一个结点
(*L) = (*L)->next;//开始:头指针*L指向首元结点 终止:头指针*L指向最后一个结点的指针域NULL
free(p);//开始:销毁头结点 终止:销毁最后一个结点
}
//[3]更新头指针,避免悬挂指针问题
*L=NULL;
return true;
}
3.循环分析:
循环的起始条件是p指向头结点
循环的终止条件是p指向最后一个结点(也就是头指针*L最后指向NULL)
5.单链表的清空(清空后所有元素不存在,保留头结点和头指针)
注意:与销毁操作类似,但保留头结点和头指针;且 只是修改头结点的 next域,并未修改头指针的值,所以无需使用二级指针。
ClearLinkList(LinkList L)
1.算法思路:依次释放所有结点,并将头结点指针域设置为空
2.算法步骤:
- (1)定义临时指针p,用以从首元结点开始销毁结点空间;定义临时指针q,用以记录每个待销毁的结点空间
- 所以q永远指向p的前一个结点,直到q指向尾结点的指针域------NULL
- (2)初始化p指向首元结点,q指向首元结点的下一个结点
- (3)进入循环,依次销毁除头结点外的所有结点
- (4)将头结点指针域置空
//5.单链表的清空
//此操作不需要修改头指针本身的指向,
// 只是修改头结点的 next 指针,所以无需使用二级指针。
bool ClearLinkList(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode *p, *q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
p = L->next;
//[3]依次销毁除头结点外的所有结点
while (p!= NULL)//注意这里不能写(q!=NULL),因为在这条语句之前q并未初始化(是一个野指针)
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//[4]清空后,将头结点的指针域置为空
L->next = NULL;
return true;
}
3.循环分析:
- 在开始循环之前,已经初始化p指向第一个要被销毁的结点(首元节点)
- 当
while (p != NULL)
条件为 false 时,表示循环终止,此时p,q均为NULL- 循环的起始条件是p指向首元结点,q指向首元结点的下一个结点
- 循环的终止条件是p,q均指向最后一个结点的指针域(NULL)
6.求单链表的表长
int LinkListLength(LinkList L)
1.算法思路:从首元结点开始,依次计数所有结点
2.算法步骤:
- (1)定义一个临时指针p,并让其指向首元节点
- (2)初始化计数器为0,因为没有排除空链表的可能
- (3)循环遍历,统计结点数
//6.求单链表的表长
//返回L中的元素个数
int LinkListLength(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1]定义一个临时指针p,并让其指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为0,因为没有排除空链表的可能
int i = 0;
//[3]循环遍历,统计结点数
while(p!=NULL)//若为空,直接返回i=0,不会进入循环
{
i++;//进入循环,证明存在首元结点,故先叠加
p = p->next;//当p指向空,计数器已经将表长记录成功
}
return i;
}
以上为几个较简单的算法实现,重要总结如下:
7.单链表的按位查找:取单链表中第i个元素的内容
GetElemLinkList(LinkList L, int i, ElemType* e)
1.辨析:
顺序表:直接返回元素位序为i即数组角标为i-1的元素 | 单链表:顺序查找元素位序为i的结点并返回 |
---|---|
时间复杂度为O(1) | 时间复杂度为O(n) |
数据随机存取 | 数据顺序存取 |
2.算法思路:从链表的首元结点开始,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止,
3.算法步骤:
- (1)定义临时指针p,并初始化p指向首元结点
- (2)定义计数器j,并初始化j=1;
- (3)循环查找第ⅰ个结点
- ①当 p指向扫描到的下一结点时,计数器j加1。
- ②当j==i时,p所指的结点就是要找的第ⅰ个结点。
//7.单链表的按位查找
//取单链表中第i个元素的内容
bool GetElemLinkList(LinkList L, int i, ElemType* e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化p指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int j = 1;
//[3]查找
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证i大于链表长度时 在p指向尾结点next时终止循环
//j<i:
//正常情况下:循环终止时p指向第i个结点
while (p!=NULL&&j < i)
{
p = p->next;//移动到下一个结点
j++;//计数器叠加
}
//[4]异常判断
//p == NULL:
//1.首元结点不存在的情况
//2.i大于链表长度的情况
//j>i:
//i<1的情况
if(p == NULL || j > i)
{
return false;
}
//[5]获取第i个结点的值
*e = p ->data;
return true;
}
4.循环分析及异常判断:
1.循环分析:
- (1)p!=NULL:
- 【1】保证首元结点不存在时,不进入循环
- 【2】保证i大于链表长度时 在p指向尾结点next(NULL)时终止循环
- (2)j < i:
- 【1】保证正常情况下:循环终止时p指向第i个结点
2.异常判断:
- (1)p == NULL:
- 【1】首元结点不存在的情况,此时并未进入循环
- 【2】i大于链表长度的情况,虽然进入了循环,但是链表中没有相应的结点
- (2) j > i:
- 用户输入的i<1的情况,不会进入循环,需要异常处理
8.单链表的按值查找:根据指定数据获取该数据所在地址或位序
算法1 返回地址:查找与元素 e 相同的元素并返回其在单链表中的地址
LNode* LocateElemLinkList(LinkList L, ElemType e)
算法2 返回位序:查找与元素 e 相同的元素并返回其在单链表中的位序
int LocateElemLinkList1(LinkList L, ElemType e)
1.算法步骤:
- (1) 从第一个结点起,依次和e相比较。
- (2)如果找到一个其值与e相等的数据元素,则返回其在链表中的地址或位序
- (3)如果查遍整个链表都没有找到其值和e相等的元素,则返回NULL或"0"
返回地址:
//8.单链表的按值查找
// 查找与元素 e 相同的元素并返回其在单链表中的地址
LNode* LocateElemLinkList(LinkList L, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return NULL;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时终止循环
//p->data!=e:
// 保证循环终止时p指向与元素e相同的结点
while (p != NULL && p->data != e)
{
p = p->next; // 移动到下一个结点
}
//[3]查找成功,则返回L中值为e的数据元素的地址,查找失败返回NULL,总之都是返回p
return p;
}
8.1.算法1(返回地址):循环分析及异常判断:
1.循环分析:
- (1)p!=NULL:
- 【1】保证首元结点不存在时,不进入循环
- 【2】保证e不在链表范围内时 在p指向尾结点next域时终止循环
- (2)p->data != e:
- 【1】保证循环终止时p指向与元素e相同的结点:默认e在链表范围内
2.不需要异常判断:
- 直接返回p即可
- 【1】首元结点不存在的情况,此时并未进入循环,最终p=NULL
- 【2】e不在链表范围内的情况,虽然进入了循环,但是表中无与元素e相同的结点,最终p=NULL
- 【3】e在链表范围内的情况,最终p指向与元素e相同的结点
返回位序:
//8.单链表的按值查找
// 查找与元素 e 相同的元素并返回其在单链表中的位序
int LocateElemLinkList1(LinkList L, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int count = 1;
//[2]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时终止循环
//p->data != e:
// 保证循环终止时p指向第与元素e相同的结点
while (p != NULL && p->data != e)
{
p = p->next; // 移动到下一个结点
count++;// 计数器加1
}
//[3]异常判断
//p == NULL:
//1.首元结点不存在的情况
//2.链表中无与元素e相同结点的情况
if (p == NULL)
{
return 0;//返回0表示查找失败
}
return count;
}
8.2.算法2:循环分析及异常判断:
1.循环分析(返回位序):
- (1)p!=NULL:
- 【1】保证首元结点不存在时,不进入循环 :最终p==NULL,返回0表示查找失败
- 【2】保证e不在链表范围内时 在p指向尾结点next域时终止循环:最终p=NULL, count的值为链表长度加1,返回0表示查找失败
- (2)p->data != e:
- 【1】保证循环终止时p指向与元素e相同的结点:默认e在链表范围内,最终的count值为所求的元素位序
2.异常判断:
- (1)p == NULL:
- 【1】首元结点不存在的情况,此时并未进入循环
- 【2】e不在链表范围内的情况,虽然进入了循环,但是表中无与元素e相同的结点
9.单链表的按位插入
注意:即使是i==1的情况即在表头插入(首元结点之前):头指针的指向是不需要改变的(指向头结点),所以不需要传入二级指针
InsertLinkList(LinkList L, int i, ElemType e)
1.算法步骤:
- (1)首先找到 ai-1 的存储位置 p
- (2)生成一个数据域为 e的新结点s
- (3)插入新结点:
- ① 新结点的指针域指向结点 ai
- ②结点 ai-1的指针域指向新结点
图解如下:
s->next = p->next;
p->next = s;
一定要注意两者的顺序不可直接互换:
p->next = s;
s->next = p->next;
//9.单链表的按位插入
//在单链表中元素位序为i的结点前插入新的结点
//注意:这里i==1的情况即在表头插入(首元结点之前)头指针的指向是不需要改变的,所以不需要传入二级指针
bool InsertLinkList(LinkList L, int i, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点
LNode* p = L;
//[2]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[3]循环寻找第i-1个结点
//p!=NULL:
// 1.保证头结点不存在时,不进入循环
// 2.保证传入的i超过表长加1时,循环到p指向尾结点next域时终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p!=NULL&&j < i - 1)
{
p = p->next;// 移动到下一个结点
j++;// 计数器加1
}
//[4]异常判断
//p == NULL:
//1.头结点不存在的情况
//2.i大于链表长度加1 的情况(i==链表长度+1:即在尾结点后面插入一个结点)
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p == NULL || j > i - 1)
{
return false;
}
//[5]为新结点动态分配内存
LNode* s =(LNode *) malloc(sizeof(LNode));
if (s == NULL)
{
printf("新结点内存分配失败!\n");
return false;
}
//[6]初始化新结点数据域
s->data = e;
//[7]将新结点插入到第i-1个结点之后(这里顺序不能互换,互换后会断链)
s->next = p->next;
p->next = s;
return true;
}
2.特殊情况分析:
1. i==1的情况(首元结点前插入)
2. i==链表长度+1的情况(尾结点后插入)
分析:
- 循环遍历结束后,p 指向链表的最后一个结点。
- 新的结点 s 被分配内存。
- 新结点 s 的 next 指向 NULL,因为 p 是最后一个结点,其 next 也是 NULL。
- 将最后一个结点 p 的 next 指针指向新结点 s。
10.单链表的按位删除
注意:i==1的情况即删除首元结点头指针的指向是不需要改变的(指向头结点),所以不需要传入二级指针
DeleteLinkList(LinkList L, int i, ElemType *e)
1.算法步骤
- (1)初始化临时指针p指向头结点(p用来寻找第i-1个结点),
定义临时指针q(q用来销毁待删除结点),
初始化计数器j等于0(此计数器用来控制循环----寻找第i-1个结点)- (2)循环寻找第i-1个结点
- (3)用q指向第i个结点(待删除节点)并记录数据
- (4)p->next = q->next; 保证链表的完整性
- (5)free( p );删除待删除结点
//10.单链表的按位删除
bool DeleteLinkList(LinkList L, int i, ElemType *e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点(p用来寻找第i-1个结点)
LNode* p = L;
//[2]定义临时指针q(q用来销毁待删除结点)
LNode* q;
//[3]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[4]循环寻找第i-1个结点
//p->next!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证传入的i超过表长时,循环到p指向尾结点时(p->next为NULL)终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != NULL && j < i - 1)
{
p=p->next;// 移动到下一个结点
j++;//计数器加1
}
//[5]异常判断
//p->next == NULL:
//1.首元结点不存在的情况
//2.i大于链表长度的情况(i==链表长度+1:p指向尾结点,p->next指向NULL)
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p->next == NULL || j > i - 1)
{
return false;
}
//[6]用q指向第i个结点并记录数据
q = p->next;
*e = q->data;
//[7]删除结点
p->next = q->next;//等价于p->next=p->next->next;即将第i-1个结点的指针域指向第i个结点的下一个结点
free(q);//释放待删除结点空间
return true;
}
2.特殊情况分析:
1. i==1的情况(删除首元结点)
2.i==链表长度(删除尾结点)
11.单链表的整表创建------头插法(每次将元素插入到链表头结点和首元结点之间)
建立一个长度为n的单链表,数据元素从键盘输入
注意:这里头指针要指向新建的头结点,所以传入二级指针
CreateLinkList_H(LinkList *L,int n)
1.算法步骤:
- (1)从一个空表开始(建立一个头结点并初始化);
- (2)循环生成新结点,并读入数据存放到新结点的数据域中
- (3)将每次创建好的新结点插入到头结点和首元结点之间
头插法建立的线性表的元素顺序与键盘中输入的元素顺序 相反
//头插法建立单链表:
//注意这里头指针要指向新建的头结点,所以传入二级指针
//建立一个长度为n的单链表,数据元素从键盘输入
bool CreateLinkList_H(LinkList *L,int n)
{
//[1]建立一个空的头结点并初始化
*L = (LinkList)malloc(sizeof(LNode));
if ((*L) == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = NULL;
//[2]循环建立新结点并插入到表头
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p= (LNode *)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d",&(p->data));//注意这里 &(p->data)和&p->data等价
//因为->优先级大于&
//[[2]]将新结点插入到表头
p->next = (*L)->next;//新结点的next域应指向原来的首元结点
(*L)->next = p;//头结点的next域指向新结点
}
return true;
}
2.插入过程图解:
12.单链表的整表创建------尾插法(每次将元素插入到链表尾部)
建立一个长度为n的单链表,数据元素从键盘输入
注意:这里头指针要指向新建的头结点,所以传入二级指针
1.算法步骤:
- (1)从一个空表开始(建立一个头结点并初始化);
- (2)建立一个尾指针并初始化指向头结点
- (2)循环生成新结点,并读入数据存放到新结点的数据域中
- (3)将每次创建好的新结点插入到当前尾指针指向的尾结点之后(即r->next)
//12.尾插插法建立单链表:
//注意这里头指针要指向新建的头结点,所以传入二级指针
//建立一个长度为n的单链表,数据元素从键盘输入
bool CreateLinkList_R(LinkList* L,int n)
{
//[1]建立一个空的头结点并初始化
*L = (LinkList)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = NULL;
//[2]建立尾指针并初始化指向头结点
LNode* r = *L;
//[2]循环建立新结点并插入到表尾
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode *)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &(p->data));//注意这里 &(p->data)和&p->data等价
//因为->优先级大于&
//[[2]]将新结点插入到表尾
p->next = NULL;//每个新结点都插入到链表的尾部,所以新结点的next域指向NULL
r->next = p;//更改原来尾结点的next域指向新结点p(新的尾结点)
r = p;//将尾指针指向新的尾结点p
}
return true;
}
2.插入过程图解:
13.输出单链表的所有元素
1.算法步骤:
- (1) 定义并初始化 临时指针p 指向首元结点
- (2)判断链表是否为空(即只有头结点)
- (3)遍历链表并输出所有结点元素,直到最后一个结点
//13.输出单链表的所有元素
bool printLinkList(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化 p 指向首元结点
LNode* p = L->next;
//[2]判断链表是否为空(即只有头结点)
if (p == NULL)
{
printf("NULL!\n"); // 输出 NULL 代表链表为空
return false;; // 直接返回,不继续执行后续代码
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p != NULL)
{
printf("%d --> ", p->data); // 输出当前结点的数据
p = p->next; // 移动指针到下一个结点
}
printf("end\n");
return true;
}
14.所有相关操作的实现
//带头结点的单链表的实现
#include<stdio.h>
#include<stdlib.h>
//自定义bool类型,
#define bool int
#define true 1
#define false 0
//自定义数据元素的数据类型
typedef int ElemType;
//1.单链表结点的结构定义
typedef struct LNode
{
ElemType data;//结点数据域
struct LNode* next;//结点指针域
}LNode, * LinkList;
//注意这里的LinkList为自定义的结构体指针类型与LNode*等价,最好用来定义头指针
//2.初始化单链表(实际上在初始化头结点)
//这里修改了主函数中头指针的值,所以要传入二级指针
bool InitLinkList(LinkList* L)
{
//[1](解引用传入的指向头指针L的二级指针)使头指针L指向新开辟出来的头结点空间
*L = (LNode*)malloc(sizeof(LNode));//为头结点开辟一个结点大小的空间
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->data = 0;//这里我存储当前链表的长度为0
//[2]置空头结点的指针域
(*L)->next = NULL;//头结点的指针域为空
return true;
}
//3.判断单链表是否为空
bool LinkListEmpty(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]若头结点指针域为空,返回true,证明是空表
if (L->next == NULL)
{
return true;
}
//否则返回false
else
{
return false;
}
}
//4.单链表的销毁(链表销毁后不存在;所有元素,头结点,头指针均被销毁)
//这里在遍历过程中改变了主函数中头指针的值,所以要传入二级指针
bool DestroyLinkList(LinkList* L)
{
//[0]判断单链表头结点是否存在
if (*L == NULL)
{
return false;
}
//[1]定义临时指针p,p用来销毁空间的
LNode* p ;
//[2]依次销毁所有结点
while (*L != NULL)
{
p = *L;//开始:p指向头结点 终止:p指向最后一个结点
(*L) = (*L)->next;//开始:头指针*L指向首元结点 终止:头指针*L指向最后一个结点的指针域NULL
free(p);//开始:销毁头结点 终止:销毁最后一个结点
}
//[3]更新头指针,避免悬挂指针问题
*L=NULL;
return true;
}
//5.单链表的清空
//此操作不需要修改头指针本身的指向,
// 只是修改头结点的 next 指针,所以无需使用二级指针。
bool ClearLinkList(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]定义了两个临时指针p,q
//p:存储待销毁结点的地址
//q:存储下一个待销毁结点的地址
LNode *p, *q;
//[2]初始化p指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
p = L->next;
//[3]依次销毁除头结点外的所有结点
while (p!= NULL)//注意这里不能写(q!=NULL),因为在这条语句之前q并未初始化(是一个野指针)
{
//q 记录下一个结点的位置
q = p->next;
// 释放当前结点
free(p);
//p 移动到下一个结点
p = q;
}
//[4]清空后,将头结点的指针域置为空
L->next = NULL;
return true;
}
//6.求单链表的表长
//返回L中的元素个数
int LinkListLength(LinkList L)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1]定义一个临时指针p,并让其指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为0,因为没有排除空链表的可能
int i = 0;
//[3]循环遍历,统计结点数
while(p!=NULL)//若为空,直接返回i=0,不会进入循环
{
i++;//进入循环,证明存在首元结点,故先叠加
p = p->next;//当p指向空,计数器已经将表长记录成功
}
return i;
}
//7.单链表的按位查找
//取单链表中第i个元素的内容
bool GetElemLinkList(LinkList L, int i, ElemType* e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化p指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int j = 1;
//[3]查找
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证i大于链表长度时 在p指向尾结点next时终止循环
//j<i:
//正常情况下:循环终止时p指向第i个结点
while (p!=NULL&&j < i)
{
p = p->next;//移动到下一个结点
j++;//计数器叠加
}
//[4]异常判断
//p == NULL:
//1.首元结点不存在的情况
//2.i大于链表长度的情况
//j>i:
//i<1的情况
if(p == NULL || j > i)
{
return false;
}
//[5]获取第i个结点的值
*e = p ->data;
return true;
}
//8.单链表的按值查找
// 查找与元素 e 相同的元素并返回其在单链表中的地址
LNode* LocateElemLinkList(LinkList L, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return NULL;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时终止循环
//p->data!=e:
// 保证循环终止时p指向与元素e相同的结点
while (p != NULL && p->data != e)
{
p = p->next; // 移动到下一个结点
}
//[3]查找成功,则返回L中值为e的数据元素的地址,查找失败返回NULL,总之都是返回p
return p;
}
//8.单链表的按值查找
// 查找与元素 e 相同的元素并返回其在单链表中的位序
int LocateElemLinkList1(LinkList L, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return 0;
}
//[1] 初始化 p 指向首元结点
//若首元结点不存在(空表),L->next会使p指向NULL,不会进入循环
LNode* p = L->next;
//[2]初始化计数器为1
int count = 1;
//[2]遍历链表,并寻找与元素e相同的结点
//p!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证链表中无与元素e相同结点时 在p指向尾结点next时终止循环
//p->data != e:
// 保证循环终止时p指向第与元素e相同的结点
while (p != NULL && p->data != e)
{
p = p->next; // 移动到下一个结点
count++;// 计数器加1
}
//[3]异常判断
//p == NULL:
//1.首元结点不存在的情况
//2.链表中无与元素e相同结点的情况
if (p == NULL)
{
return 0;//返回0表示查找失败
}
return count;
}
//9.单链表的按位插入
//在单链表中元素位序为i的结点前插入新的结点
//注意:这里i==1的情况即在表头插入(首元结点之前)头指针的指向是不需要改变的,所以不需要传入二级指针
bool InsertLinkList(LinkList L, int i, ElemType e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点
LNode* p = L;
//[2]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[3]循环寻找第i-1个结点
//p!=NULL:
// 1.保证头结点不存在时,不进入循环
// 2.保证传入的i超过表长加1时,循环到p指向尾结点next域时终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p!=NULL&&j < i - 1)
{
p = p->next;// 移动到下一个结点
j++;// 计数器加1
}
//[4]异常判断
//p == NULL:
//1.头结点不存在的情况
//2.i大于链表长度加1 的情况(i==链表长度+1:即在尾结点后面插入一个结点)
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p == NULL || j > i - 1)
{
return false;
}
//[5]为新结点动态分配内存
LNode* s =(LNode *) malloc(sizeof(LNode));
if (s == NULL)
{
printf("新结点内存分配失败!\n");
return false;
}
//[6]初始化新结点数据域
s->data = e;
//[7]将新结点插入到第i-1个结点之后(这里顺序不能互换,互换后会断链)
s->next = p->next;
p->next = s;
return true;
}
//10.单链表的按位删除
bool DeleteLinkList(LinkList L, int i, ElemType *e)
{
//[0]判断单链表头结点是否存在
if (L == NULL)
{
return false;
}
//[1]初始化临时指针p指向头结点(p用来寻找第i-1个结点)
LNode* p = L;
//[2]定义临时指针q(q用来销毁待删除结点)
LNode* q;
//[3]初始化计数器j等于0:此计数器用来控制循环----寻找第i-1个结点
int j = 0;
//[4]循环寻找第i-1个结点
//p->next!=NULL:
// 1.保证首元结点不存在时,不进入循环
// 2.保证传入的i超过表长时,循环到p指向尾结点时(p->next为NULL)终止循环
//j<i-1:
// 保证循环终止时p指向第i-1个结点
while (p->next != NULL && j < i - 1)
{
p=p->next;// 移动到下一个结点
j++;//计数器加1
}
//[5]异常判断
//p->next == NULL:
//1.首元结点不存在的情况
//2.i大于链表长度的情况(i==链表长度+1:p指向尾结点,p->next指向NULL)
//j > i - 1
//1.传入的i小于1的情况
//比如i=0:不进入循环,0>-1,进入分支,返回异常
if (p->next == NULL || j > i - 1)
{
return false;
}
//[6]用q指向第i个结点并记录数据
q = p->next;
*e = q->data;
//[7]删除结点
p->next = q->next;//等价于p->next=p->next->next;即将第i-1个结点的指针域指向第i个结点的下一个结点
free(q);//释放待删除结点空间
return true;
}
//11.头插法建立单链表:
//注意这里头指针要指向新建的头结点,所以传入二级指针
//建立一个长度为n的单链表,数据元素从键盘输入
bool CreateLinkList_H(LinkList *L,int n)
{
//[1]建立一个空的头结点并初始化
*L = (LinkList)malloc(sizeof(LNode));
if ((*L) == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = NULL;
//[2]循环建立新结点并插入到表头
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p= (LNode *)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d",&(p->data));//注意这里 &(p->data)和&p->data等价
//因为->优先级大于&
//[[2]]将新结点插入到表头
p->next = (*L)->next;//新结点的next域应指向原来的首元结点
(*L)->next = p;//头结点的next域指向新结点
}
return true;
}
//12.尾插插法建立单链表:
//注意这里头指针要指向新建的头结点,所以传入二级指针
//建立一个长度为n的单链表,数据元素从键盘输入
bool CreateLinkList_R(LinkList* L,int n)
{
//[1]建立一个空的头结点并初始化
*L = (LinkList)malloc(sizeof(LNode));
if (*L == NULL)
{
printf("内存分配失败!\n");
return false;
}
(*L)->next = NULL;
//[2]建立尾指针并初始化指向头结点
LNode* r = *L;
//[2]循环建立新结点并插入到表尾
LNode* p;
for (int i = 0; i < n; i++)
{
//[[1]]建立新结点并初始化data域
p = (LNode *)malloc(sizeof(LNode));
if (p == NULL)
{
printf("内存分配失败!\n");
return false;
}
scanf_s("%d", &(p->data));//注意这里 &(p->data)和&p->data等价
//因为->优先级大于&
//[[2]]将新结点插入到表尾
p->next = NULL;//每个新结点都插入到链表的尾部,所以新结点的next域指向NULL
r->next = p;//更改原来尾结点的next域指向新结点p(新的尾结点)
r = p;//将尾指针指向新的尾结点p
}
return true;
}
//13.输出单链表的所有元素
bool printLinkList(LinkList L)
{
//[1]初始化 p 指向头结点
LNode* p = L->next;
//[2]判断链表是否为空(即只有头结点)
if (p == NULL)
{
printf("NULL\n"); // 输出 NULL 代表链表为空
return false;; // 直接返回,不继续执行后续代码
}
//[3]遍历链表并输出所有结点元素,直到最后一个结点
while (p != NULL)
{
printf("%d --> ", p->data); // 输出当前结点的数据
p = p->next; // 移动指针到下一个结点
}
printf("end\n");
return true;
}
int main()
{
LinkList L1;//定义头指针L1
//[11]头插法建立长度为5的单链表
printf("头插输入L1各元素:\n");
CreateLinkList_H(&L1, 5);
printf("创建的单链表L1为:\n");
printLinkList(L1);
printf("\n");
//[12]尾插法建立长度为4的单链表
LinkList L2;//定义头指针L2
printf("尾插输入L2各元素:\n");
CreateLinkList_R(&L2, 4);
printf("创建的单链表L2为:\n");
printLinkList(L2);
printf("\n");
//[3]判空
printf("判断L2是否为空表:\n");
int k= LinkListEmpty(L2);
if (k == true)
{
printf("单链表L2是空表!\n");
}
else
{
printf("单链表L2不是空表!\n");
}
printf("\n");
//[5]清空L1
printf("清空L1:\n");
bool o=ClearLinkList(L1);
if (o==true)
{
printf("L1清空成功!\n");
}
printf("\n");
//[6]求L2表长
printf("求L2表长:\n");
int lon=LinkListLength(L2);
printf("L2表长为:%d\n", lon);
printf("\n");
//[7]取L2中第三个元素的值
printf("取L2中第三个元素的值:\n");
ElemType e1;//e1来取值
GetElemLinkList(L2, 3, &e1);
printf("%d\n", e1);
printf("\n");
//[8.1]L2中查找与元素 2 相同的元素并返回其在单链表中的地址
printf("L2中查找与元素 2 相同的元素并返回其在单链表中的地址\n");
LNode* p = LocateElemLinkList(L2, 2);
printf("查找到的元素为%d\n", p->data);
printf("\n");
//[8.2]L2中查找与元素 2 相同的元素并返回其在单链表中的位序
printf("L2中查找与元素 2 相同的元素并返回其在单链表中的位序\n");
int q = LocateElemLinkList1(L2, 2);
printf("位序为:%d\n", q);
printf("\n");
//[9]在L2中插入一些元素
printf("L2中插入一些元素:\n");
InsertLinkList(L2, 1, 10);
printLinkList(L2);
printf("\n");
InsertLinkList(L2, 3, 11);
printLinkList(L2);
printf("\n");
InsertLinkList(L2, 4, 12);
printLinkList(L2);
printf("\n");
InsertLinkList(L2, 2, 13);
printLinkList(L2);
printf("\n");
printf("输出插入后的单链表L2\n");
printLinkList(L2);
printf("\n");
//[10]在L2中删除一些元素
printf("L2中删除一些元素:\n");
ElemType e2, e3, e4;
DeleteLinkList(L2, 2, &e2);
printLinkList(L2);
printf("删除的元素e1:%d\n",e2);
printf("\n");
DeleteLinkList(L2, 3, &e3);
printLinkList(L2);
printf("删除的元素e2:%d\n", e3);
printf("\n");
DeleteLinkList(L2, 4, &e4);
printLinkList(L2);
printf("删除的元素e3:%d\n", e4);
printf("\n");
printf("删除后的L2:\n");
printLinkList(L2);
printf("\n");
//[4]销毁L2
printf("销毁L2:\n");
bool gg = DestroyLinkList(&L2);
if (gg == true)
{
printf("L2销毁成功!\n");
}
return 0;
}