C语言数据结构-单链表


1 链表的概念和结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针衔接实现的。
在这里插入图片描述
假设每节车厢的车门是上锁的,需要不同的钥匙才能通向下一节车厢,那怎么能在车头用一把钥匙走到最后一节车厢?
我们在每节车厢放下一节车厢的钥匙不就行了!
在这里插入图片描述
链表中每节车厢是独立申请的空间,我们交"节点"/“结点”,节点的组成主要是:当前节点保存的数据和保存下一个节点的地址(指针变量)。
链表中的每个节点是独立申请的,我们需要用指针变量来保存下一个节点位置才能从当前节点到下一个节点

在这里插入图片描述

2 单链表的实现

创建SList.h头文件定义函数,SList.c源文件声明函数
test.c测试链表,我在test.c创建了10个测试函数,一一对应10个函数,方便后来者观察调试

在这里插入图片描述
在这里插入图片描述

我们初学链表,定义链表的结构为整型

//定义链表的结构
typedef int SLDataType;
typedef struct SListNode
{
    
    
	SLDataType data;	//保存的数据
	struct SListNode* next;		//指针变量存放下一个节点的地址
}SLNode;

2.1 打印链表

第一次打印:pcur指针变量保存头结点的地址,对pcur解引用拿到next指针变量的地址,在赋值给pcur,进入循环判断

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

2.2 尾插

插入函数是要开辟内存空间的,所以写创建新节点的函数,后续函数也会使用

`SList.c`下
//创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
    
    
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

首先,我们要理解二级指针放村一级指针的地址
plist是指针,存的是地址,SLPushBack(plist, 1);把plist传给phead,是把plist的值传给phead
但是SLPushBack应该把plist的地址传给phead,所以采用二级指针
第一个节点 *plist 需要形参**phead接收
第一个节点的地址 plist 需要形参 *phead接收
指向第一个节点的指针的地址 &plist 需要形参 phead接收
旁边辨别就书写成pphead

在这里插入图片描述

`SList.c`下
void SLPushBack(SLNode** pphead, SLDataType x)	//保存第一个节点的指针的地址
{
    
    
	assert(pphead);		//尾插节点的指针地址不能为NULL
	//创建一个节点
	SLNode* node = SLBuyNode(x);
	//链表为空,返回NULL
	if (*pphead == NULL)
	{
    
    
		*pphead = node;
		return;
	}
	//链表不为空,找尾
	SLNode* pcur =*pphead;
	while (pcur->next)//相当于pcur->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

2.3 头插

在这里插入图片描述

SList.c

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

2.4 尾删

在这里插入图片描述

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点也就是NULL
	//pre->next = NULL;都行
	pre->next = ptail->next;
	
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

2.5 头删

在这里插入图片描述

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}
void test5()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->2->3->NULL

	SLPopFront(&plist); //2->3->NULL
	SLPopFront(&plist); //3->NULL
	//SLPopFront(&plist); //NULL

	//SLPopFront(&plist); //assert成功
	SLPrint(plist);
	SLDestory(&plist);
}

2.6 定点前插入数据

SLFind函数根据第一次出现的数据x的位置,这是缺陷,SLInsert函数处理新添加的node前后节点的关系
在这里插入图片描述

//这个函数是有缺陷的,它是查到的第一次出现x的位置,若想在第二次出现x的位置插入数据是不能的
SLNode* SLFind(SLNode** pphead, SLDataType x)  
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

test.c

void test6()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->3->2->3->NULL
	printf("定点插入数据前\n");
	SLPrint(plist);

	printf("定点插入数据后\n");
	SLNode* find = SLFind(&plist, 3);
	SLInsert(&plist, find, 9);//1->9->3->2->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.7 定点后插入数据

在这里插入图片描述

void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}
void test7()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);//1->2->3->NULL
	printf("定点后插入数据前\n");
	SLPrint(plist);

	printf("定点后插入数据后\n");
	SLNode* find = SLFind(&plist, 2);
	SLInsertAfter(find, 10);	//1->2->10->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.8 删除pos节点

在这里插入图片描述

void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}
void test8()
{
    
    
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 3);//1->2->1->3->NULL

	SLNode* find = SLFind(&plist, 1);  //找到1的下标
	SLErase(&plist, find);//2->1->3->NULL

	SLPrint(plist);
	SLDestory(&plist);
}

2.9 删除pos后的节点

在这里插入图片描述

void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

2.10 销毁链表

头结点记得置空!
头结点记得置空!
头结点记得置空!

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}

3 完整代码

3.1 SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>


//定义链表的结构
typedef int SLDataType;
typedef struct SListNode
{
    
    
	SLDataType data;	//保存的数据
	struct SListNode* next;		//指针变量存放下一个节点的地址
}SLNode;

//1 打印链表
void SLPrint(SLNode* phead);

