《算法笔记》总结No.10——链表

        从第10期破例插叙一期单链表的实现,这个东东相当重要!考研的同学也可以看:相较于王道考研的伪码不太相同,专注于可以运行。如果是笔试中的伪码,意思正确即可~

 注:博主之前写过一个版本的顺序表和单链表的C++实现,和这篇的写法有所不同,不过内容也较全,大家可以先行阅读~C++数据结构笔记(2)线性表的顺序存储和链式存储_c++线性表的顺序存储和链式存储-CSDN博客文章浏览阅读348次。1.线性表是0个或者多个数据元素的有限序列,其中数据元素类型相同2.线性表可以逐项访问和顺序存储3.有顺序存储和链式存储两种存储方式。接下来,_c++线性表的顺序存储和链式存储https://jslhyh32.blog.csdn.net/article/details/131440870

一.理论精炼

        线性表没什么可说的,比较简单,大家姑且把他理解为数组即可。相较于顺序表的物理结构上也连续存取,链表在内存中的存储位置是离散的。在实现的过程中,多数参考资料愿意以带头节点的链表为例,如下图:

二.分配内存空间

先来看一下链表节点的定义方式: 

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

        初学者接触的时候有人会蒙圈——因为这相当于是嵌套定义,即类型里面又直接定义了一个指向自己类型的指针。但这恰恰是链表的“几何意义”:顺序表的定义就很符合人类的思维惯式,对吧,一个表,或者说是一个有顺序的集合一样,物理上就应该是连续的一堆东西连在一起——计算机的内存中实际上也是这么存放的,因此在定义的时候直接定义成和数组“长得一样”的类型。

        不过链表就不一样了,和他本身的逻辑意义一致——即各节点之间通过指针串起来,彼此之间是不连续的。因此在声明一个链表的时候,其实是声明了一个头结点——这样后面的元素根据指针就可以存在了!即声明的时候先声明一个,然后再根据指针域的值继续赋值。

1.malloc函数

malloc是用于申请分配动态内存的函数,这篇博客已经介绍过,这里不再赘述:

C语言malloc函数及数组初始长度的辨析-CSDN博客文章浏览阅读389次,点赞7次,收藏7次。知名的教材在编写中总是给出了很多伪代码,虽然说从意图上来说只要将代码的逻辑表达清楚就没什么问题,不过很多书中的伪码有些过于逆天,会误导许多基础不扎实的人;另一方面,毕竟每个人的编码习惯不同,可能有些高手喜欢写生僻的代码来让人云里雾里语法的规则。。。。https://jslhyh32.blog.csdn.net/article/details/140559079?spm=1001.2014.3001.5502如下,声明一个LNode类型的指针,再赋予空间:

	LNode* head;
	head=(LNode*)malloc(5*sizeof(LNode));

逻辑非常清晰明了,malloc返回的是地址,因此需要将这部分地址赋予一个指针~ 

2.new运算符

C++中,可以这样写:

LNode* p=new LNode;

没什么难度,这里主要想讲C,就不展开细说了。 

3.内存泄漏

C语言的设计者认为,程序员完全右能力自己控制内存的分配与释放,因此把对内存的控制、操作都分配给了程序员。使用完malloc等之后,一定要使用free将其释放:

free(head);

三.创建链表

先来直观的感受一下如何创建链表:

	LNode* node1=(LNode*)malloc(sizeof(LNode));
	LNode* node2=(LNode*)malloc(sizeof(LNode));
	LNode* node3=(LNode*)malloc(sizeof(LNode));
	
	node1->data=1;
	node1->next=node2;
	
	node2->data=2;
	node2->next=node3;
	
	node3->data=3;
	node3->next=NULL;

显然这样太死板了,没有人机交互,那么不妨我们改善一下,main函数中可以输入链表的长度,如下:

int main(int argc, char *argv[]) {
	
	printf("请输入链表的长度:");
	int num=0;
	scanf("%d",&num);
	
	LNode* List=createLinkList(num); //根据长度创建链表
	
	List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
	while(List!=NULL)
	{
		printf("%d ",List->data);
		List=List->next;	
	} 
	

	return 0;
}

然后我们来编写createLinkList函数:

LNode* createLinkList(int x)
{
	LNode* head;// 声明头结点
	LNode* tNode;//当前结点
	LNode* pre;//当前结点的前驱节点
	head=(LNode*)malloc(sizeof(LNode));
	head->next=NULL;//初始化指针域为0 
	pre=head; 

	int i=1;
	for(;i<=x;i++)
	{
		tNode=(LNode*)malloc(sizeof(LNode));//创建一个新节点
		int temp=0;
		temp=i;
		tNode->data=temp;
		tNode->next=NULL;
		pre->next=tNode;
		pre=tNode; 
	} 
	return head;
	
}

(加前驱是为了方便链表的创建,也可以不这样做~)

 

如上图,没什么bug~


为了让大家直观感受一下所谓的【离散】,我们把链表中的节点地址也打印一下,修改main函数如下:

int main(int argc, char *argv[]) {
	
	printf("请输入链表的长度:");
	int num=0;
	scanf("%d",&num);
	
	LNode* List=createLinkList(num); //根据长度创建链表
	LNode* other=List; //另外单独存放一个头结点 
	printf("地址依次如下:\n");
	while(other!=NULL)
	{
		printf("%d ",&other);
		other=other->next;	
	} 
	printf("\n");
	printf("数值依次如下:\n");
	List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
	while(List!=NULL)
	{
		printf("%d ",List->data);
		List=List->next;	
	} 

	return 0;
}

如下图:头结点和其他5个节点之间均不是连续的!

 

 另外,每次执行的地址也不尽相同。相信这样直观感受一下,各位就能理解到链表的物理意义了~

四.查找元素

查找就非常简单了,这里我们直接写一个统计某元素个数的函数,如下:

int SearchByValue(LNode* target,int x)
{
	int count=0;
	LNode* temp=target->next;
	while(temp!=NULL)
	{
		if(temp->data==x)
			count++;
		temp=temp->next;	
	} 
	return count;
}

main函数调用:

 

继续写别的~ 

五.插入元素

这个有点意思,因为需要交换两个指针,具体的逻辑这里不多说了,太基础了,这里放个图让大家看看,千万别被逻辑绕晕~

代码如下:

void InsertByPos(LNode* L,int pos,int x)
{
	LNode* temp=L;//将头结点的值赋给临时的节点
	int i=1;
	for(;i<=pos-1;i++) //找到待插入位置的前一个指针 
		temp=temp->next; 
	LNode* tNode;	
	tNode=(LNode*)malloc(sizeof(LNode)); //创建一个新节点
	tNode->next=temp->next;
	temp->next=tNode;
	
	tNode->data=x; 
}

main函数测试:

//3.测试 InsertByPos
	InsertByPos(List,4,32); 
	List=List->next; 
	while(List!=NULL)
	{
		printf("%d %d\n",List->data,&List->next);
		List=List->next;	
	} 

结果如下:

堪称完美~ 

六.删除元素

1.按值删除

简单,直接按照上面按值查询的代码写就好,只需要改变if条件的逻辑即可:

void DeleteByValue(LNode* L,int x)
{
	LNode* temp=L->next;//忽略头结点的第一个
	LNode* pre=L;//pre始终用来保存 
 	while(temp!=NULL)
	{
		if(temp->data==x)
			pre->next=temp->next;
		pre=temp;
		temp=temp->next;
			
	} 
}

main函数:

	
//4.测试DeleteByValue
	DeleteByValue(List,4);
	List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
	while(List!=NULL)
	{
		printf("%d %d\n",List->data,&List->next);
		List=List->next;	
	} 

测试结果: 

4成功被删掉~

2.按位删除

根据插入元素的代码即可修改成功。题外话,其实这里还可以写一个按位查找,大家自己试试~

void DeleteByPos(LNode* L,int pos)
{
	LNode* temp=L->next;
	LNode* pre=L;
	int i=1;
	for(;i<=pos-1;i++) //找到待插入位置的前一个指针 
	{
		pre=temp;
		temp=temp->next; 	
	}
	pre->next=temp->next;	
	
} 

老套路:

	DeleteByPos(List,4);
	List=List->next; //第一个是头结点,因此从第二个开始才能遍历到数据
	while(List!=NULL)
	{
		printf("%d %d\n",List->data,&List->next);
		List=List->next;	
	} 
	free(List);
	return 0;

还是删除掉了第4位的元素~

主打一个过五关斩六将~ 

七.静态链表 

静态链表感觉没什么意思,感觉还不如直接用顺序表,大家自己看看就行,比较简单:

 


写在最后:无论408还是众多985名校的自命题,线性表都是算法题、大题考察的热门,大家一定要熟练掌握代码规范。至于二叉树乃至图论的具体编程实现,怎么说,要是你不是奔着140+去考,其实是可以允许自己不会的,参照二八定律——应该尽可能地从简易的20%里面获取占大头的80%分数。因此这期结束后也不优先更新各种数据结构的实现了,优先更新一些数学的问题,接着就是DFS、BFS、DP这些蓝桥杯等竞赛偏爱的内容。(说句题外话,蓝桥杯之所以被戏称圈钱杯、DP杯,就是因为近年来的考试题目逐渐以暴力和DP为主,在一个不是很发达的省份比如我们这里,即便是A组,如果你的DP异常熟练,拿个省一进国赛是没什么难度的。除了蓝桥杯还有CSP,如果代码基础非常扎实,会处理复杂的字符串,还能熟练掌握DP,考个300+似乎也不是很有难度。。由此可见DP的重要性!


完整的.c文件源码,有需要的自提:

链接:https://pan.baidu.com/s/1uN0elyL2N25vNhF2bSewoA 
提取码:hma8 

  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyric群青

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值