【C语言15】单链表,(对于二级指针与一级指针应用的详细讲述)

文章目录

单链表

1.单链表的介绍

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表

中的指针链接次序实现的 。

在上篇博客中,我们可以很清晰的看到顺序表的结构,但是链表不可以,链表的链接就是由指针指引的,

一个数据,他可能间隔着N个内存空间,但是它们却又是实实在在相连的,为了详细说明链表,我准备这么几张图片:

我们看到,单链表就像一个火车,由一个指针来确定他们不同节点的位置,从而串接起来,但是我们要了解一件事,图片中的箭头是虚拟的,是想象的,我们能确定下一个节点位置只有指针;

2.单链表的实现

c 复制代码
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
typedef int SeDataType;
//定义结点
typedef struct SeqList
{
	SeDataType data;
	struct SeqList* next;
}SeNode;
//开辟节点
SeNode* BuySeqListNode(SeNode* phead, SeDataType x)
//销毁
void SeqListDestroy(SeNode** phead);
//打印
void SeqListPrint(SeNode* phead);
//尾插
void SeqListPushBack(SeNode** phead, SeDataType x);
//尾删
void SeqLIstPopBack(SeNode** phead);
//头插
void SeqListPushFront(SeNode** phead, SeDataType x);
//头删
void SeqListPopFront(SeNode** phead);
//pos位置之后插入
void SeqListInsterAfter(SeNode* pos, SeDataType x);
//删除pos位置
void SeqListEraseAfter(SeNode* pos);

为了实现单链表,我们需要实现以上接口

2.1.1单链表结点的创建与销毁

复制代码
//初始化
SeNode* BuySeqListNode(SeDataType x)
{
	SeNode* newnode = (SeNode*)malloc(sizeof(SeNode));//创建结点
	if (newnode == NULL)//判断结点是否生成成功
	{
		perror("BuySeqListNode");
		exit(-1);
	}
	newnode->data = x;//赋值
	newnode->next = NULL;
	return newnode;//返回结点地址
}
//销毁
void SeqListDestroy(SeNode** phead)
{
	assert(phead);//判断是否为空
	while (*phead)//循环free空间
	{
		SeNode* next = (*phead)->next;//记录下一个节点,避免链接丢失
		free(*phead);//释放
		*phead = next;
	}
	*phead = NULL;//置空
}

当读完这串代码后,我们会发现这两个函数使用了完全不一样的指针类型.开辟节点使用的是一级指针,销毁结点使用的是二级指针.要注意,这是本篇博客难度最高的知识点,考验各位对指针的理解程度

在调用PushBack函数后,我们成功创建了一个节点node,我们在此时这个结点是孤立的

那么如何让让plist和node链接起来呢.那就是指针.,在此时,如果我们想要用pilst链接node,必须传递plist的地址,只有传递地址才能让plist的指向改变.如果不传递地址,后续操作只是一份临时拷贝

此时,一个纠结点就出现了.我plist本事就是指针,为什么要传递指针的地址呢.

我们来看一张图片

这张图生动的展示了指针本质上的区别.指针也是一种变量,是变量就会有地址.想要改变变量,就必须通过地址改变;在对plist进行修改的时候,虽然plist就是指针,但想要改变他存贮的值,就必须传递本身的地址,这就是为什么传递二级指针的原因.

2.1.2单链表尾插

如何进行尾插操作呢,我们要想明白一件事情,在我们创建的Sqnode类型中, next指向下一个结点.所以,如果我们想要尾插,必须找到next的前一个结点进行链接

复制代码
void SeqListPushBack(SeNode** phead, SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* node = BuySeqListNode(x);//开辟节点
	if (*phead == NULL)//如果*phead为空,代表链表为空,之间赋值替换即可
	{
		*phead = node;
	}
	else
	{
		SeNode* tail = *phead;
		while (tail->next != NULL)//如果不为空,则遍历链表,在最后一个结点进行链接
		{
			tail = tail->next;
		}
		tail->next = node;
	}
}

此时,我们完成了尾插接口的实现,如何判断程序是否正确呢,我们可以通过调试直观地观察,也可以提前将打印接口实现观察,我们使用实现打印接口.

2.1.3单链表打印

应为我们只是要打印而无需任何改变指针的操作,所以只需要传递一级指针遍历整个数组即可

复制代码
void SeqListPrint(SeNode* phead)
{
	SeNode* cur = phead;//记录位置
	while (cur)
	{
		printf("%d ", cur->data);//打印
		cur = cur->next;//赋值遍历
	}
}

程序正确,成功运行

2.1.4尾删

尾删的操作与尾插类似,不同的是我们需要遍历到倒数第二个结点,通过倒数第二个节点的next来释放最后一个节点达成尾删操作.因为需要改变内容,所以依旧传递二级指针

复制代码
void SeqListPopBack(SeNode** phead)
{
	assert(phead);//判断指针是否为空
	assert(*phead);//判断指针指向的内容是否为空
	if ((*phead)->next == NULL)//如果只有一个节点,直接释放
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		SeNode* tail = *phead;
		while (tail->next->next != NULL)//如果多个,遍历到末尾的前一个释放
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;                       
	}
}

