线性表【单链表的实现】(带头结点)

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;
}

在这里插入图片描述

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值