【数据结构】单链表

链表与顺序表的区别

顺序表是在内存中开辟一个连续的空间进行存储,如果空间不过就需要进行扩容,一边情况下我们扩容后的大小是原来大小的二倍,势必会造成空间的浪费。除此之外,就是顺序表开辟的空间是连续的,这就造成了另一个问题,扩容时如果原来开辟空间的地址处后面的空间不足,那么就需要在内存中重新寻找一个足够大的空间进行扩容,这个过程还需要把原地址空间的数据拷贝到新地址空间,并且还需要对原地址空间进行释放。再就是如果对顺序表再进行头插的时候效率也非常的低。
但是链表正好克服了上面顺序表的这些缺点,链表再内存中开辟的空间并不是连续的,它是由一块一块空间由指针链接而成,这样做的好处就是链表的空间是按需申请和销毁的,所以没有空间浪费,再就是链表的头插不需要挪动任何数据,只需要再链表的头部进行插入就可以了。但是也有弊端,比如无法做到像顺序表那样下标的随机访问那样快,而且链表的高速缓存命中率也比顺序表低。
所以这两个结构并不是一个就比另一个要优,它们两个是互补的。

链表的概念和结构

链表一共有八种,分别是:单向无头不循环链表、单向带头不循环链表、单向无头循环链表、单向带头循环链表、双向无头不循环链表、双向带头不循环链表、双向无头循环链表、双向带头循环链表。
在这里插入图片描述
此片博文内容是单向无头不循环链表,这个看起来简单,其实是比较难的。只要把这个理解了,其余的链表你就会觉得非常简单。
单链表是将内存中一块一块空间通过指针链接而构成的线性结构。
在这里插入图片描述
单链表的每一个节点都分为两个区域,一个是值域,一个址域。在这里插入图片描述
值域用来存储数据,址域用来存储下一个节点的地址。
下图为单链表的物理结构
在这里插入图片描述
下图为单链表的逻辑结构
在这里插入图片描述
当然为了可以访问这个链表,我们会定义一个头指针来存储第一个节点的地址。
在这里插入图片描述
如果一个节点后面没有节点,那么这个节点的址域存空指针。在这里插入图片描述

定义单链表的结构体

变量data为值域。
next指针为链表的址域。
在这里插入图片描述
指针plist为链表的头指针。
在这里插入图片描述

单链表的头插 && 节点开辟 && 遍历

在进行插入操作前,我们还要考虑节点开辟的问题。

节点的开辟

在这里插入图片描述

单链表的头插

如果我们要进行头插就要改变plist头指针所存储的地址。
在这里插入图片描述
逻辑结构是这样的
在这里插入图片描述
因为我们要该表plist指针所存储的地址,所以我们再传参的时候,传plist的地址,也就是指针的地址。
在这里插入图片描述

单链表的遍历

这里我们不需要对传过来的头指针进行断言,即使链表为空也要允许遍历,就像你有一张没钱的银行卡,难道没钱银行就不允许你查询余额了吗?
在这里插入图片描述

测试

我们可以用遍历链表的函数来对我们写的头插来进行测试,观察函数是否正确。
在这里插入图片描述

单链表的尾插

单链表的尾插的效率比头插低,因为单链表没有尾指针,所以我们要找到链表的尾节点才可以进行插入。
所以我们要定义一个尾指针tail,来遍历链表找到尾节点。
我们这里找尾节点的判断调剂并不是tail为空指针,如果tail为空指针的话就算给tail性节点的地址,原链表也没有链接上新节点,而且还会造成内存泄露。所以我们判断tail->next是否为空,来找尾节点。
在这里插入图片描述
除此之外,有一个特殊的情况,如果链表为空怎么办?
这时我们要对plist指针进行修改,所以就用不上尾指针,所以这里传参还是plist的地址。
在这里插入图片描述

测试

在这里插入图片描述

单链表的头删

顾名思义,就是把头节点删掉,这个操作要修改plist指向的内容,所以这里传参已让传plist的地址。
在这里插入图片描述
就提操作是,我们用一个指针存储phead的前位置的地址,然后将phead下一个节点的地址给phead,让后释放phead原来指向的节点。
这里要注意,如果链表为空的话就不需要在进行删除。
在这里插入图片描述

测试

在这里插入图片描述

单链表的尾删

尾删与头删相比就比较难了,我们不可以定义一个尾指针直接找尾节点直接删除。
在这里插入图片描述
这做的后果就是新尾指针的next还指向原来的尾节点的地址,但是原来的尾节点以及被释放了,这就出现了野指针的问题。
在这里插入图片描述
所以我们要找的并不是尾节点,而是尾节点的上一个节点。
这里还是需要注意下,如果链表就剩下一个节点的情况,这种情况我们直接让释放phead,并让phead指向空指针。
在这里插入图片描述

测试

在这里插入图片描述