//2 尾插
void SLPushBack(SLNode** pphead, SLDataType x);

//3 头插
void SLPushFront(SLNode** pphead, SLDataType x);

//4 尾删
void SLPopBack(SLNode** pphead);

//5 头删
void SLPopFront(SLNode** pphead);

//SLFind()找到要查找数据的下标
//找节点的函数这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
//缺点:这个函数有一定的局限性,他找到数据x的下标是第一次出现的x的下标后,不会找后续的x的下标
SLNode* SLFind(SLNode** pphead, SLDataType x);

//6 定点前插入数据
void SLInsert(SLNode** pphead, SLNode* pos,SLDataType x);

//7 定点后插入数据
void SLInsertAfter(SLNode* pos,SLDataType x);

//8 删除pos节点
void  SLErase(SLNode** pphead, SLNode* pos);

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos);

//10 销毁链表
void SLDestory(SLNode** pphead);

3.2 SList.c

#include"SList.h"

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

//创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
    
    
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

void SLPushBack(SLNode** pphead, SLDataType x)	//保存第一个节点的指针的地址
{
    
    
	assert(pphead);
	//创建一个节点
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
    
    
		*pphead = node;
		return;
	}
	//链表不为空,找尾
	SLNode* pcur =*pphead;
	while (pcur->next)//相当于pcur->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点
	//pre->next = ptail->next;
	pre->next = NULL;
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

SLNode* SLFind(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}



3.3 test.c

本来写了一个test()函数,因为笔者调试起来方便,但是为了让读者方便阅读,分成了10个测试函数

#include"SList.h"

void SLPrint(SLNode* phead)
{
    
    
	SLNode* pcur = phead;
	while (pcur)
	{
    
    
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n"); //手动添加NULL  
}

//创建新的节点的函数
SLNode* SLBuyNode(SLDataType x)
{
    
    
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

void SLPushBack(SLNode** pphead, SLDataType x)	//保存第一个节点的指针的地址
{
    
    
	assert(pphead);
	//创建一个节点
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
    
    
		*pphead = node;
		return;
	}
	//链表不为空,找尾
	SLNode* pcur =*pphead;
	while (pcur->next)//相当于pcur->next!=NULL
	{
    
    
		pcur = pcur->next;
	}
	pcur->next = node;
}

void SLPushFront(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新结点跟头结点连接起来
	node->next = *pphead;
	//让新节点成为头结点
	*pphead = node;
}

void SLPopBack(SLNode** pphead)
{
    
    
	assert(pphead);

	assert(*pphead);	//第一个节点不能为空
	//判断只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
    
    
		free(*pphead);
		*pphead = NULL;
		return;
	}

	//找到尾节点和尾节点的前一个节点
	SLNode* pre = NULL;
	SLNode* ptail = *pphead;
	while (ptail->next!=NULL)
	{
    
    
		pre = ptail;
		ptail = ptail->next;
	}
	//pre的next指针不指向ptail,而是指向ptail的下一个节点
	//pre->next = ptail->next;
	pre->next = NULL;
	//因为在SLPrint(SLNode* phead)函数中有判断节点是否为空while (pcur),所以置空
	free(ptail);
	ptail = NULL;

}

void SLPopFront(SLNode** pphead)
{
    
    
	assert(pphead);
	assert(*pphead);	//第一个节点不能为空
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

SLNode* SLFind(SLNode** pphead, SLDataType x)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		if (pcur->data == x) {
    
    
			return pcur;
		}
		pcur = pcur->next;
	}
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
    
    
	assert(pphead);
	assert(pos);	//pos不能为空
	assert(*pphead);  //NULL前插入数据是荒谬的,所以assert第一个节点
	SLNode* node = SLBuyNode(x);

	//当只有一个结点(pos就是第一个节点)
	//if (((*pphead)->next) == NULL||pos==*pphead),可以简化成以下代码
	if (pos == *pphead) {
    
    
		node->next = *pphead;
		*pphead = node;
		return;
	}

	//找到pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	//处理pre node pos的位置
	node->next = pos;
	pre->next = node;
}

//7 定点后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
    
    
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//8 删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
    
    
	assert(*pphead);
	assert(pphead);
	assert(pos);
	//pos是头结点
	if (pos == *pphead)
	{
    
    
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//pos不是头结点,找pos的前一个节点
	SLNode* pre = *pphead;
	while (pre->next != pos)
	{
    
    
		pre = pre->next;
	}
	pre->next = pos->next;
	free(pos);
	pos = NULL;   //代码规范
}

//9 删除pos后的节点
void SLEraseAfter(SLNode* pos)
{
    
    
	//删除pos之后的节点也不能空
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

void SLDestory(SLNode** pphead)
{
    
    
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
    
    
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//头结点记得置空
	*pphead = NULL;
}



猜你喜欢

转载自blog.csdn.net/m0_73678713/article/details/134803943