单链表介绍

目录

  • 1. 链表的概念
    • 1.1 链表
    • 1.2 链表的结构
  • 2. 链表的实现
    • 2.1 链表打印
      • 2.1.1 构造一个链表
      • 2.1.2 打印链表
        • 2.1.2.1 打印链表函数
    • 2.2 链表的尾插
      • 2.2.1 分析
      • 2.2.2 尾插函数代码
    • 2.3 链表的头插
      • 2.3.1 分析
      • 2.3.2 头插函数代码
    • 2.4 链表的尾删
      • 2.4.1 分析
      • 2.4.2 尾删函数代码
    • 2.5 链表的头删
      • 2.5.1 分析
      • 2.5.2 头删函数代码
    • 2.6 链表的节点前面插入
      • 2.6.1 分析
      • 2.6.2 节点前面插入函数代码
    • 2.7 链表的节点后面插入
      • 2.7.1 分析
      • 2.7.2 节点后面插入函数代码
    • 2.7 链表的删除
      • 2.7.1 删除`pos`位置节点
        • 2.7.1.1 分析
        • 2.7.1.2 删除pos位置节点函数
      • 2.7.2 删除pos位置之后节点
        • 2.7.2.1 分析
        • 2.7.1.2 删除pos位置之后节点函数
    • 2.8 链表的销毁
      • 2.8.1 分析
      • 2.8.2 销毁函数
  • 3. 附代码
    • SList.h
    • SList.c
    • test.c

1. 链表的概念

1.1 链表

链表也是线性表的一种。

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
单链表介绍_第1张图片
链表的结构跟火车车厢相似,每节车厢都是独立存在的,需要将火车的某节车厢去掉/加上,不会影响其他车厢。

1.2 链表的结构

单链表介绍_第2张图片
与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点”。
节点的组成主要有两个部分:当前节点要保存的数据和保存下一个节点的地址(指针变量)。
单链表介绍_第3张图片

图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。

为什么还需要指针变量来保存下一个节点的位置?
链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),我们需要通过指针变量来保存下一个节点位置才能从当前节点找到下一个节点。

链表节点的结构

struct SListNode
{
        int data;
        struct SListNode* next;
}

当我们想要保存一个整型数据时,实际是向操作系统申请了一块内存,这个内存不仅要保存整型数据,也需要保存下一个节点的地址(当下一个节点为空时保存的地址为空)。

当我们想要从第一个节点走到最后一个节点时,只需要在前一个节点拿上下一个节点的地址(下一个节点的钥匙)就可以了。

2. 链表的实现

2.1 链表打印

2.1.1 构造一个链表

test.c文件中
先要用malloc向操作系统申请一块空间,然后让第一个节点的数据等于1.

	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	node1->data = 1;

然后用同样的方法再申请三个节点

SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;

用图展示就是这样
单链表介绍_第4张图片
然后让node1保存node2的地址,再让node2保存node3的地址,再让node3保存node4的地址,最后让node4的next置为NULL

用代码实现

    node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

2.1.2 打印链表

先定义一个变量让它指向第一个节点。

    SLNode* plist = node1;
    SLPrint(plist);

将链表的地址传过去。
然后在构造打印函数,因为之后可能还涉及到链表的打印,所以定义了一个SList.c用来存储链表所用到的函数。

2.1.2.1 打印链表函数

这里用循环来实现打印,用可以用phead直接来访问。但是注意,当代码走到第14行的时候,此时phead已经变成NULL了。若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就找不到第一个节点的地址。
所以用一个新的指针变量phead来存储。

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

最终可以得到这样
单链表介绍_第5张图片

2.2 链表的尾插

2.2.1 分析

在实现链表尾插时我们需要考虑两点:
1.链表为空时,申请新节点,新节点作为头节点。
单链表介绍_第6张图片

2.当链表不为空时,申请新节点,原链表中的为节点的next指针指向新节点的地址。
单链表介绍_第7张图片

2.2.2 尾插函数代码

通过指针pcur = pcur->next,跳往下一个节点。

void SLPushBack(SLNode** pphead, SLDataType x) {
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL) 
	{
		*pphead = node;
		return;
	}
	//说明链表不为空,找尾
	SLNode* pcur = *pphead;
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	pcur->next = node;
}

