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

1.什么是单向链表

单向链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:数据域和指向下一个节点的指针域。每个节点只能访问它后面的节点,而不能访问它前面的节点。

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

链表其实就是针对顺序表的缺点来设计的,补足的就是顺序表的缺点。如下图所示:

单向链表的特点:

1、每个节点包含数据域和指向下一个节点的指针域;
2、最后一个节点的指针指向空值(NULL),表示链表的结束;
3、可以动态的添加或者删除节点,链表的长度可以根据需要进行扩展或缩小。
4、可以根据指针迅速插入或删除节点,而不需要移动其他节点。

相对于数组来说,单向链表的优点和缺点:

优点:单向链表插入和删除元素的时间复杂度为O(1),插入和删除的操作不需要像数组一样进行元素的移动;链表长度可以动态调整,没有固定大小的限制。

缺点:单向链表如果要访问特定位置的元素需要从头开始遍历,时间复杂度为O(n);相比于数组,链表需要额外使用指针域用以存储下一个节点的地址,会占用更多的内存空间。

由于单向链表的特点,它常常被用于需要频繁插入和删除元素的场景,或者在事先无法确定数据大小和数量的情况下使用。

2.顺序表和链表的区别和联系

2.1 顺序表

优点:顺序表的空间连续、支持随机访问。

缺点:1.头插或者中间插入或者删除数据的时间复杂度为O(N);2.顺序表增容的代价比较大。

2.2 链表

优点:任意位置插入或者删除数据的时间复杂度为O(1);2.没有增容的消耗,按照需要申请结点空间,链表使用完毕后可以直接释放。

缺点:链表以结点为单位存储,不支持随机访问。

3.链表的实现

3.1 无头单向非循环链表的功能

//1、创建新的结点
ListNode* CreateListNode(ListDataType val);

//2、链表的尾插
void ListPushBack(ListNode** ppHead);

//3、链表的尾删
void ListPopBack(ListNode** ppHead);

//4、链表的头插
void ListPushFront(ListNode** ppHead,ListDataType val);

//5、链表的头删
void ListPopFront(ListNode** ppHead);

//6、链表的打印,即遍历链表
void ListPrint(const ListNode* pHead);

//7、查找单链表中的某个数据
ListNode* ListFind(ListNode* pHead, ListDataType val);

//8、在当前位置之后插入一个结点
void ListInsertAfter(ListNode* pos, ListDataType val);

//9、删除当前位置后面一个结点
void ListEraseAfter(ListNode* pos);

3.2 链表的定义

我们是使用一个结构来定义无头单向非循环链表的基本结构,如下所示,该结构体包括数据域和指针域两个部分。

//链表的结点
typedef struct ListNode
{
	ListDataType data;
	struct ListNode* next;
}ListNode;

3.3 创建新的链表结点

创建一个新的链表结点,一般用于插入链表。

//1、创建新的结点
ListNode* CreateListNode(ListDataType val)
{
	//使用malloc()函数申请一个新的结点
	ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));

	if (newNode == NULL)
	{
		printf("申请结点失败\n");
		exit(-1);
	}

	newNode->data = val;

	//需要将新结点的next指针域置空,如果不置空,则next指针域为随机值,
	//可能会将该随机值当作该链表下一个结点的地址
	newNode->next = NULL;
	
	return newNode;
}

3.4 链表的尾插

//2、链表的尾插
//此时需要遍历该链表找到链表的尾部,插入结点时,头结点分为空和不为空
void ListPushBack(ListNode** ppHead, ListDataType val)
{
	ListNode* newNode = CreateListNode(val);

	//如果头结点为空,则新结点作为头结点
	if (*ppHead == NULL)
	{
		*ppHead = newNode;
	}
	else
	{
		//找链表的尾部
		ListNode* tail = *ppHead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

3.5 链表的尾删

//3、链表的尾删
void ListPopBack(ListNode**ppHead)
{
	//链表的尾删分3种情况
	//1.链表为空
	//2.链表只有一个结点
	//3.链表有一个以上的结点
	if (*ppHead == NULL)
	{
		return;
	}
	else if((*ppHead)->next==NULL)
	{
		free(*ppHead);
		*ppHead = NULL;
	}
	else
	{
		ListNode* prev = NULL;
		ListNode* tail = *ppHead;

		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		//此处的tail可以置空,也可以不需要置空,因为tail是局部变量,该函数结束后就自动销毁了
		prev->next = NULL;
	}
}

3.6 链表的头插

//4、链表的头插
void ListPushFront(ListNode** ppHead, ListDataType val)
{
	ListNode* newNode = CreateListNode(val);

	newNode->next = *ppHead;
	*ppHead = newNode;
}

3.7 链表的头删

//5、链表的头删
void ListPopFront(ListNode** ppHead)
{
	//分3种情况
	//1.空
	//2.只有1个结点
	//3.有1个以上的结点
	if (*ppHead == NULL)
	{
		return;
	}
	else
	{
		ListNode* next = (*ppHead)->next;
		free(*ppHead);
		*ppHead = next;
	}
}

3.8 链表的打印

//6、链表的打印,即遍历链表
void ListPrint(const ListNode* pHead)
{
	//遍历链表时,不要使用assert函数来断言指针pHead是否为空,
	//如果指针pHead为空,则该链表为空链表
	while (pHead != NULL)
	{
		printf("%d->", pHead->data);
		pHead = pHead->next;
	}
	printf("NULL\n");
}

3.9 查找单链表中的某个数据

//7、查找单链表中的某个数据
ListNode* ListFind(const ListNode* pHead,ListDataType val)
{
	while (pHead)
	{
		if (pHead->data == val)
		{
			return pHead;
		}
		pHead = pHead->next;
	}
	return NULL;
}

3.10 在当前位置之后插入一个结点

//8、在当前位置之后插入一个结点
void ListInsertAfter(ListNode* pos, ListDataType val)
{
	assert(pos);

	ListNode* newNode = CreateListNode(val);
	newNode->next = pos->next;
	pos->next = newNode;
}

3.11 在当前位置之后删除一个结点

//9、在当前位置之后删除一个结点
void ListEraseAfter(ListNode* pos)
{
	assert(pos);
	if (pos->next)
	{
		ListNode* posNext = pos->next;
		ListNode* nextnext = posNext->next;
		pos->next = nextnext;
		free(posNext);
	}
}

完整代码实现可以参考:代码实现

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值