无头单链表的增删查改

我们之前接触过顺序表(如果没有了解的童鞋可以看看我的另一篇Blog)
数据结构:顺序表

顺序表的优点与缺点:
我们知道顺序表存储数据可以很容易的对数据进行访问(即随机下标访问)时间复杂度O(1),但是它也有自己的缺陷:比如在频繁增删的场景下时间复杂度很不友好O(n),扩容时会导致空间的浪费等一系列问题。

面对这些问题,还有一种存储结构被称作链表
在这里插入图片描述
今天我们就来引入了链表的概念:
链表是逻辑上有连续关系,但是物理上并不连续的链式存储结构。
数据的逻辑顺序是通过链表中指针的连续次序实现的,可以通过结构体指针实现动态的存储和分配。

链表的优点与缺点:
插入和删除的效率高,只需要改变指针的指向即可完成插入和删除,时间复杂度O(1)。内存利用率高,可以使用内存中细小的不连续的空间,只有在需要时创建空间,大小不固定,拓展灵活。但是链表的查找效率比较低,因为链表是从第一个结点向后遍历查找,时间复杂度O(n)。

链表的结点:
链表中的每个结点都分为两个域,一个数据域和一个指针域,如同铁链一样,将数据连接在一起,第一个元素指向第二个,第二个指向……一直到NULL链表结束。

在这里插入图片描述
链表的结点结构体:

typedef struct Node{
	int value;
	struct Node *next;//保存着下一个结点的地址
}Node;

和顺序表一样,我们也会对链表进行一些列的增删查改等操作,接下来我们一起看看吧。

链表的初始化

Main.c

Node *first;
LinkListInit(&first);

LinkList.c
实则就是初始化一条空链表(一个结点都没有的链表)
*first的地址&(*first),一级指针的地址用二级指针来接收。Node **ppFirst

void LinkListInit(Node **ppFirst){
	*ppFirst = NULL;
}
链表的头插

在这里插入图片描述

  1. 被插入的数据,首先应当申请自己的空间 malloc。
  2. 再将数据域和指针域给向这块空间。
  3. 指针域是原本 *ppFirst的指向。
  4. 头插结束改变 *ppFirst指向新加入元素的结点地址。
void LinkListPushFront(Node **ppFirst,int v){
	Node *node = (Node*)malloc(sizeof(Node));
	node->value = v;
	
	node->next = *ppFirst;
	*ppFirst = node;
}
链表的头删

在这里插入图片描述

  1. 释放头空间前应当首先存储第一个结点的next(指针域)。
  2. 释放头空间 free(*ppFirst)
  3. 改变*ppFirst的指向新的首结点地址。
void LinkListPopFront(Node **ppFirst){
	assert(*ppFirst != NULL);
	Node *next = (*ppFirst)->next;
	free(*ppFirst);
	*ppFirst = next;
}
链表的尾插

在这里插入图片描述

  1. 首先寻找最后一个结点的位置,才能尾插。(从第一个结点向后遍历,直到指针域为NULL,说明到达最后一个结点)
  2. 和头插一样,新的数据要申请自己的空间才能加入链表。
  3. 给这块空间赋予value和指针域,因为是尾插,所以指针域为NULL。
  4. 这时,还应该改变插入前最后一个结点的指针域为新空间的地址。
void LinkListPushBack(Node **ppFirst, int v){
	if (*ppFirst == NULL){
		LinkListPushFront(ppFirst, v);
		return;
	}
	//寻找最后一个结点(至少一个结点)
	Node *cur = *ppFirst;
	while (cur->next != NULL){
		cur = cur->next;
	}
	//cur就是最后一个结点
	Node *node = (Node*)malloc(sizeof(Node));
	node->value = v;
	node->next = NULL;
	//原来最后一个结点的next = 新的结点
	cur->next = node;
}
链表的尾删

在这里插入图片描述

  1. 尾删得找到倒数第二个结点,改变其指针域为NULL。
  2. 释放最后一个结点的空间。
void LinkListPopBack(Node **ppFirst){
	assert(*ppFirst != NULL);
	//只有一个结点
	if ((*ppFirst)->next = NULL){
		free(*ppFirst);
		*ppFirst = NULL;
		return;
	}
	//找到倒数第二个结点(至少两个结点)
	Node *cur = *ppFirst;
	while (cur->next->next != NULL){
		cur = cur->next;
	}

	//释放最后一个结点
	free(cur->next);
	cur->next = NULL;
}
链表的查找
  1. 链表的查找为遍历查找
Node * LinkListFind(const Node *first, int v){
	for (Node *cur = first; cur != NULL;cur = cur->next){
		if (cur->value = v){
			return cur;
		}
	}
	return NULL;
}
链表的释放
void LinkListDestroy(Node *first){
	Node *next;
	for (Node *cur = first; cur != NULL; cur = next){
		next = cur->next;
		free(cur);
	}
}
链表中删除第一个遇到的目标结点

在这里插入图片描述

  1. 用cur->next的value和v比较
  2. 删除目标结点前,先记录cur->next的值,赋给next,删除目标结点后,cur->next = next->next。
void LinkListRemove(Node **ppFirst, int v){
	if (*ppFirst == NULL){
		return;
	}
	Node *cur = *ppFirst;
	if (cur->value == v){
		*ppFirst = cur->next;
		free(cur);
		return;
	}
	while (cur->next != NULL){
		if (cur->next->value = v){
			Node *next = cur->next;
			cur->next = next->next;
			free(next);
			return;
		}
		cur = cur->next;
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值