2.3 链表的头插

2.3.1 分析

单链表介绍_第8张图片
在链表第一个节点之前插入x=4。
先让nodenext保存phead,再让4成为新的头节点。
单链表介绍_第9张图片
而当链表为空时也同样适用。

2.3.2 头插函数代码

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

实现的结果
单链表介绍_第10张图片

2.4 链表的尾删

2.4.1 分析

要实现链表的尾删除,首先要找到尾节点。
cur指针找,还需要记录尾节点的前面一个节点用prev记录。然后将prevnext置为NULL
单链表介绍_第11张图片

2.4.2 尾删函数代码

void SLPopBack(SLNode** pphead) 
{
	assert(pphead);//第一个节点不能为空
	assert(*pphead);//只有一个节点的情况
	if ((*pphead)->next == NULL)
	 {
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		//有多个节点的情况
		//找尾结点和尾结点的前一个节点
		SLNode* prev = NULL;
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		//prev的next指针不再指向ptail,而是指向ptail的下一个节点
		prev->next = ptail->next;
		free(ptail);
		//为什么必须要置为空?代码后面明明没有再使用ptail?打印链表方法里有判断节点的地址是否为空
		ptail = NULL;
	}
}

2.5 链表的头删

2.5.1 分析

单链表介绍_第12张图片
首先使用临时节点的指针指向头节点,然后将头节点指向新的头,最后把临时指针指向的节点释放掉。
单链表介绍_第13张图片
当只有一个节点时也可以处理

2.5.2 头删函数代码

void SLPopFront(SLNode** pphead)
 {
	assert(pphead);
	assert(*pphead);
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;//出于代码规范
}

2.6 链表的节点前面插入

2.6.1 分析

在pos前插入一个x=11,先找到pos前的一个节点,然后处理2和3。将prev->next=node;再将node->next=pos
注意在这里prev->next=nodenode->next=pos,位置可以互换,因为3的位置已经用pos来记录了,不会造成丢失。
单链表介绍_第14张图片
不考虑链表为空时的情况。
当链表里面只有一个节点时,就相当于pos刚好是头结点,实行头插。
单链表介绍_第15张图片

2.6.2 节点前面插入函数代码

//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
 {
	assert(pphead);
	//约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLBuyNode(x);
	//pos刚好是头结点,头插
	if (pos == *pphead) {
		node->next = *pphead;
		*pphead = node;
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)//第一次循环的时候(缺少pos刚好是第一个节点的判断)
	{
		prev = prev->next;
	}
	//prev node  pos
	node->next = pos;
	prev->next = node;
}

2.7 链表的节点后面插入

2.7.1 分析

单链表介绍_第16张图片
与链表的节点前面插入不同的是,prev->next=nodenode->next=pos,位置不可以互换,因为没有记录3节点的位置。
所以必须先执行node->next=pos再执行prev->next=node

2.7.2 节点后面插入函数代码

void SLInsertAfter(SLNode* pos, SLDataType x) {
	assert(pos);
	SLNode* node = SLBuyNode(x);
	//pos  node  pos->next
	node->next = pos->next;
	pos->next = node;
}

2.7 链表的删除

2.7.1 删除pos位置节点

2.7.1.1 分析

首先我们要先找到pos节点和找pos的前一个节点prev。执行prev->next = pos->next,再实现free(pos)
单链表介绍_第17张图片

2.7.1.2 删除pos位置节点函数
void SLErase(SLNode** pphead, SLNode* pos) 
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (pos == *pphead) {
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//prev pos pos->next
	prev->next = pos->next;
	free(pos);
	pos = NULL;//只是规范
}

2.7.2 删除pos位置之后节点

2.7.2.1 分析

单链表介绍_第18张图片
先判断pospos的下一个节点是否为空。
del存储pos的下一个节点,然后执行pos->next = del->next;最后free(del)

