C语言手撕单链表

一、链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,也就是内存存储不是像顺序表那么连续存储,而是以结点的形式一块一块存储在堆上的(用动态内存开辟)。

既然在内存上不是连续存储,那我们如何将这一块一块单独的空间链接起来呢?

我们可以创建一个结构体充当我们的结点,里面分别存放数据(数据域)和下一个结点的地址(指针域),这样我们就可以将一块一块单独的空间链接起来了。

而单链表,顾名思义就是单向链接的链表,效果如同下图

前言:

在讲解单链表的各个接口前,很有必要讲解以下单链表的物理内存到底是如何存储的,先掌握这个,接下来的讲解就会更容易理解

头结点指向的地址就是第一个结点的总地址

第一个结点的指针域指向的是第二个结点的总地址,所以分为两个地址

一个是结点的总地址,另一个是结点总地址里面的next指针存放的指针域的地址,这个指针域的地址又指向了下一个结点的总地址。

看下面的图解内存存储的数据,实际中是没有箭头的,把箭头去掉,才是内存中真正的存储模式

二、接口实现

对数据结构我们一般采用增删查改去实现。

cpp 复制代码
#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);

SLTNode* BuySListNode(SLTDataType x);

void SLTPushBack(SLTNode** phead, SLTDataType x);

void SLTPushFront(SLTNode** phead, SLTDataType x);

void SLTPopFront(SLTNode** phead);

void SLTPopBack(SLTNode** phead);

1、遍历单链表打印函数

一般需要创建一个临时变量去接收头结点

cpp 复制代码
//遍历链表肯定需要头结点
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;//一般需要临时创建变量
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

2、创建单链表函数

cpp 复制代码
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror(malloc);
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
}

3、尾插

需要分情况讨论,

  • 如果链表本身是空,直接改变头结点的指针,直接指向新建的结点
  • 若不为空,需要找到尾结点

TIP:何时用指针变量,何时用二级指针?

改变结构体本身,需要结构体指针

改变结构体指针,需要结构体指针指针(二级指针),并且要有解引用的操作!

刚才第一种情况,直接将头结点的指针改变成新指针的地址,这种直接改变指针的模式,就需要二级指针,进行解引用才能达到目的。

cpp 复制代码
void SLTPushBack(SLTNode** phead, SLTDataType x)
{
	//需要分情况讨论,如果链表本身是空
	if (*phead == NULL)
	{
		SLTNode* newnode = BuySListNode(x);
		//改变结构体本身,需要结构体的指针
		//改变结构体指针,需要结构体指针指针(二级指针),并且要有解引用的操作
		*phead = newnode;
	}
	else
	{
		//先找尾结点
		SLTNode* newnode = BuySListNode(x);
		SLTNode* tail = *phead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
	
}

4、头插

头插肯定需要二级指针,因为每次头插都要改变头指针指向的位置

cpp 复制代码
//头插肯定需要二级指针,因为每次头插都要改变头指针指向的位置
void SLTPushFront(SLTNode** phead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *phead;
	*phead = newnode;
}

5、尾删

分三种情况

  • 第一种是链表为空,需要assert断言检查
  • 第二种链表中只有一个结点,需要改变结构体指针,因此函数传参也是需要二级指针
  • 第三种链表有多于一个的结点,需要先找到尾结点。
cpp 复制代码
void SLTPopBack(SLTNode** pphead)
{
	//分三种情况
	// 第一种就是链表为空
	assert(*pphead);
	// 第二种链表只有一个结点,需要改变结构体指针
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		pphead = NULL;
	}
	//第三种链表多于一个结点
	else
	{
		SLTNode* tail = *pphead;
		SLTNode* tailprev = NULL;
		while (tail->next != NULL)
		{
			tailprev = tail;
			tail = tail->next;
		}
		free(tail);
		tailprev->next = NULL;//严格要求尾指针前一个的next指向null
	}
}

6、头删

不需要分类讨论,直接改变头节点的指针指向,但是需要一个临时变量存储结点,便于后面的操作

cpp 复制代码
void SLTPopFront(SLTNode** phead)
{
	assert(*phead);
	SLTNode* newnode = (*phead)->next;
	free(*phead);
	*phead = newnode;
}
相关推荐
Canmag 兴隆磁性2 小时前
C 系列充磁机
c语言·开发语言·学习·永磁材料·充磁·退磁
_Narcissus_3 小时前
排序算法总结表格
c语言·数据结构·c++·笔记·算法·排序算法·总结
小雪崩4 小时前
嵌入式学习 day27:标准IO
linux·c语言·学习
一只小小的芙厨4 小时前
前缀和与差分
数据结构·算法
一只小小的芙厨5 小时前
最短路总结
数据结构·算法
xx~t5 小时前
嵌入式——Linux软件编程2
linux·运维·服务器·c语言·学习方法
来一碗刘肉面7 小时前
有向无环图 DAG(描述表达式)
数据结构
郝学胜_神的一滴8 小时前
干货版《算法导论》18:遍历增删原理、时间复杂度与集合序列实现全解
数据结构·算法
BK贝壳8 小时前
Python底层:1亿个布尔值缓存失效标记,list爆内存numpy爆拷贝,bool-hybrid-array混合存储实测
数据结构·python·缓存·性能优化·list·numpy
luj_17689 小时前
变频技术核心原理揭秘
服务器·c语言·开发语言·经验分享·算法