数据结构——单链表

一、链表简介

前面我们学习了顺序表,我们发现它有以下缺点:

1.进行数据增添或删除时,可能需要将很多数据后移或前移,时间复杂度高

2.增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

3.增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到
200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。

为了解决这种情况,我们可以使用一种新的数据结构——链表

链表与顺序表一样属于线性表,与顺序表不同的是,它在物理结构上不一定是连续的,相较于顺序表,我们在对链表进行增、删、查、改操作时要方便许多。

二、链表的结构

链表分为很多种,这里我们主要研究的是单链表中的不带头单向不循环链表,链表是由一个一个的节点构成的,每个节点都可以用来存储数据(就像火车由一节一节的车厢组成),但是我们如何将这些节点联系起来?

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;//存储数据
	struct SListNode* next;//指向下一个节点的指针
}SLTNode;

可以看到,我们通过指针来将节点联系起来, 每一个指针存储下一个节点的地址,这里我们将int定义为SListNode是因为我们要存储的数据类型不一定是int,还有可能是char等类型,如果我们需要将int改为char的话就只需改一次就可以了。

三、单链表的实现

由于代码量比较大,这里使用了SList.c、SList.h、Test.c三个文件来编写,为了方便后面解说,这里先将头文件里的代码全部写出:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义节点的结构
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;//存储数据
	struct SListNode* next;//指向下一个节点的指针
}SLTNode;

//链表的打印
void SLTPrint(SLTNode* phead);

//尾插
void SLTPushBack(SLTNode** phead, SLTDataType x);

//头插
void SLTPushFront(SLTNode** phead, SLTDataType x);

//尾删
void SLTPopBack(SLTNode** pphead);

//头删
void SLTPopFront(SLTNode** pphead);

//查找某个数据
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode** pphead);

头文件中包含了  1.节点的定义  2.要实现的函数 

1.链表的打印

void SLTPrint(SLTNode* phead)
{
	//定义一个临时指针,使代码执行后不影响原指针
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

 

 2.链表的尾插

 通过上图我们我们知道了尾插的两种情况,下面是代码实现:

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//有空链表和非空链表两种情况
	
	//申请新节点
	SLTNode* newnode=SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}

 关于上图代码,有一个问题,我们能否将函数SLTPushBack的参数改为用一级指针接受,答案是不行,因为尾插函数的形参是要对链表进行改变的,而想要对实参进行改变,我们必须传地址而不是传值,在这个函数中我们要接收的是指向第一个节点的指针的地址,故用二级指针。

 3.链表的头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//为x申请一个新节点
	SLTNode* newnode=SLTBuyNode(x);
	//*pphead为空也可行
	newnode->next = *pphead;
	*pphead = newnode;

}

 头插同样有两种情况,一种是链表为空,一种是链表不为空,但它们都适用于这段代码。

4.链表的尾删

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//链表为空不能进行尾删
	assert(*pphead);
	//只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//有多个节点
	else
	{
		//记录尾节点
		SLTNode* ptail = *pphead;
		//记录尾节点的前一个节点
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

 

5.链表的头删 

void SLTPopFront(SLTNode** pphead)
{
	    assert(pphead && *pphead);
		SLTNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
}

 同时适用于只有一个节点和多个节点的情况

6.在链表中查找某个数据

直接遍历即可

//不需要改变实参,故传值就可以
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->next == x)
		{
			return pcur;
		}
			pcur = pcur->next;
	}
	return NULL;
}

 

 7.在指定位置之前插⼊数据

 

 

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos && *pphead);
	//为数据x申请一个节点
	SLTNode* newnode = SLTBuyNode(x);
	//用于找pos的前一个节点
	if (pos == *pphead)
	{
		//调用头插函数
		SLTPushFront(pphead, x);

	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

8.删除pos节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

 

 9.在指定位置之后插入数据

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	//pos是头节点
	if (pos == *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}//pos不是头节点
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 

 

10.删除pos之后的节点

void SLTEraseAfter(SLTNode* pos)
{
	//pos和pos的下一个节点都不能为空
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

11.销毁链表 

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

下面是SList.c的全部代码,

#include"SList.h"
void SLTPrint(SLTNode* phead)
{
	//定义一个临时指针,使代码执行后不影响原指针
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//有空链表和非空链表两种情况
	
	//申请新节点
	SLTNode* newnode=SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;
	}
}

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//为x申请一个新节点
	SLTNode* newnode=SLTBuyNode(x);
	//*pphead为空也可行
	newnode->next = *pphead;
	*pphead = newnode;

}

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//链表为空不能进行尾删
	assert(*pphead);
	//只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//有多个节点
	else
	{
		//记录尾节点
		SLTNode* ptail = *pphead;
		//记录尾节点的前一个节点
		SLTNode* prev = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

void SLTPopFront(SLTNode** pphead)
{
	    assert(pphead && *pphead);
		SLTNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
}

//不需要改变实参,故传值就可以
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->next == x)
		{
			return pcur;
		}
			pcur = pcur->next;
	}
	return NULL;
}

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos && *pphead);
	//为数据x申请一个节点
	SLTNode* newnode = SLTBuyNode(x);
	//用于找pos的前一个节点
	if (pos == *pphead)
	{
		//调用头插函数
		SLTPushFront(pphead, x);

	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	//pos是头节点
	if (pos == *pphead)
	{
		//头删
		SLTPopFront(pphead);
	}//pos不是头节点
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLTEraseAfter(SLTNode* pos)
{
	//pos和pos的下一个节点都不能为空
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值