数据结构之链表(C语言)

今天这篇博客我会介绍链表以及它的实现

链表的定义

链表是一种物理存储结构上非连续且非顺序的存储结构,链表的逻辑顺序通过各个节点之间的指针链接实现

链表的结构

 链表的结构可以这样表示:

可以把它理解成一辆小火车,指针起到链接每个车厢的作用

链表有很多种类,包括带头/不带头、循环/非循环、双向/非双向

带头链表

带头链表相比不带头链表,多出了一个不存储任何数据的头节点,这个头节点又被称为“哨兵位”

循环链表

循环链表最后一个节点的指针指向它的头节点,这让它在逻辑上形成了一个闭环

双向链表

双向链表的每个节点多出了一个指针,用于指向该节点的前一个节点,这样我们就能在访问一个节点时回到它的前一个节点

链表的实现

接下来我会演示无头单向非循环单链表的实现,学会这一个后就能触类旁通实现其他链表

先讲讲一个节点是怎么出来的,各个节点是怎么链接的

节点的创建

第一步:开辟节点

链表一个节点的代码表示

struct plist
{
	DataType Data;
	struct plist* next;
};

首先创建链表的第一个节点,它的next指针指向NULL,同时我们需要记录它的地址,用于在之后访问整个链表

第二步:链接节点

创建第二个节点之后,我们需要对这几个节点进行链接,具体就是将一个节点的next指针指向下一个节点,往后需要以这个地址来访问整个链表

链表的基本操作

链表的基本操作包括增删查改,包括:

头部插入/尾部插入节点

头部删除/尾部删除节点

链表节点的查找

指定位置插入/删除节点

销毁链表

几点需要注意的地方:

1.对于这个的查找和尾部删除等操作,都需要通过保存的第一个节点的位置来遍历整个链表进行

2.对各个节点的next指针进行的操作都需要使用二级指针,因为我们对链表的增删查改等操作时改变了next指针,事实上我们是改变了这个next指针的地址。当我们只是将一级指针作为参数传给函数时,相当于只是将一级指针作为形式参数在函数中进行操作,影响不了这个一级指针的地址

(我在尽力用我有限的文化来解释这个原因)

3.对于每个函数接口,我们需要进行assert操作,来预防对函数的不正确操作引起的程序崩溃等问题

4.可以将链表的数据类型统一typedef,这样方便各种数据类型的存储

链表的完整代码

typedef int DataType;
#include<assert.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct plist
{
	DataType Data;
	struct plist* next;
}SList;

//打印链表
void plistPrint(SList** pphead)
{
	assert(pphead);
	if (*pphead != NULL)
	{
		SList* cur = *pphead;
		while (cur)
		{
			printf("%d->", cur->Data);
			cur = cur->next;
		}
		printf("NULL");
	}
	else
		printf("NULL");
}

//开辟节点
SList* BuyNode(DataType x)
{
	SList* node = (SList*)malloc(sizeof(SList));
	node->Data = x;
	node->next = NULL;
	return node;
}

//尾插
void PushBack(DataType x, SList** pphead)
{
	SList* node = BuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	SList* cur = *pphead;
	while (cur->next)
	{
		cur = cur->next;
	}
	cur->next = node;
}

//头插
void PushFront(DataType x, SList** pphead)
{
	assert(pphead);
	SList* SLNode = BuyNode(x);
	SLNode->next = *pphead;
	*pphead = SLNode;
}

//尾删
void PopBack(SList** pphead)
{
	assert(pphead);
	assert(*pphead);

	

	//单个节点
	if ((*pphead)->next == NULL)
	{
		*pphead = NULL;
	}

	else
	{
		//多个节点
		SList* prev = *pphead;
		SList* cur = (*pphead)->next;
		while (cur->next)
		{
			cur = cur->next;
			prev = prev->next;
		}
		free(cur);
		cur = NULL;
		prev->next = NULL;
	}
}

//查找
SList* SLFind(SList** pphead,DataType x)
{
	assert(pphead);
	assert(*pphead);
	SList* cur = *pphead;
	while (cur->Data != x)
	{
		cur = cur->next;
	}
	if (cur->Data == x)
		return cur;
	else
		return NULL;
}

//选择插入(在节点前插入)
void SLInsert(SList** pphead, SList* pos, DataType x)
{
	assert(pphead);
	assert(pos);
	assert(*pphead);

	//只有一个节点
	if (pos == *pphead)
	{
		PushFront(x,pphead);
	}
	//多个节点
	else
	{
		SList* node = BuyNode(x);	
		SList* cur = (*pphead)->next;
		SList* prev = *pphead;
		while (cur != pos)
		{
			cur = cur->next;
			prev = prev->next;
		}
		prev->next = node;
		prev->next->next = cur;

	}
}

//选择插入(在之后插入)
void InsertAfter(SList* pos, DataType x)
{
	assert(pos);
	SList* node = BuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//删除在pos之后的数
void PopAfter(SList* pos)
{
	assert(pos);
	if (pos->next == NULL)
		return;
	else
	{
		SList* tmp = pos->next;
		pos->next = pos->next->next;
		free(tmp);
		tmp = NULL;
	}
}

//头删
void PopFront(SList** pphead)
{
	assert(pphead);
	SList* tmp = *pphead;
	//只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SList* tmp = *pphead;
		*pphead = (*pphead)->next;
		free(tmp);
		tmp = NULL;
	}
}


//删除pos位置
void SLErase(SList** pphead, SList* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (pos == *pphead)
	{
		PopFront(pphead);
	}
	else
	{
		SList* cur = (*pphead)->next;
		SList* prev = *pphead;
		while (cur != pos)
		{
			cur = cur->next;
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;

	}
}

//销毁链表
void SLDestroy(SList** pphead)
{
	assert(pphead);

	SList* cur = (*pphead);
	while (cur)
	{
		SList* next = cur->next;
		free(cur);
		cur = NULL;
	}

	*pphead = NULL;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值