【数据结构】单链表——增删查改【万字介绍】

目录

 一,线性表

        1,什么是线性表

         2,线性表的结构

 二,链表

        1,什么是链表

         2,链表的分类

        3,单链表的特点

三,链表的实现

        1,结构的定义

        2,创建一个新结点

        3,单链表的头插

         4,单链表的尾插

        5,单链表的头删

        6,单链表的尾删

         7,单链表的查找

        8,单链表的在指定位置前插入

         9,单链表的在指定位置后插入

        10,单链表的在指定位置前删除

        11,单链表的在指定位置后删除

四,源码展现

        1,SList.c

        2,SList.h

        3,test_0301.c


 一,线性表

        1,什么是线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列;线性表是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表,链表,栈,队列,字符串...

         2,线性表的结构

线性表在逻辑上是线性结构,通俗来说就是一条连续的直线;但是在物理结构上不一定是连续的,线性表在物理存储上,通常是以数组以及链式结构存储的形式存储的。

 

 注意:

        1,从上图可以看出链式结构在逻辑上是连续的,但在物理上不一定

        2,现实中的节点一般都是从堆上申请出来的

        3,从堆上申请的空间,是按照一定的策略来划分的,两次申请的空间可能是连续的,也可能不是连续的

 二,链表

        1,什么是链表

链表是一种 物理存储结构上非连续,非顺序的存储结构,数据元素的 逻辑顺序是通过链表中的 指针链接次序实现的。

         2,链表的分类

实际中链表的种类多种多样,基本情况组合起来就有8种链表情况之多;本篇文章我们不讨论那么多种就只讨论常用两种:无头单向非循环链表和带头双向循环链表中的无头单向非循环链表,我们简称为:单链表。 

        3,单链表的特点

无头单向非循环链表:结构简单,一般不会用来单独存放数据,实际中更多是作为其他数据结构的子结构,比如哈希桶,图的邻接表等等。另外这种结构在笔试面试中出现很多。

三,链表的实现

        1,结构的定义

#pragma once

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


typedef int SLTDataType;    //将数据类型重定义

typedef struct SListNode 
{
	SLTDataType data;    //对应数据类型的指针,指向动态内存开辟的空间
	struct SListNode* next; //结点

	//SListNode* data;  不完整这里,要加上struct
	//SLTNode* data; 函数基本是向上寻找定义
}SLTNode;

如上图代码:我们把int定义成了SLTDataType,此做法是为了方便我们以后用此链表去管理其他不同的数据的类型,这样以后只用修改一个地方即可。

        2,创建一个新结点

//提取节点
SLTNode* BuySLTNode(SLTDataType x) 
{
	//创建一个节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	//初始化
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

不止一次创建一个新的节点,我们可以写一个提取节点的函数,直接malloc申请一个新的节点,将数据放在节点中,把指针置空,并返回新节点的地址。

        3,单链表的头插

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

这里要注意链接的顺序,如果先将头指针指向新结点的头,再将新的结点的尾链接到原来链表的头的话,原来链表的头就找不到了,因为原来链表的头是放在头指针里面的,若先将头指针改了就找不到原来的头了,就链接不上了。

         4,单链表的尾插

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);

	创建一个节点
	//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//if (newnode == NULL) 
	//{
	//	perror("malloc fail");
	//	return;
	//}
	初始化
	//newnode->data = x;
	//newnode->next = NULL;

	if(*pphead == NULL)
	{
		*pphead = newnode;

	}
	else
	{
		//找尾
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

这里使用到了二级指针,如果只是单纯的将phead=newnode的化,实际的头结点是没有改变的,因为形参实际上只是实参的一份临时拷贝,尾插函数只是将phead的值改变成了newnode,实际上并没有真正的改变头指针。我们之前学过想要改变值就要传地址,所以这里用到了二级指针。

        5,单链表的头删

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

这里我们只需要创建一个新的指针变量接收一下*pphead,再把*pphead也就是我们的头部指向刚才的指针变量的下一个那我们的新的头部就改变了,在把我们新创建的变量释放掉,并且置为空,我们就解决了我们的头部删除。

        6,单链表的尾删

//尾删
void SLTPopBack(SLTNode** pphead)
{
	//先检查pphead一定不能为空
	assert(pphead);
	assert(*pphead);
	//1,只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//2,多个节点
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

这里我们需要判断两个情况:一个是链表只有一个结点,还有就是多个结点的情况,只有一个结点很好判断,他的next就是空,就只要把他释放掉并置空就完成了;多个结点的情况我们就需要遍历这个链表,我们这里用了一个新的变量来代替我们的头部变量,目的就是可能会有后续操作还会用到我们的头结点,如果直接用我们的头部结点已经消失了。去判断下一个结点的下一个结点为不为空,不为空tail就去到下一个结点接着判断,直到为空,tail停下的位置就是尾结点,把释放置空掉,我们就达到了一个尾删的操作。

 

         7,单链表的查找

//查找/修改
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) 
{
	//cur = current
	SLTNode* cur = phead;
	while (cur) 
	{
		if (cur->data == x) 
		{
			return cur;
		}
		cur = cur->next;
	}
	//找不到
	return NULL;
}

我们只需要遍历我们的链表,如果等于我们的传进来的数字,直接返回就好了,不等于就继续往后找,如果出了循环那就只有一种情况了,那就是找不到,我们直接返回NULL就可以了。

        8,单链表的在指定位置前插入

oid SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) 
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead) 
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else 
	{
		//先找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos) 
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

这里我们也要分为两种情况来进行讨论,如果只用一个结点,我们的pos等于他,那我们在他之前插入不就是头插的操作吗,我们直接调用头插函数就可以了;如果不是一个结点,我们要先找到pos的前一个位置,并且让我们最开始pos的前一个结点指向我们新创建的结点,再让我们的新结点指向pos就可以了。

         9,单链表的在指定位置后插入

//在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x) 
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

这个操作是很简单的了,但是一定要注意的是一个先后顺序。切记不要粗心大意。

//在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x) 
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	pos->next = newnode;
    newnode->next = pos;
}