2.1.5头插

头插需要注意的是对于指针的操作,在有多个节点时,需要考虑如何在头插的同时链接原头结点

而在一个节点是直接替换赋值即可.

复制代码
void SeqListPushFront(SeNode** phead, SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* newnode = BuySeqListNode(x);//创建新节点
	if (*phead == NULL)//当只有一个节点的时候,直接赋值
	{
		*phead = newnode;
	}
	else//多个节点是先链接在赋值
	{
		newnode->next = *phead;
		*phead = newnode;
	}
}

运行成功

2.1.6头删

头删要注意的与头插类似,直接上图

复制代码
//头删
void SeqListPopFront(SeNode** phead)
{
	assert(phead);//判断指针是否为空
	assert(*phead);//判断指针指向的内容是否为空
	if ((*phead)->next == NULL)//只有一个节点时,直接释放
	{
		free(*phead);
		*phead = NULL;
	}
	else//多个节点时,记录新头结点,释放原头结点
	{	
		SeNode* pphead = (*phead)->next;
		free(*phead);
		*phead = pphead;
	}
}

运行成功

2.1.7查找

该接口原理与打印接口相同,同是遍历链表,不同点为该函数会返回一个地址

复制代码
SeNode* SeqListFind(SeNode* phead,SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* cur = phead;//赋值遍历
	while (cur->next != NULL)
	{
		if (cur->data == x)//判断相等
		{
			return cur;//相等返回地址
		}
		cur = cur->next;
	}
	return NULL;	//不等返回NULL;
}

2.1.8在pos位置之后插入数据

复制代码
void SeqListInsterAfter(SeNode**phead,SeNode* pos, SeDataType x)
{
	assert(pos);//判断指针是否为空
	assert(*phead);
	if (pos == *phead)//如果相等,就是头插
	{
		SeqListPushFront(phead,x);
	}
	else
	{
		SeNode* node = BuySeqListNode(x);
		SeNode* pphead = pos->next;//记录原结点链接的数据
		node->next = pphead;//链接
		pos->next = node;
	}
}

修改成功

2.1.9删除pos位置

复制代码
void SeqListEraseAfter(SeNode** phead, SeNode* pos)
{
	assert(pos);//判断空指针
	if (pos==*phead)//如果相等就是头删
	{
		SeqListPopFront(phead);
	}
	else
	{
		SeNode* cur = *phead;
		while (cur->next != pos)//记录pos的前一个位置
		{
			cur = cur->next;
		}
		cur->next = pos->next;//链接
		free(pos);//释放
		pos = NULL;
	}
}

完整代码

复制代码
//SeqList.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
typedef int SeDataType;
//定义结点
typedef struct SeqList
{
	SeDataType data;
	struct SeqList* next;
}SeNode;
//初始化
void SeqListInit(SeNode** phead);
//销毁
void SeqListDestroy(SeNode** phead);
//打印
void SeqListPrint(SeNode* phead);
//尾插
void SeqListPushBack(SeNode** phead, SeDataType x);
//尾删
void SeqListPopBack(SeNode** phead);
//头插
void SeqListPushFront(SeNode** phead, SeDataType x);
//头删
void SeqListPopFront(SeNode** phead);
//pos位置之后插入
void SeqListInsterAfter(SeNode** phead,SeNode* pos, SeDataType x);
//删除pos位置
void SeqListEraseAfter(SeNode** phead, SeNode* pos);

