链表

前言

  • 由于顺序表中存在缺陷或者不能适用其他情况,所以进一步去学习链表。

概念

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

结构

  • 链表有好几种结构,这里学习的是最基础的 无头+单向+非循环的链表,这几个词可以暂时忽略
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
  • 逻辑结构: 为方便理解、使用的想象出的结构,可以形象的表达出链表的特点,可以搭配物理逻辑理解。
    在这里插入图片描述
  • 物理结构: 链表实际在内存中存储的形式
    在这里插入图片描述
  • 上图的代码解释
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;

int main() {
	//这里是从后向前创建,之后会实现接口函数,就是正常顺序
    SListNode plist3;	//创建结构体变量plist3
    plist3.data = 3;	//plist3的data设值
    plist3.next = NULL;	//由于plist3之后没有数据,所以下一个节点指针设空

    SListNode plist2;	//创建结构体变量plist2
    plist2.data = 2;	//plist2的data设值
    plist2.next = &plist3;	//plist2的next指向plist3,所以取plist3的地址赋值给plist2.next

    SListNode plist1;	//创建结构体变量plist1
    plist1.data = 1;	//plist1的data设值
    plist1.next = &plist2;	//plist1的next指向plist2

	//这样就可以通过plist1找到plist2、plist3
	SListNode* p2 = plist1.next; // *p2现在就是&plist2(plist2的结构体指针),但通常不会这么手动赋值下一个节点
	
	//更多是这样使用
	SListNode* cur = &plist1;	//现在cur是plist1的结构体指针
	cur = cur->next;	//等同于 cur = plist1.next 现在cur就是plist2的结构体指针
	
	//通过cur来遍历链表
	while(cur != NULL)
	{
		//cur->data = ...	更改当前节点数据
		cur = cur->next;
	}
    return 0;
}

对比

  • 顺序表

      • 优点:
        支持随机访问(下标访问),存取速度高效。
        空间利用率高。(局部性原理,连续存放,命中率高)
      • 缺点:
        不利于插入和删除操作。
        空间不足就需要扩容,扩容是繁琐且有消耗。
  • 链表

      • 优点:
        链表的插入和删除操作的效率较高,可以把通过地址直接改变节点的指向。
        更合理地使用空间,不需要的空间、节点直接删除释放。
        基本上没有空间限制,只与内存空间大小有关。
      • 缺点:
        占用额外的空间以存储指针(浪费空间,不连续存放,malloc开辟,空间碎片多) 。
        不能进行索引/随机访问,查找时,需要循环链表访问,需要从开始节点一个一个节点去查找元素访问。

接口函数

  • 接口链表

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);

// 单链表打印
void SListPrint(SListNode* plist);

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删
void SListPopBack(SListNode** pplist);

// 单链表头删
void SListPopFront(SListNode** pplist);

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

// 单链表的销毁
void SListDestroy(SListNode** plist);
  • 创建节点(BuySListNode)

顺序表的每个节点都是动态开辟的,为了方便使用,所以写成函数

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* new_node = (SListNode*)malloc(sizeof(SListNode));	//动态申请空间
	assert(new_node);	//断言malloc是否成功
	new_node->data = x;		//插入数据
	new_node->next = NULL;		//next置空
	return new_node;	//返回节点的指针
}
  • 链表打印(SListPrint)

    打印链表数据
// 单链表打印
void SListPrint(SListNode* plist)
{
	//assert(plist);		//断言指针
	SListNode* cur = plist;
	while (cur)		//当前节点不为空的时候打印数据
	{
		printf("%d ", cur->data);
		cur = cur->next;		//把下一个节点的指针赋值给cur
	}
}
  • 链表尾插(SListPushBack)

    在链表的最后一个节点后插入一个节点
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);		//断言指针

	SListNode* new_node = BuySListNode(x);		//创建新节点
	if (!(*pplist))		//当链表为空时(*pplist==NULL)
	{
		*pplist = new_node;		//直接将新创建的节点指针赋值给*pplist
	}
	else		//当链表不为空时
	{
		SListNode* tail = *pplist;		//为了方便使用,将*pplist赋值给tail,接下来就不需要对二级指针操作

		while (tail->next != NULL)		//当tail->next保存了指针时进入循环,直到找到最后一个有数据的节点
		{
			tail = tail->next;			//将下一个节点指针赋值给tail
		}

		tail->next = new_node;			//将新创建的节点指针赋值给tail->next,就是插入到最后一个节点后
	}
}
  • 链表头插(SListPushFront)

    在链表第一个节点之前插入一个节点
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);		//断言指针

	SListNode* new_node = BuySListNode(x);		//创建新节点

	new_node->next = *pplist;		//将链表的第一个节点指针赋值给new_node->next
	*pplist = new_node;				//将new_node赋值给*pplist, new_node现在就是链表第一个节点
}
  • 链表尾删(SListPopBack)

    将链表最后一个节点删除
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* tail = *pplist;
	if (!tail->next)		//当链表只有一个节点时
	{
		free(tail);			//释放空间
		*pplist = NULL;		//将链表置空
	}
	else		//当链表有两个及以上节点时
	{
		while (tail->next->next)		//当tail下一个节点的next不为空时
		{
			tail = tail->next;			//将tail下一个节点赋值给tail
		}

		//当tail下一个节点的next为空时,那么tail的下一个节点就是最后一个节点
		free(tail->next);		//释放掉tail的下一个节点
		tail->next = NULL;		//tail的next置空
	}
}
  • 链表头删(SListPopBack)

    将链表第一个节点删除
// 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* head = (*pplist)->next;		//将链表第一个节点的next赋值给head,head将是链表新的第一个节点
	free(*pplist);		//释放掉第一个节点
	*pplist = head;		//将head作为链表第一个节点
}
  • 链表查找(SListFind)

    查找链表中 x 数据的节点,返回节点的指针
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);		//断言指针
	SListNode* cur = plist;		
	while (cur)		//当cur不为空时
	{
		if (cur->data == x)		//如果cur->data等于x
		{
			return cur;			//返回当前节点指针
		}
		else		//不等就向后寻找
			cur = cur->next;
	}
	return NULL;		//链表没有x,返回空指针
}
  • 指定位置后插入

    配合查找使用,在pos后插入一个节点
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);		//断言指针
	SListNode* new_node = BuySListNode(x);		//创建新节点
	new_node->next = pos->next;					//将pos下一个节点的指针赋值给new_node->next
	pos->next = new_node;						//将new_node赋值给pos的next
}
  • 指定位置后删除

    配合查找使用,删除pos后第一个节点
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);		//断言指针
	assert(pos->next);	//断言pos不是最后一个节点

	SListNode* next = pos->next->next;		//保存pos之后的第二个节点指针
	free(pos->next);		//释放pos之后的第一个节点空间
	pos->next = next;		//将pos的下一个节点设置为之前保存的next
}
  • 链表销毁

    链表每个节点都是动态开辟的,所以需要一个一个释放掉
// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* cur = *pplist;
	while (cur)		//当链表不为空时
	{
		SListNode* next = cur->next;		//保存下一个节点指针
		free(cur);	//释放当前节点空间
		cur = next;		//将下一个节点设置为当前节点
	}
	*pplist = NULL;		//将链表置空
}

完整代码

  • “SeqList.h”

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

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);

// 单链表打印
void SListPrint(SListNode* plist);

// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);

// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);

// 单链表的尾删
void SListPopBack(SListNode** pplist);

// 单链表头删
void SListPopFront(SListNode** pplist);

// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);

// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);

// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

// 单链表的销毁
void SListDestroy(SListNode** plist);
  • “SeqList.c”

#define _CRT_SECURE_NO_WARNINGS
#include "SList.h"

// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
	SListNode* new_node = (SListNode*)malloc(sizeof(SListNode));	//动态申请空间
	assert(new_node);	//断言malloc是否成功
	new_node->data = x;		//插入数据
	new_node->next = NULL;		//next置空
	return new_node;	//返回节点的指针
}


// 单链表打印
void SListPrint(SListNode* plist)
{
	//assert(plist);		//断言指针
	SListNode* cur = plist;
	while (cur)		//当前节点不为空的时候打印数据
	{
		printf("%d ", cur->data);
		cur = cur->next;		//把下一个节点的指针赋值给cur
	}
}


// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
	assert(pplist);		//断言指针

	SListNode* new_node = BuySListNode(x);		//创建新节点
	if (!(*pplist))		//当链表为空时(*pplist==NULL)
	{
		*pplist = new_node;		//直接将新创建的节点指针赋值给*pplist
	}
	else		//当链表不为空时
	{
		SListNode* tail = *pplist;		//为了方便使用,将*pplist赋值给tail,接下来就不需要对二级指针操作

		while (tail->next != NULL)		//当tail->next保存了指针时进入循环,直到找到最后一个有数据的节点
		{
			tail = tail->next;			//将下一个节点指针赋值给tail
		}

		tail->next = new_node;			//将新创建的节点指针赋值给tail->next,就是插入到最后一个节点后
	}
}


// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
	assert(pplist);		//断言指针

	SListNode* new_node = BuySListNode(x);		//创建新节点

	new_node->next = *pplist;		//将链表的第一个节点指针赋值给new_node->next
	*pplist = new_node;				//将new_node赋值给*pplist, new_node现在就是链表第一个节点
}

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* tail = *pplist;
	if (!tail->next)		//当链表只有一个节点时
	{
		free(tail);			//释放空间
		*pplist = NULL;		//将链表置空
	}
	else		//当链表有两个及以上节点时
	{
		while (tail->next->next)		//当tail下一个节点的next不为空时
		{
			tail = tail->next;			//将tail下一个节点赋值给tail
		}

		//当tail下一个节点的next为空时,那么tail的下一个节点就是最后一个节点
		free(tail->next);		//释放掉tail的下一个节点
		tail->next = NULL;		//tail的next置空
	}
}


// 单链表头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* head = (*pplist)->next;		//将链表第一个节点的next赋值给head,head将是链表新的第一个节点
	free(*pplist);		//释放掉第一个节点
	*pplist = head;		//将head作为链表第一个节点
}


// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
	assert(plist);		//断言指针
	SListNode* cur = plist;		
	while (cur)		//当cur不为空时
	{
		if (cur->data == x)		//如果cur->data等于x
		{
			return cur;			//返回当前节点指针
		}
		else		//不等就向后寻找
			cur = cur->next;
	}
	return NULL;		//链表没有x,返回空指针
}


// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
	assert(pos);		//断言指针
	SListNode* new_node = BuySListNode(x);		//创建新节点
	new_node->next = pos->next;					//将pos下一个节点的指针赋值给new_node->next
	pos->next = new_node;						//将new_node赋值给pos的next
}


// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
	assert(pos);		//断言指针
	assert(pos->next);	//断言pos不是最后一个节点

	SListNode* next = pos->next->next;		//保存pos之后的第二个节点指针
	free(pos->next);		//释放pos之后的第一个节点空间
	pos->next = next;		//将pos的下一个节点设置为之前保存的next
}


// 单链表的销毁
void SListDestroy(SListNode** pplist)
{
	assert(pplist);		//断言指针
	assert(*pplist);		//断言指针
	SListNode* cur = *pplist;
	while (cur)		//当链表不为空时
	{
		SListNode* next = cur->next;		//保存下一个节点指针
		free(cur);	//释放当前节点空间
		cur = next;		//将下一个节点设置为当前节点
	}
	*pplist = NULL;		//将链表置空
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值