数据结构之无头单向非循环链表

链表在数据结构中相当的重要,在以后的实际工作中使用率也很高,所以今天分享一些关于链表的基本操作:

1.链表的概念以及定义:

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,形式如下图所示:
在这里插入图片描述

typedef struct Node         //链表的定义
{
	int value;               //结点的数据域
	struct Node* next;       //结点的指针域
}Node;

2.无头单向非循环链表的一些基本操作:

1.链表的初始化
这里说一下为什么要使用二级指针,首先是定义一个指针指向首结点,因为链表的操作全程靠指针完成,凡是牵扯到首结点指针指向的改变都得用二级指针。

void SListInit(Node **ppFirst)
{
	*ppFirst = NULL;
}

2.销毁链表

void SListDestroy(Node *first)
{
	Node* ptr;                //定义ptr指针记录当前结点的下一个结点位置,然后再free当前结点
	for (Node* cur = first; cur != NULL; cur = ptr) //定义cur指针来操作链表,而不是直接使用头指针。
	{
		ptr = cur->next;
		free(cur);
	}
}

3.链表的头插

//头插不需要担心头指针为空的问题,因为这个方法一样适用
void SListPushFront(Node **ppFirst, int v)
{
		Node* node = (Node*)malloc(sizeof(Node));  //先为要插入的新结点申请空间
		node->next = *ppFirst;            
		*ppFirst = node;
		node->value = v;
}

4.链表的尾插

void SListPushBack(Node **ppFirst, int v)
{
	if (*ppFirst == NULL)      //头指针为空的话就相当于头插
	{
		Node* node = (Node*)malloc(sizeof(Node));
		node->next = *ppFirst;
		*ppFirst = node;
		node->value = v;
		return; 
	}
	//找到尾结点
	Node* cur = *ppFirst;
	while (cur->next != NULL){
		cur = cur->next;
	}

	Node* node = (Node*)malloc(sizeof(Node));
	node->next = NULL;           //这行和下面这行的代码顺序不能改变,否则会出错
	cur->next = node;
	node->value = v;
}

5.链表的头删

void SListPopFront(Node **ppFirst)
{
	assert(*ppFirst != NULL);       //首先判断是否为空
	Node* ptr = (*ppFirst)->next;   //首先定义指针ptr记录首结点的下一个结点的位置,然后再释放首结点,否则会导致后面的数据全部丢失
	free(*ppFirst);
	*ppFirst = ptr;
}

6.链表的尾删

void SListPopBack(Node **ppFirst)
{
	assert(*ppFirst != NULL);         
	if ((*ppFirst)->next == NULL)     //这是链表中只有一个结点的情况
	{
		free(*ppFirst);
		*ppFirst = NULL;
		return;
	}
	Node* ptr = *ppFirst;
	while (ptr->next->next!= NULL)     //找到倒数第二个结点
	{
		ptr = ptr->next;
	}
	free(ptr->next);    //先释放尾结点所占内存,否则会导致内存泄漏
	ptr->next = NULL;   //再让倒数第二个结点的next=NULL
}

7.查找数据为指定值的结点

Node * SListFind(Node* first, int v)
{
	for (Node* cur = first; cur != NULL; cur = cur->next) 
	{
		if (cur->value == v)
			return cur;
	}
}

8.在pos后插入一个结点

void SListInsertAfter(Node *pos, int v)
{
	Node* node = (Node*)malloc(sizeof(Node));
	node->value = v;
	node->next = pos->next;
	pos->next = node;
}

9.删除pos后的一个结点

void SListEraseAfter(Node *pos)
{
	Node* ptr = pos->next;              //定义一个指针ptr记录pos的下一个结点地址
	pos->next = pos->next->next;        //改变pos指针域,让pos和他后面的第二个结点相连
	free(ptr);                          //此时释放ptr
}

10.修改指定值的结点的值

void SListModify(Node *ppFirst,int u,int v)
{
	if (ppFirst == NULL)
	{
		return;
	}
	Node* ret = SListFind(ppFirst,u);
	if (ret != NULL)
	{
		if (u != v)
		{
			ret->value = v;
		}
		else
			return;
	}
	else
		return;
}

11.打印链表

void SLprint(Node* first)
{
	if (first == NULL)
	{
		return;
	}
	for (Node* cur = first; cur != NULL; cur = cur->next)
	{
		printf("%d ",cur->value);
	}
}

3.总结

将顺序表和链表做一个对比:
顺序表:
空间连续,支持随机访问且方便;但是中间或前面插入的时间复杂度为O(n),容量的代价比较大。
链表:
是以结点为单位存储,不支持随机访问,在任意位置插入删除时间复杂度O(1),没有增容问题,插入一个开辟一个空间,但是查找的时间复杂度就为O(n),对于不清楚总量的数据,空间利用率高。
链表和顺序表的使用场景还得因情况而定。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值