//SeqList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//初始化
SeNode* BuySeqListNode(SeDataType x)
{
	SeNode* newnode = (SeNode*)malloc(sizeof(SeNode));//创建结点
	if (newnode == NULL)//判断结点是否生成成功
	{
		perror("BuySeqListNode");
		exit(-1);
	}
	newnode->data = x;//赋值
	newnode->next = NULL;
	return newnode;//返回结点地址
}
//销毁
void SeqListDestroy(SeNode** phead)
{
	assert(phead);//判断是否为空
	while (*phead)//循环free空间
	{
		SeNode* next = (*phead)->next;//记录下一个节点,避免链接丢失
		free(*phead);//释放
		*phead = next;
	}
	*phead = NULL;//置空
}
//打印
void SeqListPrint(SeNode* phead)
{
	SeNode* cur = phead;//记录位置
	while (cur)
	{
		printf("%d->", cur->data);//打印
		cur = cur->next;//赋值遍历
	}
	printf("NULL\n");
}
//尾插
void SeqListPushBack(SeNode** phead, SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* node = BuySeqListNode(x);//开辟节点
	if (*phead == NULL)//如果*phead为空,代表链表为空,之间赋值替换即可
	{
		*phead = node;
	}
	else
	{
		SeNode* tail = *phead;
		while (tail->next != NULL)//如果不为空,则遍历链表,在最后一个结点进行链接
		{
			tail = tail->next;
		}
		tail->next = node;
	}
}
//尾删
void SeqListPopBack(SeNode** phead)
{
	assert(phead);//判断指针是否为空
	assert(*phead);//判断指针指向的内容是否为空
	if ((*phead)->next == NULL)//如果只有一个节点,直接释放
	{
		free(*phead);
		*phead = NULL;
	}
	else
	{
		SeNode* tail = *phead;
		while (tail->next->next != NULL)//如果多个,遍历到末尾的前一个释放
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;                       
	}
}
//头插
void SeqListPushFront(SeNode** phead, SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* newnode = BuySeqListNode(x);//创建新节点
	if (*phead == NULL)//当只有一个节点的时候,直接赋值
	{
		*phead = newnode;
	}
	else//多个节点是先链接在赋值
	{
		newnode->next = *phead;
		*phead = newnode;
	}
}
//头删
void SeqListPopFront(SeNode** phead)
{
	assert(phead);//判断指针是否为空
	assert(*phead);//判断指针指向的内容是否为空
	if ((*phead)->next == NULL)//只有一个节点时,直接释放
	{
		free(*phead);
		*phead = NULL;
	}
	else//多个节点时,记录新头结点,释放原头结点
	{	
		SeNode* pphead = (*phead)->next;
		free(*phead);
		*phead = pphead;
	}
}
SeNode* SeqListFind(SeNode* phead,SeDataType x)
{
	assert(phead);//判断指针是否为空
	SeNode* cur = phead;//赋值遍历
	while (cur->next != NULL)
	{
		if (cur->data == x)//判断相等
		{
			return cur;//相等返回地址
		}
		cur = cur->next;
	}
	return NULL;	//不等返回NULL;
}
//pos位置之后插入
void SeqListInsterAfter(SeNode**phead,SeNode* pos, SeDataType x)
{
	assert(pos);//判断指针是否为空
	assert(*phead);
	if (pos == *phead)//如果相等,就是头插
	{
		SeqListPushFront(phead,x);
	}
	else
	{
		SeNode* node = BuySeqListNode(x);
		SeNode* pphead = pos->next;//记录原结点链接的数据
		node->next = pphead;//链接
		pos->next = node;
	}
}
//删除pos位置
void SeqListEraseAfter(SeNode** phead, SeNode* pos)
{
	assert(pos);//判断空指针
	if (pos==*phead)//如果相等就是头删
	{
		SeqListPopFront(phead);
	}
	else
	{
		SeNode* cur = *phead;
		while (cur->next != pos)//记录pos的前一个位置
		{
			cur = cur->next;
		}
		cur->next = pos->next;//链接
		free(pos);//释放
		pos = NULL;
	}
}


//SeqListTest.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
void SeqListTest()
{
	SeNode* plist = NULL;
	SeqListPushFront(&plist, 5);
	SeqListPushFront(&plist, 4);
	SeqListPushFront(&plist, 3);
	SeqListPushFront(&plist, 2);
	SeqListPushFront(&plist, 1);
	SeqListPushFront(&plist, 0);
	SeqListPrint(plist);
	SeNode* ptr = SeqListFind(plist, 3);
	if (ptr != NULL)
	{
		SeqListEraseAfter(&plist, ptr);
	}
	else
	{
		printf("数据有误\n");
	}
	SeqListPrint(plist);
	/*SeNode* ptr = SeqListFind(plist, 3);
	if (ptr != NULL)
	{
		printf("%p %d", ptr,ptr->data);
	}
	else
	{
		printf("数据有误\n");
	}*/
	//SeqListPopFront(&plist);
	//SeqListPopFront(&plist);
	//SeqListPopFront(&plist);
	//SeqListPopFront(&plist);
	//SeqListPopFront(&plist);
	//SeqListPrint(plist);
	//SeqListPopFront(&plist);
	//SeqListPrint(plist);
	/*SeqListPushBack(&plist, 1);
	SeqListPushBack(&plist, 2);
	SeqListPushBack(&plist, 3);
	SeqListPushBack(&plist, 4);
	SeqListPushBack(&plist, 5);
	SeqListPrint(plist);
	SeqListPopBack(&plist);
	SeqListPrint(plist);
	SeqListPopBack(&plist);
	SeqListPrint(plist);
	SeqListPopBack(&plist);
	SeqListPrint(plist);
	SeqListPopBack(&plist);
	SeqListPrint(plist);
	SeqListPopBack(&plist);
	SeqListPrint(plist);
	SeqListDestroy(&plist);*/

}
int main()
{
	SeqListTest();
	return 0;
}

ont(&plist);

//SeqListPrint(plist);

/SeqListPushBack(&plist, 1);
SeqListPushBack(&plist, 2);
SeqListPushBack(&plist, 3);
SeqListPushBack(&plist, 4);
SeqListPushBack(&plist, 5);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListDestroy(&plist);
/

}

int main()

{

SeqListTest();

return 0;

}

复制代码
相关推荐
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区12 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大13 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai13 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa13 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio