线性表(链表的实现)

本文介绍了链表的概念,包括非连续非顺序的存储结构以及通过指针链接实现逻辑顺序。文章详细阐述了无哨兵卫头节点链表的创建、初始化、节点创建、尾插、头插、尾删、头删、查找、插入和删除节点等操作,并提供了相应的C语言实现代码。此外,还讨论了使用二级指针的原因以及在操作链表时的注意事项。
摘要由CSDN通过智能技术生成

一.概念

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

 写链表的方法有很多

  • 有哨兵卫头节点的链表
  • 无哨兵卫头节点的链表(需要传二级指针)

(1)有哨兵卫头节点的链表(head里面可以不赋值,就是随机值)

 (2)无哨兵卫头节点的链表(需要传二级指针)

二.链表的实现(无哨兵卫头节点)  

1.定义一个链表

typedef int SLTDataType;    //定义SLDataType为int型,方便更改数据类型

typedef struct SListNode
{
	SLTDataType data;         //要存的数据
	struct SListNode* next;   //指向下一个结点的地址
}SLTNode;

2.初始化链表

这种链表的初始化很简单(直接在主函数中定义为NULL

SLTNode* plist = NULL;   //初始化链表为NULL

3.链表节点的创造 

这个函数是一个频繁要调用的函数,在插入数据时都需要调用这个函数去构造一个节点

//创造一个节点
SLTNode* BuyListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));    //去malloc一个新的节点
	if (newnode == NULL)          //判断malloc是否成功
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;           //赋值
	newnode->next = NULL;        //节点的next定义为NULL
	return newnode;
}

4.尾插 

     为什么要传二级指针?

  • 前面初始化链表 SLTNode* plist = NULL; 要改变plist里的内容,就要传递地址,plist本身就是一个一级指针,一个一级指针的地址就需要二级指针来接收
  • 在运用时
    	SLTNode* plist = NULL;
    	SListPushBack(&plist, 1);   //&plist是一个一级指针的地址,要用二级指针接受
//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)   //这里要用二级指针,要改变形参里的内容,就要传地址
{
	assert(pphead);                        //断言,第一次进去时 *pphead 是NULL,但 pphead 是*pphead的地址,不为空
	SLTNode* newnode = BuyListNode(x);     //调用函数,去创造一个节点
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else 
	{
		//找到尾节点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)       //找到链表的结尾
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

5.头插

头插也要用二级指针(只要是改动链表中的数据的,都要传递二级指针)

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);                      //断言,好习惯

	SLTNode* newnode = BuyListNode(x);  //调用函数,去创造一个节点

	newnode->next = *pphead;
	*pphead = newnode;
}

 6.尾删

//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead != NULL);        //链表不为NULL,如果为NULL,则会报错

	if ((*pphead)->next == NULL)    //只有一个节点,直接删空链表并释放空间
	{
		free(*pphead);
		*pphead = NULL;
	}
	else                            //链表有多个节点,遍历寻找尾节点,并且要记录尾节点的前一个结点
	{
		SLTNode* tail = *pphead;
		SLTNode* prev = NULL;      

		while (tail->next != NULL)
		{
			prev = tail;           //记录尾节点的前一个节点,链表没办法找到前面的那个节点
			tail = tail->next;
		}

		free(tail);                //释放尾节点的空间
		tail = NULL;

		prev->next = NULL;
	}
}

7.头删

头删比尾删简单,链表的头节点比尾节点好找

//头删
void SListPopFront(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* front = *pphead;    //定义一个指针指向头节点
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		*pphead = front->next;   
	}

	free(front);                //释放头节点的空间
	front = NULL;
}

8.查找一个值(返回值是,这个数的地址)

不需要二级指针,查找数据,而不改变数据,所以不需要二级指针

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)            //遍历,找到链表中的值
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;         //没有,返回NULL
}

9. 在pos后插入一个节点

也可以在pos前面插入一个节点这个插入比在后面插入麻烦很多,需要判断pos是否是第一个节点(头节点),还需要找到pos的前一个节点进行插入,倒不如在pos后面插入,简单,适合。