单链表的查找

查找是不需要对链表进行修改的,所以传参的时候只传plist的值就可以了。
在这里插入图片描述
这个函数还有一个拓展功能,就是可以修改查找位置的值。
在这里插入图片描述

单链表的随机插入

随机插入的意思就是你想在哪插入就在哪插入,我们这里的插入是在pos之前进行插入。
在这里插入图片描述
如果pos就是头节点,那么我们直接头插。
有人认为如果pos是空那么是不正确的,因该断言一下,还有的人认为pos为空,就是尾插。我这里采取的是后者。
在这里插入图片描述
我才用后者是因为,如果在写链表的时候就可以直接写随机插入,然后直接在头插和尾插中调用这个函数就可以了

测试

在这里插入图片描述

单链表的随机删除

随机删除也是,你想删除哪里就删除哪里。
这里删除的pos位置的数据,程序与尾删上大致一样。
在这里插入图片描述

测试

在这里插入图片描述

单链表的销毁

单链表的销毁和顺序表的就不同了,顺序表连续的空间,而链表是不连续的,所以链表要一个一个节点进行释放。
销毁函数,我是按照free函数写的,释放完要手动将指向空间的指针置空
在这里插入图片描述

测试

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

代码

SList.h

#pragma once

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

typedef int SLDataType;

typedef struct SListNode
{
	SLDataType data;
	struct SListNode* next;
}SLNode;



void SLPushFront(SLNode** pphead, SLDataType x);

void SLPrint(SLNode* phead);

void SLPustBack(SLNode** pphead, SLDataType x);

void SLPopFront(SLNode** pphead);

void SLPopBack(SLNode** pphead);

SLNode* SLFind(SLNode* phead, SLDataType x);

void SLInsert(SLNode** pphead,SLNode* pos, SLDataType x);

void SLErase(SLNode** pphead, SLNode* pos);

void SLDestroy(SLNode* ph

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
SLNode* CreateSLNode(SLDataType x)
{
	SLNode* newNode = (SLNode*)malloc(sizeof(SLNode));
	if (newNode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}

void SLPushFront(SLNode** pphead, SLDataType x)
{
	assert(pphead);//这里是防止传过来的地址为空
	SLNode* newNode = CreateSLNode(x);
	newNode->next = *pphead;
	*pphead = newNode;
}

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

void SLPustBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newNode = CreateSLNode(x);
	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}

void SLPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
}

void SLPopBack(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLNode* prevTail = *pphead;
		while (prevTail->next->next != NULL)
		{
			prevTail = prevTail->next;
		}
		free(prevTail->next);
		prevTail->next = NULL;
	}
}

SLNode* SLFind(SLNode* phead, SLDataType x)
{
	SLNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead);
	SLNode* newNode = CreateSLNode(x);
	if (pos == *pphead)
	{
		*pphead = newNode;
		newNode->next = pos;
	}
	else
	{
		SLNode* prevPos = *pphead;
		while (prevPos->next != pos)
		{
			prevPos = prevPos->next;
		}
		prevPos->next = newNode;
		newNode->next = pos;
	}
}

void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	if ( *pphead == pos )
	{
		*pphead = (*pphead)->next;
		free(pos);
	}
	else
	{
		SLNode* prevPos = *pphead;
		while (prevPos->next != pos)
		{
			prevPos = prevPos->next;
		}
		prevPos->next = pos->next;
		free(pos);
	}
}

void SLDestroy(SLNode* phead)
{
	while (phead)
	{
		SLNode* del = phead;
		phead = phead->next;
		free(del);
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void Test()
{
	SLNode* plist = NULL;
	/*SLPushFront(&plist, 1);
	SLPushFront(&plist, 2);
	SLPushFront(&plist, 3);
	SLPushFront(&plist, 4);
	SLPrint(plist);*/
	SLPustBack(&plist, 7);
	SLPustBack(&plist, 8);
	SLPustBack(&plist, 9);
	SLPustBack(&plist, 10);
	SLPrint(plist);
	//中间插入
	SLNode* pos = SLFind(plist, 8);
	if (pos != NULL)
	{
		pos->data = 800;
		SLInsert(&plist, pos, 1000);
	}
	SLPrint(plist);
	头插
	//SLInsert(&plist, plist, 1000);
	//SLPrint(plist);
	尾插
	//SLInsert(&plist, NULL, 1000);
	//SLPrint(plist);
	SLErase(&plist, pos);
	SLPrint(plist);
	/*SLPopFront(&plist);
	SLPopFront(&plist);
	SLPopFront(&plist);
	SLPrint(plist);*/
	/*SLPopBack(&plist);
	SLPopBack(&plist);
	SLPopBack(&plist);
	SLPrint(plist);*/
	SLDestroy(plist);
	plist = NULL;
}

int main()
{
	Test();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗!伯!特!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值