链表(1)

开篇备忘录: “过我嶙峋 拥我九春”


正文开始

1. 前言

问题与思考

前面我们了解了顺序表

  1. 中间/头部的插⼊删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷⻉数据,释放旧空间。会有不⼩的消耗。
  3. 增容⼀般是呈2倍的增⻓,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插⼊了5个数据,后⾯没有数据插⼊了,那么就浪费了95个数据空间。

链表就可以很好解决以上的问题

首先定义链表类型,和顺序表一样, 定义一个头文件,两个源文件,在头文件中进行链表的定义和函数的声明,以及包含需要的库函数文件

在这里插入图片描述

在头文件中定义链表结构体

typedef int SLTDatatype;
//定义结点的结构
typedef struct SListNode
{
	SLTDatatype data;
	struct SListNode* next;
}SLTNode;

2. 链表的打印

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

传入一个形参, 这个形参指向了链表首地址,定义临时变量pcur指向phead, 这是一种良好的习惯, 不直接改变phead, 只要pcur->next不为NULL,就打印pcur->data 的值,然后让pcur指向下一个结点。

这里先手动创建一些结点, 后面实现了插入函数, 就不需要手动创建了

	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	node2->data = 2;
	node3->data = 3;
	node4->data = 4;
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;
	SLTNode* plist = node1;
	SLPrint(plist);

打印结果

在这里插入图片描述

3. 链表的插入与删除

头插与尾插

尾插

首先这里需要进行传实参, 因为phead的值我可能会改变, 而不是去改变一个临时拷贝的变量, 比如说开始phead所指向的为NULL, 我需要插入一个结点而让phead指向这个结点, 这里需要断言你给我传递的链表首结点地址的地址不可以为NULL, 分为两种情况, 一开始就是空链表和非空链表, 再插入之前都需要开辟一个结点, 直接可以封装成函数.

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 SLPushBack(SLTNode** pphead, SLTDatatype x)
{
	assert(pphead);
	//空链表和非空链表
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)//如果链表为空
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* ptail = *pphead;//寻找最后一个节点
		while (ptail->next)//分为两种情况,否则这里会对NULL解引用
		{
			ptail = ptail->next;
		}
		ptail->next = newnode;//找到之后让它的指针指向新的结点
	}
}

头插

这里会相对简单, 因为头插无论是否为空链表, 代码都可以插入, 让新节点的指针指向头结点, 再让头指针指向新的结点,此时头结点就是新节点

void SLPushFront(SLTNode** pphead, SLTDatatype x)
{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

测试一下

	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPrint(plist);
	SLPushFront(&plist, 2);
	SLPrint(plist);
	SLPushFront(&plist, 3);
	SLPrint(plist);

运行结果:

在这里插入图片描述

头删与尾删

尾删

这里还是分两种情况, 看代码注释

void SLPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);//这里链表的地址不能为空,链表也不能为空,否则无结点删除
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;//寻找尾结点的前一个结点
		SLTNode* ptail = *pphead;
		while (ptail->next)//如果删除头结点,这里终止循环
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;//让尾节点的前一个结点指针指向NULL
		//这里就会非法访问
	}
}

头删

这里只需要先保存头结点的下一个结点的位置,然后释放掉头结点, 让保存的结点成为新的头结点

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

代码测试:

	SLTNode* plist = NULL;
	SLPushBack(&plist, 1);
	SLPrint(plist);
	SLPushFront(&plist, 2);
	SLPrint(plist);
	SLPushFront(&plist, 3);
	SLPrint(plist);
	SLPopBack(&plist);
	SLPrint(plist);
	SLPopFront(&plist);
	SLPrint(plist);

在这里插入图片描述

4. 完整代码

下面附上完整代码:

头文件:

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


typedef int SLTDatatype;
//定义结点的结构
typedef struct SListNode
{
	SLTDatatype data;
	struct SListNode* next;
}SLTNode;

//打印
void SLPrint(SLTNode* phead);

//尾插
void SLPushBack(SLTNode** pphead, SLTDatatype x);
//头插
void SLPushFront(SLTNode** pphead, SLTDatatype x);

//尾删
void SLPopBack(SLTNode** pphead);
//头删
void SLPopFront(SLTNode** pphead);

函数的实现文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"

void SLPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d ", pcur->data);
		pcur = pcur->next;
	}
	printf("\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 SLPushBack(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 SLPushFront(SLTNode** pphead, SLTDatatype x)
{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SLPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
		prev->next = NULL;
	}
}

//头删
void SLPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

5. 总结与思路

链表是一种数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表的插入、删除操作相对于数组来说更为灵活。

头插法和尾插法是链表的两种常见插入操作。头插法是将新节点插入到链表的头部,即新节点的next指针指向原来的头节点,然后更新头节点指针;而尾插法是将新节点插入到链表的尾部,即原来的尾节点的next指针指向新节点,然后更新尾节点指针。

头删和尾删是链表的两种常见删除操作。头删是将链表的头节点删除,即更新头节点指针为原头节点的next指针;尾删是将链表的尾节点删除,即更新尾节点指针为倒数第二个节点,并将倒数第二个节点的next指针置为空。

本文分享先到这, 如果认为文章有帮助的话,感谢点赞收藏关注! ! !


  • 34
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

酷酷学!!!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值