//在pos后插入,这个更适合,也简单
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);                          //pos不能为NULL

	SLTNode* newnode = BuyListNode(x);    //调用函数,创造新节点

	newnode->next = pos->next;
	pos->next = newnode;
}

10.删除pos后的一个节点

//删除pos后一个节点
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	assert(pphead);
	assert(pos->next);      //删除的节点不能为NULL

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);             //释放next空间
	next = NULL;            //可有可无,next是形参,置不置空,没有影响,养成好习惯,加上也行
}

11.打印链表

打印链表就传用一级指针就行,只访问链表里的数据,不对链表修改,所以一级指针就行

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

12.释放空间

//释放链表
void SListDestory(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* cur = *pphead;
	while (cur)              //遍历链表,一个节点一个节点的释放
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = NULL;
		cur = next;
	}
	*pphead = NULL;
}

总结

  • 整个链表都是使用二级指针进行传参的,对于指针的应用应当更加熟练(知道一级指针是什么,二级指针是什么)
  • 尽量不要去动链表的第一个节点(*pphead),要遍历增删要定义指针去完成任务,最后 *pphead 就是这个链表的第一个节点,就能找到整个链表内容(可以通过定义的tail和cur去遍历链表)

全部代码 

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int SLTDataType;    //定义SLDataType为int型,方便更改数据类型

typedef struct SListNode
{
	SLTDataType data;         //要存的数据
	struct SListNode* next;   //指向下一个结点的地址
}SLTNode;

//创造一个节点
SLTNode* BuyListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));    //去malloc一个新的节点
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;                                      //赋值
	newnode->next = NULL;
	return newnode;
}

//打印
void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)   //这里要用二级指针,要改变形参里的内容,就要传地址
{
	assert(pphead);
	SLTNode* newnode = BuyListNode(x);                //调用函数,去创造一个节点
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找到尾节点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)               //找到链表的结尾
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);                      //断言,好习惯

	SLTNode* newnode = BuyListNode(x);  //调用函数,去创造一个节点

	newnode->next = *pphead;
	*pphead = newnode;
}


//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead != NULL);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}

	else
	{
		SLTNode* tail = *pphead;
		SLTNode* prev = NULL;         //链表没办法找到前面的那个,所以要定义
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}

		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

//头删
void SListPopFront(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* front = *pphead;
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		*pphead = front->next;
	}

	free(front);
	front = NULL;
}

//查找
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)            //遍历,找到链表中的值
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return NULL;
}

//在pos后插入,这个 更适合,也简单
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyListNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos后一个
void SListEraseAfter(SLTNode** pphead, SLTNode* pos)
{
	assert(pos);
	assert(pphead);
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
	next = NULL;  //可有可无
}

//释放链表
void SListDestory(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* cur = *pphead;

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

	*pphead = NULL;
}

int main()
{
	SLTNode* plist = NULL;
	printf("头插:");
	SListPushFront(&plist, 1);
	SListPushFront(&plist, 2);
	SListPushFront(&plist, 3);
	SListPushFront(&plist, 4);
	SListPushFront(&plist, 5);
	SListPrint(plist);

	printf("头删:");
	SListPopFront(&plist);
	SListPrint(plist);

	printf("尾插:");
	SListPushBack(&plist, 10);
	SListPushBack(&plist, 20);
	SListPrint(plist);

	printf("尾删:");
	SListPopBack(&plist);
	SListPrint(plist);

	printf("在2后面插入200:");
	SLTNode* pos = SListFind(plist, 2);
	if (pos)                  //pos不为NULl
	{
		SListInsertAfter(pos, 200);
	}
	SListPrint(plist);

	printf("删除2后的200:");
	if (pos)
	{
		SListEraseAfter(&plist, pos);
	}
	SListPrint(plist);

	SListDestory(&plist);        //释放链表
 
	return 0;
}

感谢大家的观看,希望你能从这篇文章中学到一些东西(如有错误,提醒我,我会及时修改)

谢谢大家!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值