带头结点的单链表实现(C语言)
代码的大致结构如下:
1.声明库函数
2.定义全局变量
3.命名
4.数据结构的定义
5.相关操作的声明
6.main函数(用来测试函数)
7.相关操作的实现
代码的具体实现如下:
代码中每个地方都加入了较为详细的注释,这里就不再解释了
/*
* 带头结点的单链表实现
*/
#include <stdio.h>
#include <stdlib.h>
/*
* 全局变量声明
*/
#define true 1//代表函数的执行状态为成功,没有出错
#define false 0//代表函数的执行状态为失败,出错
#define overflow -1//内存溢出,代表没有足够的内存空间
/*
* 返回值和元素类型命名
*/
typedef int Status;//代表函数返回值的状态
typedef int ElemType;//代表元素类型
/*
* 定义单链表的结点类型
* LNode用于声明结点
* LinkList用于声明单链表,注意是指针类型
*/
typedef struct LNode {
ElemType data;//结点的数据域,用于存放数据
struct LNode *next;//结点的指针域,用于存放下一个结点的地址
} LNode, *LinkList;
/*
* 带头结点的单链表基本操作的函数声明
*/
// 1. 单链表的初始化
Status List_Init(LinkList *L);
// 2. 单链表的建立---头插法
LinkList List_HeadInsert(LinkList L);
// 3. 单链表的建立---尾插法
LinkList List_TailInsert(LinkList L);
// 4. 单链表的销毁
Status List_Destroy(LinkList *L);
//*****************插入操作*********************
// 5. 单链表的第i个位置插入结点
Status List_InsertElem(LinkList L, int i, ElemType e);
// 6. 单链表指定结点的后插操作
Status List_InsertNextNode(LNode *p, ElemType e);
// 7. 单链表的指定结点前插入操作1
Status List_InsertPreNodeFirst(LinkList L, LNode *p, ElemType e);
// 8. 单链表的指定结点前插入操作2
Status List_InsertPreNodeSecond(LNode *p, ElemType e);
//************删除操作****************
// 9. 单链表的第i个结点删除
Status List_DeleteElem(LinkList L, int i, ElemType *e);
// 10. 单链表的指定结点的删除1
Status List_DeleteNodeFirst(LinkList L, LNode *p, ElemType *e);
// 11. 单链表的指定结点的删除2
Status List_DeleteNodeSecond(LNode *p, ElemType *e);
// 12. 单链表指定结点的后一个结点删除
Status List_DeleteElemNextNode(LNode *p, ElemType *e);
//**************************
// 13. 单链表的判空
Status List_IsEmpty(LinkList L);
// 14. 单链表的长度,第一种方法
int List_LengthFirst(LinkList L);
// 15. 单链表的长度,第二种长度
int List_LengthSecond(LinkList L);
// 16. 单链表的查找---按位查找
LNode *List_GetElem(LinkList L, int i);
// 17. 单链表的查找---按值查找
LNode *List_LocateElem(LinkList L, ElemType e);
// 18. 单链表的打印操作
Status List_PrintElem(LinkList L);
// 19. 两个单链表的合并1
LinkList List_MergeFirst(LinkList L1, LinkList L2, LinkList L3);
// 20. 两个单链表的合并2
LinkList List_MergeSecond(LinkList L1, LinkList L2);
// 21. 单链表的清空
Status List_Clear(LinkList L);
int main() {
LinkList L1, L2, L3;
LinkList L;
printf("单链表的判空:%d\n", List_IsEmpty(L));
printf("单链表的初始化执行状态:%d\n", List_Init(&L));
printf("单链表的判空:%d\n", List_IsEmpty(L));
printf("***********\n");
List_HeadInsert(L);
printf("单链表的头插法建立:%d\n", List_PrintElem(L));
printf("单链表的判空:%d\n", List_IsEmpty(L));
printf("当前单链表的长度1:%d\n", List_LengthFirst(L));
printf("当前单链表的长度2:%d\n", List_LengthSecond(L));
printf("单链表的销毁:%d\n", List_Destroy(&L));
printf("*********************\n");
List_Init(&L);
List_TailInsert(L);
printf("单链表的尾插法建立:%d\n", List_PrintElem(L));
printf("当前单链表的长度1:%d\n", List_LengthFirst(L));
printf("当前单链表的长度2:%d\n", List_LengthSecond(L));
printf("*********************\n");
LNode *p = List_GetElem(L, 4);
printf("单链表按位查找:%d\n", p->data);
// LNode *s = List_GetElem(L, -1);
// printf("单链表按位查找异常情况1:%d\n", s->data);
// printf("单链表按位查找异常情况2:%d\n", s->data);
LNode *q = List_LocateElem(L, 17);
printf("单链表按值查找:%d\n", q->data);
// LNode *s = List_LocateElem(L, 67);
// printf("单链表按位查找异常情况:%d\n", s->data);
printf("*********************\n");
p = List_GetElem(L, 4);
printf("单链表的元素插入:%d\n", List_InsertElem(L, 4, 23));
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("p结点前插结点1:%d\n", List_InsertPreNodeFirst(L, p, 90));
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("p结点前插结点2:%d\n", List_InsertPreNodeSecond(p, 67));
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("单链表的后插操作:%d\n", List_InsertNextNode(p, 34));
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
printf("*************\n");
ElemType e;
p = List_GetElem(L, 4);
printf("单链表的删除结点:%d", List_DeleteElem(L, 4, &e));
printf(",删除结点的值:%d\n", e);
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("单链表p结点的删除1:%d", List_DeleteNodeFirst(L, p, &e));
printf(",删除结点的值:%d\n", e);
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("单链表p结点的删除2:%d", List_DeleteNodeSecond(p, &e));
printf(",删除结点的值:%d\n", e);
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
p = List_GetElem(L, 4);
printf("单链表p结点后继的删除:%d", List_DeleteElemNextNode(p, &e));
printf(",删除结点的值:%d\n", e);
printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
printf("当前单链表的长度:%d\n", List_LengthFirst(L));
List_Destroy(&L);
printf("***************\n");
List_Init(&L1);
List_Init(&L2);
List_Init(&L3);
List_HeadInsert(L1);
List_TailInsert(L2);
printf("单链表L1的打印执行状态:%d\n", List_PrintElem(L1));
printf("当前单链表L1的长度:%d\n", List_LengthFirst(L1));
printf("单链表L2的打印执行状态:%d\n", List_PrintElem(L2));
printf("当前单链表L2的长度:%d\n", List_LengthFirst(L2));
printf("单链表L3的打印执行状态:%d\n", List_PrintElem(L3));
printf("当前单链表L3的长度:%d\n", List_LengthFirst(L3));
printf("___________\n");
printf("单链表的合并1\n");
// List_MergeFirst(L1,L2,L3);
// printf("单链表的打印执行状态:%d\n", List_PrintElem(L3));
// printf("当前单链表的长度:%d\n", List_LengthFirst(L3));
printf("单链表的合并2\n");
List_MergeSecond(L1, L2);
printf("单链表的打印执行状态:%d\n", List_PrintElem(L1));
printf("当前单链表的长度:%d\n", List_LengthFirst(L1));
//
// printf("单链表的打印执行状态:%d\n", List_PrintElem(L));
// printf("当前单链表的长度:%d\n", List_LengthFirst(L));
return 0;
}
/*
* 带有结点的单链表的基本操作的函数实现:
*
* 可能的问题:
* 1.函数中很多的返回值是true或者false,一些题目都是void,没有返回值:
* 这么写是为了方便测试bug,容易测出来是哪个功能出了问题,帮助快速定位
* 考试题目中全部换成void类型,不需要返回值
* void StaticInitSqList(SqList *L) {
* *********
* //不需要return
* }
*/
// 1. 单链表的初始化
Status List_Init(LinkList *L) {
/*
* 时间复杂度O(1)
*/
/*
* 这里形式参数使用二级指针,指针的指针,也就是指向指针的指针
* 因为声明L的时候,L一级指针,指向的是某一块空间,但是一级指针只能修改所指内容的值,不能修改自身的值
* 不使用二级指针的话,下面创建头结点的操作会报错,因为不能改变L的值,L的值是某个地方的地址
* 如果要修改L自身的值,就需要使用二级指针指向一级指针,通过对二级指针操作达到修改头指针的目的
*
* 使用二级指针的情况:
* 1、单链表的初始化和销毁
* 2、单链表的其他操作,如插入、删除、遍历,等等操作使用一级指针就好
*/
// 创建单链表的头结点
*L = (LinkList) malloc(sizeof(LNode));
// 判断头结点是否创建成功
if (!(*L)) {
// 头结点创建失败,但内存空间不够的情况下可能会出现这个情况,一般情况下没有这个问题
return false;
}
// 将头结点的指针域指向空,代表为空链表
(*L)->next = NULL;
// 返回函数执行成功的状态
return true;
}
// 2. 单链表的建立---头插法
LinkList List_HeadInsert(LinkList L) {
/*
* 时间复杂度O(n)
*/
// 声明s指针用来指向新节点
LNode *s;
// 这里直接指定存放的值,不进行人为输入了
int i;//声明循环变量
for (i = 10; i >= 0; --i) {
s = (LNode *) malloc(sizeof(LNode));
// 这里没有判断s结点内存是否分配成功,默认都分配成功。其实加上判断也可以
/*
* if(!s){
* return false;
* }
* 这里函数返回值是单链表类型,不能写这个,想写这个的话,需要把返回值类型改成STATUS
*/
s->data = i + 1;
// 新结点指向头结点的下一个位置,也就是首元结点
// 如果是第一个结点的话,s指向的是空,在初始化的时候已经让头结点的指针域为空,所以这里不需要变动
s->next = L->next;
// 新结点作为第一个结点,也就是首元结点,头结点指向新节点
L->next = s;
}
// 这里其实用void也可以或者写成return true,因为使用的是指针,可以直接修改,不需要通过返回值达到修改的目的
return L;
}
// 3. 单链表的建立---尾插法
LinkList List_TailInsert(LinkList L) {
/*
* 时间复杂度O(n)
*/
// 声明s指针用来指向新分配的结点
LNode *s;
// 声明并初始化尾指针r,刚开始尾指针指向头结点,由于使用尾插法,所以需要标记尾结点的位置
LNode *r = L;
int i;//声明循环变量
for (i = 10; i < 20; ++i) {
// 让s指针指向新分配的结点,插入到最后
s = (LNode *) malloc(sizeof(LNode));
// 这里没有异常判断s结点是否分配成功,默认都是分配成功
s->data = i + 1;
// 让尾指针指向结点的指针域指向s结点,使得s指向的结点为最后一个结点
r->next = s;
// 移动r指针到s指向的结点,使r指针指向的是最后一个结点
r = s;
}
// 在结点插入完成后,将尾指针指向结点的指针域指向NULL
// 循环里没有写是因为会在尾部一直插入结点,这会让这行代码多余
r->next = NULL;
// 和头插法一样,不多说了
return L;
}
// 4. 单链表的销毁
Status List_Destroy(LinkList *L) {
/*
* 时间复杂度O(n)
*/
// 声明并初始化一个p指针指向头结点的下一个位置,也就是首元结点
// 这里注意二级指针,看初始化的方法
LNode *p = (*L)->next;
// 声明q指针,用来指向 p指向的即将被删除的结点,因为直接释放p所指的结点的话,会导致p指针无法移动到下一个位置
LNode *q;
while (p != NULL) {
// q指针指向p所指的待删除的结点
q = p;
// 将p指针移动到下一个位置
p = p->next;
// 释放q所指的结点
free(q);
}
// 其他结点都释放完成后,释放头结点
free(*L);
return true;
}
// 5. 单链表的第i个位置插入结点
Status List_InsertElem(LinkList L, int i, ElemType e) {
/*
* 时间复杂度O(n)
*/
/*
* 对i的值的合法性进行判断,i的合法范围[1,length+1]
* 这里没有进行判断是因为如果i值不合法查找到的i-1位置的结点也不合法,后面会因为p结点判空不符合条件结束
* 这里也可以加入i>List_LengthFirst(L)+1和i<1的判断,这样的话保证了i的合法性,后面p结点的判空就可以不写了
* 不过考试题目的话最好写上,保险,这里不写是因为冗余了
* if(i>List_LengthFirst(L)+1||i<1){
* return false;
* }
* 写法很多,不唯一
*/
// p指向i-1位置的结点,这里有对i-1位置的合法性判断
LNode *p = List_GetElem(L, i - 1);
// 对i-1位置的结点是否为空进行判断,也可以写成p==NULL
if (!p) {
return false;
}
// 开辟一块新空间,让s指针指向这个结点,这个结点为要插入的结点
LNode *s = (LNode *) malloc(sizeof(LNode));
// 判断开辟的空间是否成功,考试的时候写的下的话最好也写上
if (!s) {
return overflow;
}
s->data = e;
// s指向结点的指针域指向p指针指向结点的指针域,也就是p指向结点的下一个位置
s->next = p->next;
// 让p指向结点的指针域指向s指向的结点,也就是让s成为p的下一个位置
p->next = s;
// 以上的两个操作不可以换顺序
return true;
}
// 6. 单链表指定结点的后插操作
Status List_InsertNextNode(LNode *p, ElemType e) {
/*
* 时间复杂度O(1)
*/
// 判断指定结点是否为空,这里也可以写成p==NULL
if (!p) {
return false;
}
// s指针指向了这个新开辟的空间,作为新插入的结点
LNode *s = (LNode *) malloc(sizeof(LNode));
// 判断开辟的空间是否成功
if (!s) {
return overflow;
}
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
// 7. 单链表的指定结点前插入1
Status List_InsertPreNodeFirst(LinkList L, LNode *p, ElemType e) {
/*
* 时间复杂度O(n)
* 算法思想:找到当前结点的前一个结点,相当于进行后插操作
* 指定结点为第一个结点的处理:正常,找到前一个结点,也就是头结点
* 指定结点为最后一个结点的处理:正常,找到尾结点的前驱,
* 指定结点为最后一个结点后的NULL,正常,找到尾结点即可
*/
// 声明初始化q指针指向单链表的头结点,这里注意,指定结点p有可能是首元结点,它的前一个位置就是头结点,所以q要从头结点开始
LNode *q = L;
// 找到p位置的前一个位置
// 如果q的下一个位置不是p,q往后移动一个位置,直到q为p的前驱
while (q->next != p) {
q = q->next;
}
LNode *s = (LNode *) malloc(sizeof(LNode));
s->data = e;
s->next = q->next;
q->next = s;
return true;
}
// 8. 单链表的结点前插入2
Status List_InsertPreNodeSecond(LNode *p, ElemType e) {
/*
* 时间复杂度O(1)
* 算法思想:将指定结点后插入一个新节点,将指定结点的值放到新结点,要插入的值放到指定结点
* 这里注意和第一种方法的区别
* 指定结点为第一个结点的处理:正常按上述操作执行
* 指定结点为尾结点的处理:正常处理
* 指定结点为尾结点后的NULL的处理:无法处理(和第一个方法区别),这种方法的问题,或者处理的话就是找到尾结点,然后在尾结点后插入一个结点也行,这里就不处理了
*/
// 判断指定结点是否为空,这里也可以写成p!=NULL
if (!p) {
return false;
}
// s指针指向新节点作为p后插的结点
LNode *s = (LNode *) malloc(sizeof(LNode));
// 这里注意s结点的值为指定结点p的值
s->data = p->data;
// 指定结点p的值变为待插入的值e
p->data = e;
// s结点放在p后面
s->next = p->next;
p->next = s;
return true;
}
// 9. 单链表的第i个结点删除
Status List_DeleteElem(LinkList L, int i, ElemType *e) {
/*
* 时间复杂度O(n)
*/
/*
*判断i的合法性,i的合法性范围[1,length]
* 这里值判断最小是不是小于1,不判断最大的情况
* 因为后面查找i-1的时候,如果太大查过了长度,会因为q为空不符合条件结束
* 也可以最大值判断i>List_LengthFirst(L),这么写的话,后面q的空值判断就可以省略了,考试的话可以这么些,减少代码量
* 因为这样的操作已经保证了i值的合法性,i-1必然是合法的,如果i-1是0的话,返回的是头结点
*/
// 判断i的合法性
if (i < 1) {
return false;
}
// 获取i-1位置的结点
LNode *p = List_GetElem(L, i - 1);
// 判断q指向的结点是否为空
if (!p) {
return false;
}
/*
* 删除i位置结点
* p指向的结点是i-1位置的结点,所以将q指向p下一个位置的结点
* 删除操作即是让p的指针域,也就是下一个位置成为q的下一个位置
* 最后释放q指向的结点
*
* 注:如果选择题中不需要释放结点的时候也可以这么写
* p->next=p->next->next;
* 因为这么写的话p原来的后继就找不到了,无法释放空间
*/
LNode *q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return true;
}
// 10. 单链表的指定结点的删除1
Status List_DeleteNodeFirst(LinkList L, LNode *p, ElemType *e) {
/*
* 时间复杂度O(n)
* 算法思想:和插入一样,找到p指向结点的前驱,后面的操作和上面一样
* 这里注意
* 第一个结点和最后一个结点的处理正常,都是寻找前驱
*/
// p指向的结点有可能是首元结点,所以要从头结点开始
LNode *q = L;
// 如果q的下一个位置不是p,则移动q到下一个位置
while (q->next != p) {
q = q->next;
}
// 删除操作,核心代码
q->next = p->next;
*e = p->data;
free(p);
return true;
}
// 11. 单链表的指定结点的删除2
Status List_DeleteNodeSecond(LNode *p, ElemType *e) {
/*
* 时间复杂度O(1)
* 算法思想:将p指向的结点和后面一个结点的值交换,将问题转化为删除p结点后面的结点
* 这里注意
* 第一个结点的处理正常
* 最后一个结点只能寻找前驱删除,否则无法处理,这个方法的弊端
*/
/*
* 判断p指向的结点的后一个位置是否为NULL,来判断是否为尾结点
* 这里也可以写成p->next==NULL
* 这里不进行尾结点的处理,因为函数没有传入指向单链表头结点的头指针,无法进行处理
*/
if (!p->next) {
return false;
}
// 这里要提前给e赋值,后面的p指向的结点值会覆盖
*e = p->data;
// 让q指向p的下一个位置
LNode *q = p->next;
// 这里只把q的值赋值给p,因为q的结点要删除,值是多少也没有意义了,在写就是多余操作了
p->data = q->data;
p->next = q->next;
free(q);
return true;
}
// 12. 单链表指定结点的后一个结点删除
Status List_DeleteElemNextNode(LNode *p, ElemType *e) {
/*
* 时间复杂度O(1)
*/
/*
* 判断p结点是否为空,和p结点的下一个结点是否为空
* p结点为空,说明p为尾结点后的NULL,不合法
* p的后继为空,说明p是尾结点,不合法
*/
if (!p || !p->next) {
return false;
}
LNode *q = p->next;
p->next = q->next;
*e = q->data;
free(q);
return true;
}
// 13. 单链表的判空
Status List_IsEmpty(LinkList L) {
/*
* 时间复杂度O(1)
*/
if (L->next == NULL) {
return true;
} else {
return false;
}
}
// 14. 单链表的长度
int List_LengthFirst(LinkList L) {
/*
* 时间复杂度O(n)
*/
/*
* 这里的p指针指向单链表的头结点的下一个结点,也就是首元结点
*/
LNode *p = L->next;
/*
* 声明初始化计数器,用来计数单链表的结点个数
* 这里初始化为0的原因是有可能单链表里面只有头结点的情况,这里没有对单链表是否为空进行判断,所以从0开始,把单链表为空的情况放进去
*/
int count = 0;
/*
* 这里的意义是如果p指针指向的结点不为空,则说明当前结点是单链表的一部分
* 这里也可以写成p!=NULL
*/
while (p) {
// 当前结点是单链表的一部分,计数器加1
count++;
// p指针移动到下一个位置
p = p->next;
}
// count的值即为单链表的长度
return count;
}
// 15. 单链表长度获取第二种方法
int List_LengthSecond(LinkList L) {
/*
* 时间复杂度O(n)
*/
// 单链表的长度有可能有空,所以从0开始,如果前面进行了判空,可以从1开始
int count = 0;
// p指针指向头结点的位置,这里区别第一种方法
LNode *p = L;
/*
* 这里通过判断p指向结点的下一个结点是否为空来计数
* 和第一种方法进行区别
* 这里的条件也可以写成p->next!=NULL
*/
while (p->next) {
// 说明当前p指向的结点后面还有结点,移动p指针
p = p->next;
// 计数器加1
count++;
}
return count;
}
// 16. 单链表的查找---按位查找
LNode *List_GetElem(LinkList L, int i) {
/*
* 时间复杂度O(n)
*/
// 判断单链表是否为空,也可以不写,让后面while循环去判断,因为p为空,最后返回空值来处理,都可
// 这种写法在实际中更容易找到出错的原因,排bug
// 考试的话可以不写,减少非必需的代码,防止写不下
if (List_IsEmpty(L)) {
// 说明单链表内没有元素
return false;
}
/*
* 声明并初始化单链表索引序号,标记单链表遍历到第几个,默认从1开始
* 如果i值为0,返回头结点
* 如果i值小于0,返回空值
* 如果i值的超过了单链表的长度的话,p指针最终会指向尾结点后面的NULL,因为不满足p不为空的条件,循环终止,返回p指针,也就是空值
* 如果i合法的情况下,如果查找到元素,会因为j=i不满足循环继续的条件而停止循环,返回p指针指向的结点,为查到的结点
*/
if (i == 0) {
return L;
}
// 条件判断也可以写成i<0
if (i < 1) {
// 也可以写成return false
return NULL;
}
int j = 1;
/*
* p指针指向第一个结点,也就是首元结点,因为j是从1开始的,也就是首元结点开始遍历
* 如果是从头结点开始,查到的结点为对应i的结点的前一个结点
* int j = 0;
* LNode *p = L;
* while(p->next&&j<i){
* p=p->next;
* j++;
* }
*/
LNode *p = L->next;
// 注意条件判断
// 如果出现i的值大于单链表长度的情况,会因为p结点为空不符合条件而结束,返回空值,
// 也可以在前面加入对i是否大于单链表长度的判断,这样的话可以减少不合法位置的判断时间
// 这里没有写是因为可以减少代码量
while (p && j < i) {
// 说明没有找到
// p指针的位置移动一位
p = p->next;
// j的索引值加1
j++;
}
return p;
}
// 17. 单链表的查找---按值查找
LNode *List_LocateElem(LinkList L, ElemType e) {
/*
* 时间复杂度O(n)
*/
// 判断单链表是否为空,也可以不写,让后面while循环去判断,因为p为空,最后返回空值来处理,都可
// 这种写法在实际中更容易找到出错的原因,排bug
//考试的话可以不写,减少非必需的代码,防止写不下
if (List_IsEmpty(L)) {
return false;
}
LNode *p = L->next;
/*
* 这里有两个判断条件
* 1. p 代表p不为空,也可以按书上的写法p!=NULL,效果是一样的,如果p为空,代表单链表内没有查询到和e值相等的结点,此时p指向NULL(尾结点后面的NULL),返回p(NULL)
* 2. p->data!=e,代表p指针指向的结点中的数据域的值不等于e,如果查询到p指向的结点的数据域和e相等的话,会因为不满足这个条件结束循环,返回p指向的结点
* 注:这里的第二个条件也可以在while循环里写一个if语句,效果一样,只是这种写法,不如上面的写法精炼简洁,考试有可能会因为行数太多导致地方不够写,如下:
* while(p!=NULL){
* if(p->data==e){
* break;
* }
* }
* return p;
*/
while (p && p->data != e) {
// 没有找到结点,移动p指针
p = p->next;
}
return p;
}
// 18. 单链表的打印操作
Status List_PrintElem(LinkList L) {
/*
* 时间复杂度O(n)
*/
// 判断单链表是否为空
if (List_IsEmpty(L)) {
return false;
}
// 声明一个指针p指向头结点的下一个位置,也就是首元结点
LNode *p = L->next;
printf("单链表的内容输出:");
// 判断当前结点是否为空,如果为空,终止循环
while (p != NULL) {
printf("%d ", p->data);
// 输出之后将p指针移动到下一个位置
p = p->next;
}
printf("\n");
// 注:这里的p指针为静态分配的变量,不是动态分配,因此不需要通过free来释放空间,该变量会随着函数执行的完成而自动释放空间
return true;
}
// 19. 两个单链表的合并1
LinkList List_MergeFirst(LinkList L1, LinkList L2, LinkList L3) {
/*
* 两个链表按从小到大合并(从大到小也一样)
* 假设:L1长度m,L2长度n,m>n
* 时间复杂度O(n)
* 方法说明:将两个链表的结点都放在第三个链表中,这有个弊端,如果L1或者L2销毁了,那么L3也不能用了
*/
// 对两个链表进行判空操作,不能为空
if (!L1 || !L2) {
return false;
}
// p,q指针分别指向L1,L2的首元结点,r指针指向L3的头结点
LNode *p = L1->next;
LNode *q = L2->next;
LNode *r = L3;
// 如果p或者q的结点为空,说明有一个链表已经没有了,直接把另外一个链表都放在这个链表的后面就好
while (p && q) {
/*
* if p小于q的值,则r指向p,否则指向q
* 然后移动r指针到那个结点
* 最后移动p或者q到下一个位置
*/
if (p->data <= q->data) {
r->next = p;
r = p;
p = p->next;
} else {
r->next = q;
r = q;
q = q->next;
}
}
/*
* 这里说明
* 循环结束后,如果有一个链表的结点没有了,则直接把另外一个链表的剩余结点都放进来
* 这里要判断是哪个链表不为空,那个空
*/
if (p) {
r->next = p;
}
if (q) {
r->next = q;
}
// 这里也可以用这个写法,减少代码量,考试比较推荐,上面的比较清晰好理解
// r->next = p ? p : q;
return L3;
}
// 20. 两个单链表的合并2
LinkList List_MergeSecond(LinkList L1, LinkList L2) {
/*
* 两个链表按从小到大合并(从大到小也一样)
* 假设:L1长度m,L2长度n,m>n
* 时间复杂度O(n)
* 方法说明:这里将L1的头结点作为了第三方的头结点,这么做的好处是节省空间,两个链表的结点都放在这里面,
* 合并完成后会销毁L2的头结点,这个有个弊端,合并完成后,L1和L2就不存在了
* 这种写法比较多,题目常考
*/
// 对两个链表进行判空操作,不能为空
if (!L1 || !L2) {
return false;
}
// p,q指针分别指向L1,L2的首元结点,r指针指向L1的头结点
LNode *p = L1->next;
LNode *q = L2->next;
LNode *r = L1;
// 如果p或者q的结点为空,说明有一个链表已经没有了,直接把另外一个链表都放在这个链表的后面就好
while (p && q) {
/*
* if p小于q的值,则r指向p,否则指向q
* 然后移动r指针到那个结点
* 最后移动p或者q到下一个位置
*/
if (p->data <= q->data) {
r->next = p;
r = p;
p = p->next;
} else {
r->next = q;
r = q;
q = q->next;
}
}
/*
* 这里说明
* 循环结束后,如果有一个链表的结点没有了,则直接把另外一个链表的剩余结点都放进来
* 这里要判断是哪个链表不为空,那个空
*/
if (p) {
r->next = p;
}
if (q) {
r->next = q;
}
// 这里也可以用这个写法,减少代码量,考试比较推荐
// r->next = p ? p : q;
free(L2);
return L1;
}
// 21. 单链表的清空
Status List_Clear(LinkList L){
/*
* 时间复杂度O(n)
* 清空就是把带头结点的单链表除了头结点以外的所有结点全部删除,销毁的话会把头结点也删除
* 如果是不带头结点的单链表,清空和销毁其实没啥差别
*/
if(List_IsEmpty(L)){
return true;
}
LNode *p=L->next;
while (L->next){
L->next=L->next->next;
free(p);
}
return true;
}