当前位置: 首页 > article >正文

单链表——单链表的定义及基本操作(初始化、头插法尾插法建表、查找、插入、删除、判空等)

单链表的定义

由于顺序表存在以下缺陷,所以衍生出了链表,而链表种类有很多种,今天我们讲的是单链表

顺序表存在的问题如下

1.中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

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

链表就像开火车一样,火车头代表开始,后面链接着运送货物的车厢。

链表的分类:实际中链表的结构非常多样,以下情况组合起来就有8种链表结构。

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构无头单向非循环链表和带头双向循环链表。

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

今天我们重点来实现无头单向非循环链表的实现俗称单链表

单链表上的操作

 

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

2.现实中的结点一般都是在堆上malloc申请出来的。

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

4.如果最后一个结点后面没有要存储的值,要把最后一个结点的指针设成NULL,方便下一次的插入新存储的值。

建立单链表

#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{
	SedListdatatype data;
	struct SedList* next;
}SLNode;

因为我们不知道单链表存储的数据是什么类型的,所以我们把int重定义成 SedListdatatype,如果我们要改其他数据类型,只需要在上面改一下即可。

我们再把结构体的名字也重定义方便实现单链表的功能实现。

单链表不需要初始化,因为我们都是从堆上malloc申请出的空间结点。

数据结构无非是对数据进行管理,要实现数据的增删查改,因此链表的基本功能也都是围绕着数据的增删查改展开。 

单链表的打印

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

单链表传过来的指针为空,就打印NULL。因为单链表传过来的指针也可能为空,所以不用断言。

这里while的条件为什么是cur!=NULL?因为这样才能打印单链表的所有存储的值,如果写出cur->next,单链表最后一个元素存储的值就不能打印出来,因为单链表最后一个结点指针为NULL

所以当cur为NULL就可以停止打印。

创建malloc出新结点

SLNode* buynode(SedListdatatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

我们要在单链表中实现尾插和头插等等功能,这个结点包括存储的数据和指针(地址)为了方便和减少代码的重复度,我们另写一个函数用来专门创建新结点

单链表的头插

void SLPushFront(SLNode** phead, SedListdatatype x)
{
	assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	newnode->next = *phead;
	*phead = newnode;
}

单链表的尾插

void SLPushBack(SLNode** phead, SedListdatatype x)
{
	assert(phead);即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	链表为空
	if (*phead == NULL)
	{
		*phead = newnode;
不用让newnode->next=NULL,因为我们在开堆上申请新结点的时候已经完成了这步操作

	}
	多个结点
	SLNode* cur = *phead;
	while (cur->next != NULL)
	{
		cur = cur->next;
	}
	cur->next = newnode;
}

 当我们遍历单链表的时候,最好给一个另外的变量赋值去遍历,因为我们有时候会需要找到头的地址,为了不丢失头指针的地址,所以我们不用头指针phead自己遍历单链表

另外大家已经注意到为什么头插和尾插使用的是二级指针?因为结构体里面的next本身就是一个一级指针,尾插和头插,都会改变结构体里面存储的数据,而修改一级指针的内容就需要去二级指针来存储一级指针的地址,并传址才能改变单链表的内容,如果使用一级指针来存储一级指针的地址出了循环以后就会销毁,并不会影响单链表的内容也不能增删查改和管理存储数据。相当于形参与实参的差别。形参不会影响实参的改变,只是实参的临时的一份拷贝。所以要用二级指针。

当尾插时链表为空,就相当于头插。不为空时,就找尾再插入即可。

单链表的头删 

void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查  选一种即可
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		*phead = (*phead)->next;
		free(cur);
	}

}

单链表的尾删

void SLPopBack(SLNode** phead)
{

	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查 选一种即可
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur=cur->next;
		}
		free(cur);
		pre->next = NULL;
	}
	


}

用一个新指针指向cur指向的上一个结点next,等cur找到最后一个NULL就free,然后再把pre的next设为NULL即可。

单链表的遍历和查找数据 

SLNode* Find(SLNode* phead, SedListdatatype x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;


}

函数的返回值不再是void而是一个指针变量,因为我们要把找到的结点地址返回回去,这个函数可以配合修改返回当前结点的值。所以这个函数既能查找又修改单链表结点。

单链表的插入分为pos之前插入和pos之后插入

单链表的pos之后插入

void SLInsertAfter(SLNode* pos, SedListdatatype x)
{
	SLNode* newnode = buynode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

单链表的删除分为pos之前删除和pos之后删除

单链表的pos之后删除

void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);

}

单链表的销毁

void SLDestory(SLNode** phead)
{
	SLNode* cur = *phead;
	while (cur != NULL)
	{
		SLNode* pre = cur->next;
		free(cur);
		cur = pre;
	}
	*phead = NULL;
}

在销毁链表的时候不能直接free,因为单链表在物理结构上是不连续存储的,必须要一个一个结点的销毁,再把phead设为NULL。

 

总代码展示

SedList.h
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
typedef int SedListdatatype;
typedef struct SedList
{
	SedListdatatype data;
	struct SedList* next;
}SLNode;

void SLprint(SLNode* phead);//打印

void SLPushFront(SLNode** phead, SedListdatatype x);//头插
void SLPushBack(SLNode** phead, SedListdatatype x);//尾插

void SLPopFront(SLNode** phead);//头删
void SLPopBack(SLNode** phead);//尾删

SLNode* Find(SLNode* phead,SedListdatatype x);//查找和遍历

void SLInsertAfter(SLNode* pos, SedListdatatype x);//在pos之后插入
void SLEraseAfter(SLNode* pos);//在pos之后删除

void SLDestory(SLNode** phead);//销毁
SedList.c
#include"SedList.h"
void SLprint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
	printf("\n");
}
SLNode* buynode(SedListdatatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}
void SLPushFront(SLNode** phead, SedListdatatype x)
{
	assert(phead);//即使链表为空,phead也不为空,因为指向的是链表头指针plist的地址
	SLNode* newnode = buynode(x);
	newnode->next = *phead;
	*phead = newnode;
}
void SLPushBack(SLNode** phead, SedListdatatype x)
{
	assert(phead);
	SLNode* newnode = buynode(x);
	//链表为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
void SLPopFront(SLNode** phead)
{
	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		*phead = (*phead)->next;
		free(cur);
	}

}
void SLPopBack(SLNode** phead)
{

	assert(phead);
	assert(*phead);//第一种方法
	//第二种方法 温柔的检查
	if (*phead == NULL)
	{
		printf("链表为空!不能删除!");
		return;
	}
	//只有一个结点
	if ((*phead)->next == NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//多个结点
	else
	{
		SLNode* cur = *phead;
		SLNode* pre = NULL;
		while (cur->next != NULL)
		{
			pre = cur;
			cur=cur->next;
		}
		free(cur);
		pre->next = NULL;
	}
	


}
SLNode* Find(SLNode* phead, SedListdatatype x)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;


}
void SLInsertAfter(SLNode* pos, SedListdatatype x)
{
	assert(pos);
	SLNode* newnode = buynode(x);
	newnode->next = pos->next;
	pos->next = newnode;


}
void SLEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);

}

void SLDestory(SLNode** phead)
{
	SLNode* cur = *phead;
	while (cur != NULL)
	{
		SLNode* pre = cur->next;
		free(cur);
		cur = pre;
	}
	*phead = NULL;
}
test.c
#include"SedList.h"
void test1()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);

	SLprint(plist);


}
void test2()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);

	SLprint(plist);


}
void test3()
{
	SLNode* plist = NULL;
	SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPopFront(&plist);
	SLprint(plist);


}
void test4()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPopBack(&plist);
	SLPopBack(&plist);

	SLprint(plist);


}
void test5()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLNode* pos = Find(plist, 3);
	pos->data = 40;

	SLprint(plist);


}
void test6()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLNode* pos = Find(plist, 2);
	if (pos)
	{
		SLInsertAfter(pos, 4);
	}

	SLprint(plist);


}
void test7()
{
	SLNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPushBack(&plist, 2);
	SLPushBack(&plist, 3);
	SLPushBack(&plist, 4);
	SLNode* pos = Find(plist, 2);
	if (pos)
	{
		SLEraseAfter(pos);
	}
	SLprint(plist);
	


}

int main()
{
	test1();
	test2();
	test3();
	test4();
	test5();
	test6();
	test7();

	return 0;
}


http://www.kler.cn/a/17278.html

相关文章:

  • 推荐一款好用的postman替代工具2024
  • AI绘画经验(stable-diffusion)
  • 笔记 | image may have poor performance,or fail,if run via emulation
  • Spark 的容错机制:保障数据处理的稳定性与高效性
  • vscode下nuget包的本地引入方法
  • node.js安装和配置教程
  • 当ChatGPT续写《红楼梦》,能替代原著吗?
  • [计算机图形学]动画与模拟:关键帧动画、质点弹簧系统、运动学与绑定(前瞻预习/复习回顾)
  • 为什么用Selenium做自动化测试
  • ebpf-linux 安全“双刃剑”
  • 不合格机器人工程讲师如何坦然面对失败
  • Java算法比赛常用方法
  • Java入门超详细(内含Java基础 Java高级)
  • 100种思维模型之万物系统思维模型-57
  • Packet Tracer - 配置扩展 ACL - 场景 1
  • C语言通过控制台命令行传入参数
  • 安卓联发科MT6737手机开发核心板 开发模块
  • 【Java笔试强训 15】
  • JS逆向系列之猿人学爬虫第5题
  • 网络编程 总结一
  • Vim 文本查找/替换
  • 都23年了你还记得渐进式框架是什么意思吗
  • 国民技术N32G430开发笔记(18)- I2C1 从机收发数据
  • 【react全家桶学习】react组件中的ref属性(详)
  • K8S之HPA自动扩缩容机制
  • 【五一创作】QML、Qt Quick /Qt中绘制圆形