2.7.1.2 删除pos位置之后节点函数
void SLEraseAfter(SLNode* pos)
 {
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

2.8 链表的销毁

2.8.1 分析

单链表介绍_第19张图片
通过循环从前往后依次进行,而在销毁节点之前先把下一个个节点保存下来。

2.8.2 销毁函数

void SLDesTroy(SLNode** pphead) {
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3. 附代码

SList.h

#pragma once
#include
#include
#include
#include

//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode {
	SLDataType data;//要保存的数据
	struct SListNode* next;
}SLNode;

//我来创建几个结点组成一个链表,并打印链表
void SLPrint(SLNode* phead);

//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);

//找节点,这里的第一个参数写一级指针还是二级指针
//这里传一级实际上就可以了,因为不改变头结点
//但是这里要写二级指针,因为要保持接口一致性
SLNode* SLFind(SLNode** pphead, SLDataType x);

//指定位置的插入删除
//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);

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

SList.c

#include"SList.h"

void SLPrint(SLNode* phead) {
	//循环打印
	//可以用phead直接来访问,但是注意,当代码走到
	//第14行的时候,此时phead已经变成NULL了
	//若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就
	//找不到第一个节点的地址
	SLNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
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 = pcur->next;
	}
	pcur->next = node;
}
void SLPushFront(SLNode** pphead, SLDataType x) {
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新节点跟头结点连接起来
	node->next = *pphead;//plist
	//让新的节点成为头结点
	*pphead = node;
}
void SLPopBack(SLNode** pphead) {
	assert(pphead);
	//第一个节点不能为空
	assert(*pphead);
	//只有一个节点的情况
	if ((*pphead)->next == NULL) {
		//直接把头结点删除
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//有多个节点的情况
		//找尾结点和尾结点的前一个节点
		SLNode* prev = NULL;
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		//prev的next指针不再指向ptail,而是指向ptail的下一个节点
		prev->next = ptail->next;
		free(ptail);
		//为什么必须要置为空?代码后面明明没有再使用ptail?打印链表方法里有判断节点的地址是否为空
		ptail = NULL;
	}
}
void SLPopFront(SLNode** pphead) {
	assert(pphead);
	assert(*pphead);
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;//出于代码规范
}

//查找第一个为X的节点
SLNode* SLFind(SLNode** pphead, SLDataType x) {
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}


//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x) {
	assert(pphead);
	//约定链表不能为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLBuyNode(x);
	//pos刚好是头结点,头插
	if (pos == *pphead) {
		node->next = *pphead;
		*pphead = node;
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)//第一次循环的时候(缺少pos刚好是第一个节点的判断)
	{
		prev = prev->next;
	}
	//prev node  pos
	node->next = pos;
	prev->next = node;
}
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x) {
	assert(pos);
	SLNode* node = SLBuyNode(x);
	//pos  node  pos->next
	node->next = pos->next;
	pos->next = node;
}
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos) {
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (pos == *pphead) {
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	//prev pos pos->next
	prev->next = pos->next;
	free(pos);
	pos = NULL;//只是规范
}
//删除pos之后节点
void SLEraseAfter(SLNode* pos) {
	assert(pos && pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}
//销毁链表
void SLDesTroy(SLNode** pphead) {
	assert(pphead);
	SLNode* pcur = *pphead;
	//循环删除
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

test.c

#include"SList.h"

void slttest() {
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;

	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;

	//打印链表
	SLNode* plist = node1;
	SLPrint(plist);
}

void slttest01() {
	SLNode* plist = NULL;
	//尾插
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);//1->2->3->4->NULL
	SLPrint(plist);
	//SLPushFront(&plist, 1);
	//SLPushFront(&plist, 2);
	//SLPushFront(&plist, 3);
	//SLPushFront(&plist, 4);//4->3->2->1->NULL
	//尾删
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//SLPopBack(&plist);
	//头删
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLPopFront(&plist);
	//SLNode* find = SLFind(&plist, 4);
	//SLInsert(&plist, find,11);//1->11->2->3->4->NULL
	//在指定位置之后插入数据
	//SLInsertAfter(find, 100);
	//删除pos位置的节点
	//SLErase(&plist, find);//1->2->3->NULL
	//删除pos之后的节点
	//SLEraseAfter(find);
	//销毁链表
	SLDesTroy(&plist);
	SLPrint(plist);
}
int main() {
	//slttest();
	slttest01();
	return 0;
}

你可能感兴趣的:(数据结构,c语言,数据结构)