很多时候一大意就写成这样了哟,各位小伙伴一定要注意。

        10,单链表的在指定位置前删除

//在pos位置处删除
void SLTErase(SLTNode** pphead, SLTNode* pos) 
{
	assert(pphead);
	assert(pos);
	//assert(*pphead);//能断也可以不断
	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其实是不需要置空的啊,因为这里形参只是实参的一份临时拷贝,形参的改变不改变实参,一般的这里的置空是交给传参的人。

  

        11,单链表的在指定位置后删除

//在pos后面删除
void SLTEraseAfter(SLTNode* pos) 
{
	assert(pos);
	assert(pos->next);
	//pos->next = pos->next->next;从右往左执行
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

很多小伙伴一上来就写了下面这段代码,这样是错误的啊,因为他是从右往左执行的啊,一定不要粗心大意啊;上面的代码才是正确的啊。

//pos->next = pos->next->next;从右往左执行

四,源码展现

        1,SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

//打印
void SLTPrint(SLTNode* phead)
{
	//assert(phead);不能断言:链表的结束条件就是为NULL
	SLTNode* cur = phead;
	while (cur != NULL) //为空结束
	{
		printf("%d->", cur->data);
		cur = cur->next;//指向下一个节点
		//cur++;error
	}
	printf("NULL\n");//结束标志
}

//提取节点
SLTNode* BuySLTNode(SLTDataType x)
{
	//创建一个节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	//初始化
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);

	创建一个节点
	//SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	//if (newnode == NULL) 
	//{
	//	perror("malloc fail");
	//	return;
	//}
	初始化
	//newnode->data = x;
	//newnode->next = NULL;

	if(*pphead == NULL)
	{
		*pphead = newnode;

	}
	else
	{
		//找尾
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SLTPopBack(SLTNode** pphead)
{
	//先检查pphead一定不能为空
	assert(pphead);
	assert(*pphead);
	//1,只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//2,多个节点
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* first = *pphead;
	*pphead = first->next;
	free(first);
	first = NULL;
}

//查找/修改
SLTNode* SLTFind(SLTNode* phead, SLTDataType x) 
{
	//cur = current
	SLTNode* cur = phead;
	while (cur) 
	{
		if (cur->data == x) 
		{
			return cur;
		}
		cur = cur->next;
	}
	//找不到
	return NULL;
}

//在pos之前插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) 
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead) 
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else 
	{
		//先找到pos的前一个位置
		SLTNode* prev = *pphead;
		while (prev->next != pos) 
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

//在pos位置处删除
void SLTErase(SLTNode** pphead, SLTNode* pos) 
{
	assert(pphead);
	assert(pos);
	//assert(*pphead);//能断也可以不断
	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之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x) 
{
	assert(pos);
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//在pos后面删除
void SLTEraseAfter(SLTNode* pos) 
{
	assert(pos);
	assert(pos->next);
	//pos->next = pos->next->next;从右往左执行
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

        2,SList.h

#pragma once

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


typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;

	//SListNode* data;  不完整这里,要加上struct
	//SLTNode* data; 函数基本是向上寻找定义
}SLTNode;

//打印
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//pos位置插入
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//pos位置前面删除
void SLTErase(SLTNode** pphead, SLTNode* pos);
//在pos之后插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//在pos后面删除
void SLTEraseAfter(SLTNode* pos);

        3,test_0301.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void TestSList1()
{
	//
	/*SLTNode* plist = NULL;
	SLTPushBack(plist, 1);
	SLTPushBack(plist, 2);
	SLTPushBack(plist, 3);
	SLTPushBack(plist, 4);
	SLTPrint(plist);*/

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

void TestSList2()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

}
void TestSList3()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	
	SLTPopBack(&plist, 1);
	SLTPrint(plist);

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

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



}
void TestSList4()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	/*SLTPopFront(&plist, 4);
	SLTPrint(plist);

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

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

	SLTPopFront(&plist, 1);
	SLTPrint(plist);*/

	//值为2的结点*2倍
	SLTNode* ret = SLTFind(&plist, 2);
	ret->data *= 2;
	SLTPrint(plist);
}
void TestSList5()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsert(&plist, ret, 20);
	SLTPrint(plist);
}
void TestSList6()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTErase(&plist, ret);
	ret = NULL;
	SLTPrint(plist);
}
void TestSList7()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTInsertAfter(ret, 30);
	SLTPrint(plist);
}
void TestSList8()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* ret = SLTFind(plist, 2);
	SLTEraseAfter(ret);
	SLTPrint(plist);
}

int main()
{
	//TestSList1();
	//TestSList2();
	TestSList3();
	//TestSList4();
	//TestSList5();
	//TestSList6();
	// TestSList7();
	//TestSList8();
	return 0;
}

本篇文章的图都是小编手画的,虽然有点难看,但还是希望得到大家的支持哟!!!

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值