C语言数据结构单链表链表

数据结构–单链表

学习了顺序表,我们发现顺序表在向里面存放数据的时候很麻烦,比如我们要使用头插法存放一个数据到顺序表的时候,我们要将整个表都向后挪一位,这个操作就让人很难受。那么有没有一种结构可以让我们存放数据的操作变得简单一些呢?这就要用到线性结构的另一种–链表

链表的概念及结构

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

在这里插入图片描述

注意:

  • 从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
  • 现实中的结点一般都是从堆上申请出来的
  • 从对上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续

链表的分类

实际中链表的结构非常多,单链表、双向链表、带头单链表、带头双向链表、循环链表、不循环链表等等,虽然结构很多,但是我们实际最常用的还是无头单向非循环链表带头双向循环链表

单向或者双向链表

在这里插入图片描述

带头链表

在这里插入图片描述

循环链表

在这里插入图片描述

在这里插入图片描述

链表的一些特点

**1.无头单向非循环链表:**结构简单,一般不会单独用来存数据。实际中更是多为其他数据结构的子结构。

**带头双向循环链表:**机构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。

链表的理解

链表的代码结是这样定义的:

struct Node{
    int data; // 用来存放数据
    struct Node* next; // 存放下一个节点的地址,用于找到下一个节点
}

我在学习链表的时候对于next的理解一直很困惑,后面突然顿悟,next就是指向下一个节点的地址。我们知道,指针是存放地址的,通过这个地址我们能访问到那个位置的数据。而这个next就是存放下一个节点地址的,通过这个地址,我们就能找到下一个节点。

在这里插入图片描述

单链表的实现

链表的操作和顺序表的操作是差不多的,无非是增删改查,这一系列操作。先看看我们要实现的功能

typedef int SLDataType;

// 定义链表的结构
typedef struct SListNode {
	SLDataType data;
	struct SListNode* next;
 }SListNode;

// 创建一个新节点
SListNode* BuyNewNode(SLDataType x);

// 尾插
void SListPushBack(SListNode** pphead, SLDataType x);

// 头插
void SListPushFront(SListNode** pphead, SLDataType x);

// 尾删
void SListPopBack(SListNode** pphead);

// 头删
void SListPopFront(SListNode** pphead);

// 打印链表
void SListPrint(SListNode* pphead);

// 单链表查找
SListNode* SListFind(SListNode* plist, SLDataType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLDataType x);

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

// 单链表的销毁
void SListDestory(SListNode* plist);

首先我们得先把链表的结构给定义出来,就是用结构,定义一个链表结点的结构。如下图,我们要把一个结点划分为两个部分,即data和next部分。data部分用来存储数据,next用来存放下一个结点的位置,也就是用来将结点“链接起来”。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xasr70N8-1636910284535)(D:\blogs\结点.png)]

// 链表结点的定义
typedef struct SListNode {
	SLDataType data;
	struct SListNode* next; // 因为结点是SListNode类型,所以next得是SListNode*
 }SListNode;

在定义好结点的结构之后,就得创建结点,我们可以将它封装到一个函数中

SListNode* BuyNewNode(SLDataType x) {
	SListNode* tmp = (SListNode*)malloc(sizeof(SListNode)); // 为结点申请一块空间
    // 给成员变量赋值
	tmp->data = x;
	tmp->next = NULL;
	return tmp;
}
// 使用创建结点的函数
int main(){
    SListNode* node = BuyNewNode(2);
    return 0;
}

这样我们就的到了单链表的第一个结点,接着就是不断地在这个结点后面插入数据 ,就形成的单链表。下面我们看看几种插入数据的方法。

// 尾插法
void SListPushBack(SListNode** pphead, SLDataType x) {
	if (*pphead == NULL) {
		*pphead = BuyNewNode(x);
	}
	else {
		SListNode* tail = *pphead;
		while (tail->next) {
			tail = tail->next;
		};
		tail->next = BuyNewNode(x);
	}
}

尾插法,故名思意就是每次都将数据插入到链表的尾部。所以,我们定义了一个tail指针,用它来找到链表的尾部。找的方式就是通过循环来遍历链表tail = tail->next;,这段代码就是让tail指针指向tail的下一个结点,实现遍历。


头插法

// 头插
void SListPushFront(SListNode** pphead, SLDataType x) {
	if (*pphead == NULL) {
		*pphead = BuyNewNode(x);
	}
	else {
		SListNode* tmp = BuyNewNode(x);
		tmp->next = *pphead;
		*pphead = tmp;
	}
}

头插法,正好和尾插相反,头插是将新结点插入到链表的最前面,然后返回新节点的地址


尾删

// 尾删
void SListPopBack(SListNode** pphead) {
	if (*pphead == NULL) {
		return;
	}
	if ((*pphead)->next == NULL) {
		free(*pphead);
		*pphead = NULL;
	}
	else {
		SListNode* tail = *pphead;
		while (tail->next->next) {
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

尾删和尾插一样,都需要一个指针来遍历链表,让tail指向最后一个结点的前一个位置,先把最后一个结点free掉,然后再将tail的next指向空。

在这里插入图片描述


// 头删
void SListPopFront(SListNode** pphead) {
	if (*pphead == NULL) {
		return;
	}
	SListNode* tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
	tmp = NULL;
}

头删就是将最前面的一个结点删掉,所以我们需要先将头结点的地址保存起来,然后让头结点指向它的下一个结点,最后在把结点给free掉


// 单链表查找
SListNode* SListFind(SListNode* plist, SLDataType x) {
	if (plist == NULL) {
		return NULL;
	}
	SListNode* tmp = plist;
	while (tmp) {
		if (tmp->data == x) {
			break;
		}
		else {
			tmp = tmp->next;
		}
	}
	return tmp;
}

单链表的查找就很简单了,就是把单链表遍历一下,看看有没有哪个结点的data部分和x是相等的,如果有就返回这个结点的地址,否则返回空。


接下来就是单链表的随机插入和删除,这里的随机不是说,听天由命的让电脑选一个位置插入到链表中,而是,我们自己想把它插到哪就插到哪。

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLDataType x) {
	assert(pos);
	SListNode* newnode = BuyNewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos) {
	assert(pos);
	if (pos->next == NULL) {
		return;
	}
	SListNode* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
	tmp = NULL;

}

当我们不用单链表的时候一定要将它销毁,把申请的空间给释放掉

// 单链表的销毁
void SListDestory(SListNode* plist) {
	assert(plist);
	SListNode* prev = plist;
	SListNode* cur = plist->next;
	while (cur) {
		free(prev);
		prev = cur;
		cur = cur->next;
	}
	free(prev);
	prev = NULL;
}

下次分享双向带头环形链表,那是一个很棒的结构。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_yiyi_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值