C语言单链表、单链表的创建、打印单链表、尾插、头插、尾删、头删、查找、指定位置之前和之后插入、删除pos节点、删除pos之后的节点、销毁链表等介绍


前言

C语言单链表、单链表的创建、打印单链表、尾插、头插、尾删、头删、查找、指定位置之前和之后插入、删除pos节点、删除pos之后的节点、销毁链表等介绍


单链表的创建

  • 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
  • 简单来说就是物理上不连续,但是逻辑上连续。由一个一个的节点链接成链表。
  • 每个节点的本质是一个结构体
  • 结构体的成员为 当前节点保存的数据下一个节点的地址
    在这里插入图片描述

因此单链表节点创建如下:

// SList.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;
  • typedef int SLTDataType; 是为了方便每个节点数据类型的更改。

一、打印单链表

  • 后续每个函数都需要在单链表的头文件(SList.h)中声明,为了方便不过多说明。
  • 在测试源文件(test.c)中手动初始化每个节点,然后打印
  • 在测试源文件中,测试函数都要放在主函数中调用,为了方便,不过多说明。
// test.c
#include "SList.h"

void SLTNodetest01()
{
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;

	SLTNode* node5 = (SLTNode*)malloc(sizeof(SLTNode));
	node5->data = 5;

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

	SLTNode* plist = node1;
	
	// 打印链表函数
	SLTPrint(plist);
}

打印单链表函数的实现:

// SList.c
#include "SList.h"

// 打印单链表
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	
	while (pcur != NULL)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");

}
  • 我们习惯在接收到链表头节点后,用另一个变量(pcur)接收。
  • 如果pcur 不为空,则打印数据(pcur->data), 并让pcur指向下一个节点,循环打印
  • pcur为空,则退出循环,打印一个NULL和换行符。
  • 效果如下:

在这里插入图片描述

二、尾插

  • 顾名思义,在链表的尾部插入节点
  • 先创建一个节点,让原链表最后一个节点的地址,存放新节点的地址,让新节点的地址存放为NULL。

尾插函数的实现:

// SLish.c
// 创建新节点函数
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}

	newNode->data = x;
	newNode->next = NULL;

	return newNode;
}
// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newNode = SLTBuyNode(x);

	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLTNode* ptail = *pphead;

		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		ptail->next = newNode;
	}
}

  • 定义一个创建新节点的函数(SLTBuyNode),创建新节点后返回新节点的地址
  • 判断若头节点的地址为空,说明链表为空新节点的地址直接赋给头节点的地址
  • 若不为空,则找到链表最后一个节点(ptail)最后一个节点的地址为新节点的地址

测试尾插函数如下:

void SLTNodetest03()
{
	// 测试尾插

	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPrint(plist);

	SLTPushBack(&plist, 2);
	SLTPrint(plist);

	SLTPushBack(&plist, 3);
	SLTPrint(plist);

	SLTPushBack(&plist, 4);
	SLTPrint(plist);

	SLTPushBack(&plist, 5);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述

三、头插

  • 创建一个新的节点,新节点中存放的地址指向原链表头节点。
  • 再将原链表头节点的指针指向新的节点。

头插函数的实现:

// SList.c
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newNode = SLTBuyNode(x);

	newNode->next = *pphead;

	*pphead = newNode;

}

测试头插函数如下:

void SLTNodetest04()
{

	//测试头插
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 0);
	SLTPrint(plist);

	SLTPushFront(&plist, -1);
	SLTPrint(plist);

	SLTPushFront(&plist, -2);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述

四、尾删

  • 找到单链表的最后一个节点,将其free释放
  • 同时将最后一个节点之前的节点存储的地址(next)置为空(NULL)。

尾删函数的实现:

// SList.c
// 尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}

		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
	
}

测试尾删函数如下:

// test.c
void SLTNodetest05()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);


	 // 测试尾删
	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);
}
  • 先尾插5个节点,然后一次删除一个节点
  • 若对空链表进行删除,会报错

在这里插入图片描述

五、头删

  • 创建临时变量(next)存储单链表头节点中存放的下一个节点的地址
  • free释放头节点地址指向的空间
  • 再将临时变量next的地址,赋给头节点的地址

头删函数的实现:

// 头删
void SLTPopFront(SLTNode * *pphead)
{
	assert(pphead && *pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

测试头删函数如下:

void SLTNodetest06()
{

	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);


	// 测试头删
	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

}

效果如下:
在这里插入图片描述

六、查找

  • 在单链表的节点中查找传入的数据,若找到返回节点的地址,若没找到返回NULL。

查找函数的实现:

SLTNode* SLTFind(SLTNode* pphead, SLTDataType x)
{
	SLTNode* pcur = pphead;

	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

测试查找函数如下:

void SLTNodetest07()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);


	// 测试查找
	SLTNode* find = SLTFind(plist, 1); // 找到了
	if (find != NULL)
		printf("1 找到了!!\n");
	else
		printf("1 没找到!!\n");

	SLTNode* find1 = SLTFind(plist, 10); // 没找到
	if (find1 != NULL)
		printf("10 找到了!!\n");
	else
		printf("10 没找到!!\n");

}

效果如下:
在这里插入图片描述

七、指定位置之前插入

  • 找到这个位置(pos)的前一个节点(prev)
  • 创建一个新的节点,prev的next指向这个新的节点。
  • 新节点的next指向pos节点。

指定位置之前插入函数的实现:

// 指定位置之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && *pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newNode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = newNode;
		newNode->next = pos;
	}

}
  • 若传入的位置是头节点,则直接调用头插函数

测试指定位置之前插入函数如下:

void SLTNodetest08()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);

	// 找到3的位置
	SLTNode* find = SLTFind(plist, 3);

	//指定位置之前插入
	SLTInsert(&plist, find, 10);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述

八、指定位置之后插入

  • 创建新的节点
  • 指定位置pos的next赋值给新节点的next。
  • 再将新节点的地址赋值给指定位置pos的next。

指定位置之后插入函数的实现:

// 在指定位置之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newNode = SLTBuyNode(x);

	newNode->next = pos->next;
	pos->next = newNode;

}

测试指定位置之后插入函数如下:

void SLTNodetest09()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);

	// 找到3的位置
	SLTNode* find = SLTFind(plist, 3);

	// 指定位置之后插入
	SLTInsertAfter(find, 50);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述

九、删除pos节点

  • 先得到指定位置pos之前的节点prev。
  • pos的next 赋值给 prev的next。
  • free 释放掉 pos节点。

删除pos节点函数的实现:

// 删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;

		free(pos);
		pos = NULL;
	}
}

  • 若pos指向头节点,则直接调用头删函数。

测试删除pos节点函数如下:

void SLTNodetest10()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);

	// 找到3的位置
	SLTNode* find = SLTFind(plist, 3);

	// 删除pos节点
	SLTErase(&plist, find);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述


十、删除pos之后的节点

  • 创建临时变量del,存储指定位置(pos)的下一个节点的地址。
  • 指定位置pos的next指向pos->next->next。
  • free 临时变量del所指向的空间。
  • del置为NULL。

删除pos之后节点函数的实现:

// 删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);

	SLTNode* del = pos->next;

	pos->next = pos->next->next;
	free(del);
	del = NULL;
}


测试删除pos之后的节点函数如下:

void SLTNodetest11() 
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);

	// 找到3的位置
	SLTNode* find = SLTFind(plist, 3);

	// 删除pos节点之后的节点
	SLTEraseAfter(find);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述

十一、销毁链表

  • 使用临时变量存储头节点的next地址
  • 释放头节点的空间
  • 再将临时变量赋值给头节点地址
  • 最后,将头节点置为空
  • 循环,知道链表结束

销毁链表函数的实现:

// 销毁链表
void SLTDestory(SLTNode** pphead)
{
	assert(pphead && *pphead);

	SLTNode* pcur = *pphead;

	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;

}

测试销毁链表函数如下:

void SLTNodetest12()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);

	SLTPrint(plist);


	// 销毁链表
	SLTDestory(&plist);
	SLTPrint(plist);
}

效果如下:
在这里插入图片描述


总结

C语言单链表、单链表的创建、打印单链表、尾插、头插、尾删、头删、查找、指定位置之前和之后插入、删除pos节点、删除pos之后的节点、销毁链表等介绍

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值