数据结构之单链表的详细实现(图解)

本文详细介绍了单向不带头非循环链表的结构特点,包括其在实际应用中的作用,以及如何实现链表的初始化、插入、删除和查找等基本操作。通过代码示例展示了如何动态分配内存、进行头插、尾插、头删和尾删等操作。
摘要由CSDN通过智能技术生成

前言

本次博客讲结合图例讲解单向不带头非循环链表

此后会讲解一些题目

1单链表的实现

1.1什么是单链表

我们先看数组,即顺序表的是什么样的,再看链表

1.2单链表的特点

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

1. 单向、双向

2. 带头、不带头

3. 循环、非循环

我们今天可以讲解最复杂的情况单向不带头非循环链表

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

2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都 是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了

链表的本质就是牺牲空间节省时间,它的访问速度快

1.2链表的实现

头文件部分

#pragma once
#define _SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int datatype;
struct slnode{
	datatype data;
	struct slnode* next;
};
void slinit(struct slnode**pphead);
void slprint(struct slnode* phead);
void slPushBack(struct slnode** pphead,datatype put);
void slPushfront(struct slnode** pphead, datatype put);
void slPopfront(struct slnode** pphead);
void slPopback(struct slnode** pphead);
struct slnode* slFind(struct slnode* phead,datatype n);
void slInsertfront(struct slnode**pphead, datatype find,datatype i);
void slErase(struct slnode** pphead, datatype find);

大家简略看看就好,后面还会再提到,主要关注链表的定义

接下来讲解如何实现该链表

 初始化链表 slinit()

由于 没有哨兵位指针,所以它的初始化只需要让头指针为空即可

void slnodeinit(struct slnode **pphead)

{

*pphead=NULL;

}

扩展空间  struct slnode* buyslnode(datatype put)

如果要尾插或者头插一个链表,我们必须要动态开辟空间,才能增添一个结点

思路

首先他它有开辟一个空间,就必须要有一个指针记住该空间的地址

所以让该函数返回一个指针,注意在开辟成功后,必须要

让开辟的空间的next成员指向空指针,不然它就是一个野指针

struct slnode* buyslnode(datatype put)
{
	struct slnode* newnode = (struct slnode*)malloc(sizeof(struct slnode));
	if (newnode == NULL)
	{
		return NULL;
	}
	newnode->data = put;//插入元素的大小
	newnode->next = NULL;//赋值w为空指针
	return newnode;
}

 头插void slPushfront(struct slnode** pphead, datatype put)

思路

如果一开始没有一个结点,也就是 phead此时为NULL

要让  *phead=buyslnode(put);

否则就是普通的头插,此时画图让大家理解头插

但此时 不管头结点是不是NULL指针都可以直接通过以下代码实现头插

void slPushfront(struct slnode** pphead, datatype put)
{
	struct slnode* newnode = buyslnode(put);
		newnode->next = *pphead;
		*pphead = newnode;
}

尾插void slPushBack(struct slnode** pphead,datatype put)

思路

仍然考虑phead为NULL的情况,此时只要让newnode=*pphead即可

但是它是需要找到尾巴也就是最后一个非0结点

怎么找,看图

void slPushBack(struct slnode** pphead,datatype put)
{
	struct slnode* newnode = buyslnode(put);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		struct slnode* cur = *pphead;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

打印 void slprint(struct slnode* phead)

其实只需要遍历一遍即可

直接看代码

但是要注意这里是cur!=NULL才算遍历完

void slprint(struct slnode* phead)
{
	struct slnode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

到这里应该要测试一遍了

看图

这里分别尾插1 2 3 4 5 最后头插一个 0 再把他们打印出来

尾删 void slPopback(struct slnode** pphead)

分析

首先要有结点才能删除,所以头结点可以通过assert判断

如果只有一个结点让头指针置空

如果有两个以上的结点那么就找到尾巴并且找到尾巴前一个结点让它置空,并删除尾结点

看图吧

看代码

void slPopback(struct slnode** pphead)
{
	    assert(*pphead);
		struct slnode* cur = *pphead;
		struct slnode* prev = *pphead;
		if (cur->next == NULL)
		{
			free(*pphead);
			pphead = NULL;
		}
		else
		{
			while (cur->next)
			{
				prev = cur;
				cur = cur->next;
			}
			free(cur);
			prev->next = NULL;
		}

}

头删 void slPopfront(struct slnode** pphead)

分析

首先还是要有结点可删才可以删,所以还是要assert

其次还是这样,如果只有一个结点那就直接free掉

如果有两个以上的结点,还要找到下一个结点

看图

这里头删只要一个变量就可

void slPopfront(struct slnode** pphead)
{
	 assert(*pphead);
	 if ((*pphead)->next == NULL)
	 {
		free(*pphead);
		*pphead = NULL;
	 }
	else
	{
		struct slnode*tem = (*pphead)->next;
		free(*pphead);
		*pphead = tem;
	}
}

 定位 struct slnode* slFind(struct slnode* phead, datatype n)

分析

遍历一遍 判断即可

struct slnode* slFind(struct slnode* phead, datatype n)
{
	    assert(phead);
		while (phead)
		{
			if (phead->data== n)
			{
				return phead;
			}
			else
				phead = phead->next;
		}
		printf("没有该数据\n");
		return NULL;
}

 在pos前插入 void slInsertfront(struct slnode**pphead, datatype find, datatype i)

分析

先找到该位置,如果没有找到位置,结束

找到该位置后

如果该位置是第一个位置就是头插

还得找到这个位置的前一个位置把他们相连

看图

void slInsertfront(struct slnode**pphead, datatype find, datatype i)
{
	struct slnode* pfind=slFind(*pphead, find);
	if (pfind == NULL)
		return;
	else
	{
		if (pfind == *pphead)
			slPushfront(pphead, i);
		else
		{
			struct slnode* cur = *pphead;
			while (cur->next != pfind)
			{
				cur = cur->next;
			}
			cur->next = buyslnode(i);
			cur->next->next = pfind;
		}
	}
}

删除该位置的数据 void slErase(struct slnode** pphead, datatype find)

分析

但凡要删除数据就必须要有数据,所以还是要assert一下头指针

如果是pos位置在头指针位置即为头删 如果是在最后一个结点即为尾删

如果在中间,就是必须要找到前一个和后一个结点

看图

看代码

void slErase(struct slnode** pphead, datatype find)
{
	struct slnode* pfind = slFind(*pphead, find);
	if (pfind == *pphead)
	{
		*pphead = pfind->next;
		free(pfind);
	}
	else if(pfind->next==NULL)
	{
		slPopback(pphead);
	}
	else
	{
		struct slnode* cur = *pphead;
		while (cur->next != pfind)
		{
			cur = cur->next;
		}
		cur->next = pfind->next;
		free(pfind);
	}
}

至此基本所有的功能都实现了

我们可以测试一下

看看首先 尾插 1 2 3 4 5然后头插一个0

此时链表为 0 1 2 3 4 5 

然后再头删 尾删 链表为  1 2 3 4

再在2的位置前插入10,删除4这个结点

最终结果为 1 2 10 3      ok对上了

总结

到这里单链表的实现就完成了,还是多练

祝大家开心

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值