【数据结构(C语言)】链表

前言

本篇文章将介绍什么是链表,以及如何用C语言实现基础的单链表

什么是链表

链表是一种线性表

我们知道,线性表在逻辑上是连续的,但在物理上不一定连续,就是说,表中数据的存放地址不一定连续

链表就是这样,在逻辑上连续,在物理上不一定连续


链表存储数据的方式

链表中含有一个个节点,每个节点都存放着数据和指向下一个节点的指针,最后一个节点指向空

image-20231121172304426


链表存储的优势

相对于顺序表来说:

  • 链表增加和删除数据不用对数据进行挪动,更加高效

  • 链表每增加一个节点都会开辟一块单独的空间,删除时也会释放空间,不存在空间的浪费

增加数据
image-20231121174551723

删除数据
image-20231121174937099


了解链表的原理后,我们就可以将其实现了

模块化

将代码封装为不同模块,提升代码可读性,还可以使我们编程更加方便

Slist.h

包含头文件,定义数据类型和结构体,以及函数声明等

Slist.c

用来实现函数接口的各种功能

test.c

用来测试代码


定义结构体

先将要存储的数据类型进行定义

typedef int SLNDataType;

这里存储的数据是整型,如果要存其他类型的数据,只需修改 int 即可

定义结构体

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

这个结构表示链表的一个节点,data 用来存放数据,next 用来指向下一个节点


链表的接口

想要对链表进行增删查改等操作,就要定义接口,以下是常用的接口

// 打印
void SLTPrint(SLNode* phead);
// 尾插
void SLTPushBack(SLNode** pphead, SLNDataType val);
// 尾删
void SLTPopBack(SLNode** pphead);
// 头插
void SLTPushFront(SLNode** pphead, SLNDataType val);
// 头删
void SLTPopFront(SLNode** pphead);
// 查找某个数据
SLNode* SLTFind(SLNode* phead, SLNDataType val);
// 在 pos 前插入节点
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType val);
// 删除 pos 节点
void SLTErase(SLNode** pphead, SLNode* pos);
// 在 pos 后插入节点
void SLTInsertAfter(SLNode* pos, SLNDataType val);
// 删除 pos 的后一个节点
void SLTEraseAfter(SLNode* pos);
// 销毁
void SLTDestroy(SLNode** pphead);

其中有些接口用到了二级指针,这是为什么呢?

我们要创建一个链表,一般是定义一个节点指针,初始化为NULL

SLNode* head = NULL;

这个指针是用来指向链表的首结点的,当需要要对 head 指针修改时,就需要把 head 的地址传给函数,也就是传二级指针

例如,现在一个节点都没有,链表为空,当要尾插一个节点时,就要修改 head 了
如果只传一级指针给尾插函数,那么函数中的指针 phead 是 head 的拷贝,修改 phead 并不会对 head 有任何影响

void SLTPushBack(SLNode* phead, SLNDataType val);

image-20231121205812606

如果传的是 head 的地址,在函数中将地址解引用后再修改,那就可以对 head 完成实质性的修改

void SLTPushBack(SLNode** pphead, SLNDataType val);

image-20231121211618077

总结:当需要修改 head 指针时,需要传 head 的地址,即二级指针


尾插

void SLTPushBack(SLNode** pphead, SLNDataType val);

插入数据之前,我们先断言一下

assert(pphead != NULL);

这里断言的作用是防止链表不存在,对空指针进行解引用操作,后面的接口会有相同的断言,不再解释

插入一个节点之前需要先创建一个节点,鉴于后面的接口还会创建节点,我们就写一个创建节点的函数

没什么好说的,就是开辟一个节点,如果开辟成功就存入数据,最后返回新节点的地址

SLNode* CreateNode(SLNDataType val)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
    // 开辟空间失败
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
    // 开辟成功
	newnode->data = val;
	newnode->next = NULL;
	return newnode;
}

创建新节点

SLNode* newnode = CreateNode(val);

接着进行尾插操作

这里就要分情况了:
链表为空,需要修改 head;
不为空,则不需要修改head,找尾节点,将尾节点的 next 指针指向新节点

链表为空

直接将新节点的地址赋给 head

image-20231121214240399

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}

链表不为空

那就需要找尾节点了

尾节点的特点就是 next 指针为空,我们以此来遍历

while (tail->next != NULL)
	{
		tail = tail->next;
	}

循环结束,找到尾节点

将尾节点的 next 修改为指向新节点

SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;

代码:

void SLTPushBack(SLNode** pphead, SLNDataType val)
{
	assert(pphead != NULL);
	// 新节点
	SLNode* newnode = CreateNode(val);
    // 为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
    // 不为空
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

销毁

定义指针 cur 来进行遍历,当 cur 为空时,循环结束,将head置空

注意:在删除节点时,不能先释放空间,再访问 next ,因为此时的节点空间已经还给了系统

image-20231121223648814

要么先把 next 存起来再释放

image-20231121223934000

while (cur != NULL)
	{
		SLNode* next = cur->next;
		free(cur);
		cur = next;
	}

要么把当前节点存起来,cur 指向下一个,再释放

image-20231121225315990

代码:

void SLTDestroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* cur = *pphead;
	while (cur != NULL)
	{
		SLNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	*pphead = NULL;
}

打印

void SLTPrint(SLNode* phead);

因为打印不需要修改 head,所以传一级指针就行

定义指针 cur 来进行遍历

SLNode* cur = phead;

打印当前指针指向的结构体的数据,然后指向下一个节点,当前指针为空时,循环结束,并打印NULL表示结束

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

下面来测试尾插:

尾插 1 2 3 4

void test1()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	printf("尾插:\n");
	SLTPushBack(&head, 1);
	SLTPrint(head);
	SLTPushBack(&head, 2);
	SLTPrint(head);
	SLTPushBack(&head, 3);
	SLTPrint(head);
	SLTPushBack(&head, 4);
    SLTPrint(head);
    
	SLTDestroy(&head);
}

int main()
{
	test1();
	return 0;
}

运行结果:image-20231122114028258


尾删

void SLTPopBack(SLNode** pphead);

链表为空肯定不能尾删的,这里进行断言

// 链表必须存在
assert(pphead);
// 链表不能为空
assert(*pphead);

删除还是要分情况的:

链表只有一个节点,删除节点后,就需要修改 head 为空
链表有多个节点,不需要修改 head,而是需要找到尾节点的前一个结点,删除尾节点后,将前一个节点的 next 置空

一个节点

如果 *pphead 的 next 为空,说明只有一个节点

删除节点,将head置空

image-20231122105828474

	// 只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}

多个节点

*pphead 的 next 不为空,那么就存在多个节点

找到尾节点的前一个结点,删除尾节点后,将前一个节点的 next 置空

image-20231122110338830

定义指针 prev 用来遍历寻找

SLNode* prev = *pphead;

如果 prev 的下一个节点的 next 为空,那就说明下一节点是尾节点

image-20231122110759239

找到尾节点后,将其释放,并将前一节点的 next 置空

		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;

代码:

void SLTPopBack(SLNode** pphead)
{
	// 链表必须存在
	assert(pphead);
	// 链表不能为空
	assert(*pphead);

	// 只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	// 多个节点
	else
	{
		// 找尾节点的前一个节点
		SLNode* prev = *pphead;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;
	}
}

测试:

尾插 1 2 3 4,再尾删 4 3 2 1

void test1()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	printf("尾插:\n");
	SLTPushBack(&head, 1);
	SLTPrint(head);
	SLTPushBack(&head, 2);
	SLTPrint(head);
	SLTPushBack(&head, 3);
	SLTPrint(head);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 尾删
	printf("尾删:\n");
	// 删除4
	SLTPopBack(&head);
	SLTPrint(head);	
	// 删除3
	SLTPopBack(&head);
	SLTPrint(head);
	// 删除2
	SLTPopBack(&head);
	SLTPrint(head);
	// 删除1
	SLTPopBack(&head);
	SLTPrint(head);
	// 报错
	//SLTPopBack(&head);

	SLTDestroy(&head);
}

int main()
{
	test1();
	return 0;
}

运行结果:image-20231122114157309

此时链表为空,如果再进行尾删,就会因为断言而停止

image-20231122114258228


头插

头插较为简单,因为无论是链表为空,还是链表只有一个节点或者多个节点,都需要修改 head,不需要分情况

image-20231122113200136

void SLTPushFront(SLNode** pphead, SLNDataType val)
{
	assert(pphead);

	// 新节点
	SLNode* newnode = CreateNode(val);
	// 连接
	newnode->next = *pphead;
	*pphead = newnode;
}

测试:

头插 1 2 3 4

void test2()
{
	SLNode* head = NULL;

	// 头插
	printf("头插:\n");
	SLTPushFront(&head, 1);
	SLTPrint(head);
	SLTPushFront(&head, 2);
	SLTPrint(head);
	SLTPushFront(&head, 3);
	SLTPrint(head);
	SLTPushFront(&head, 4);
	SLTPrint(head);

	// 销毁
	SLTDestroy(&head);
}
int main()
{
	test2();
	return 0;
}

运行结果:image-20231122113713434


头删

同样地,头删总是会修改 head,也不需要分情况

将当前节点存起来,head 指向下一节点,删除当前节点

image-20231122165130754

void SLTPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLNode* tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
}

测试:

先头插 1 2 3 4,再尾删 4 3 2 1

void test2()
{
	SLNode* head = NULL;

	// 头插
	printf("头插:\n");
	SLTPushFront(&head, 1);
	SLTPrint(head);
	SLTPushFront(&head, 2);
	SLTPrint(head);
	SLTPushFront(&head, 3);
	SLTPrint(head);
	SLTPushFront(&head, 4);
	SLTPrint(head);
	
	// 尾删
	printf("头删:\n");
	// 删4
	SLTPopFront(&head);
	SLTPrint(head);
	// 删3
	SLTPopFront(&head);
	SLTPrint(head);
	// 删2
	SLTPopFront(&head);
	SLTPrint(head);
	// 删1
	SLTPopFront(&head);
	SLTPrint(head);
	// 报错
	//SLTPopFront(&head);

	// 销毁
	SLTDestroy(&head);
}
int main()
{
	test2();
	return 0;
}

运行结果:image-20231124204012306

同样,因为断言的存在,在链表为空时再头删就会报错

image-20231124204138814


查找

SLNode* SLTFind(SLNode* phead, SLNDataType val);

因为不需要修改 head ,所以只将 head 本身传进函数即可

val 为要查找的目标值,如果找到了就返回节点的地址,找不到就返回NULL

定义变量 cur ,用来遍历链表

SLNode* cur = phead;

只要 cur 不为空,循环就不结束,当 cur 为空时,说明链表遍历完了

每次循环都将节点中的值与目标值比较,如果匹配,返回节点地址
遍历一遍还没返回,那就是说链表中没有目标值,返回NULL

SLNode* SLTFind(SLNode* phead, SLNDataType val)
{
	SLNode* cur = phead;

	while (cur)
	{
         // 找到了
		if (cur->data == val)
		{
			return cur;
		}
		cur = cur->next;
	}
    // 没找到
	return NULL;
}

在 pos 前插入节点

void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType val);

断言时,我们要严格确定 pos 是链表的有效节点:pos 节点不能是NULL

assert(pos);

而要在 pos 节点前插入,pos 又不为空,那链表至少得有一个节点吧,即链表不为空

assert(*pphead);

插入操作,依旧是分为两种情况
pos 等于 head ,说明是头插,直接调用我们写的头插接口
pos 不等于 head ,那就找 pos 的前一个节点,将前一个节点的 next 改为新节点,新节点的 next 改为 pos

pos 和 head相等

// pos = head
if (pos == *pphead)
{
	// 调用头插
	SLTPushFront(pphead, val);
}

pos 和 head不相等

找到前一个节点,然后重新连接

image-20231124220903710

定义 prev ,循环,当 prev 的下一节点是 pos 时,循环停止

// pos != head
else
{
    SLNode* newnode = CreateNode(val);
    // 找前一个节点
    SLNode* prev = *pphead;
    while (prev->next != pos)
    {
    prev = prev->next;
    }
    // 重新连接
    prev->next = newnode;
    newnode->next = pos;
}

代码:

void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType val)
{
    //链表存在
	assert(pphead);
    //pos节点不为空
	assert(pos);
    //链表不为空
	assert(*pphead);

	// pos = head
	if (pos == *pphead)
	{
		// 调用头插
		SLTPushFront(pphead, val);
	}
	// pos != head
	else
	{
		SLNode* newnode = CreateNode(val);
		// 找前一个节点
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		// 重新连接
		prev->next = newnode;
		newnode->next = pos;
	}
}

测试:

链表中有数据 1 2 3 4,在 1 的前面插入 10,之后在 2 的前面插入 20

void test3()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 前面插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsert(&head, pos, 10);
	SLTPrint(head);
	// 在 2 前面插入 20
	pos = SLTFind(head, 2);
	SLTInsert(&head, pos, 20);
	SLTPrint(head);
}
int main()
{
	test3();
	return 0;
}

运行结果:image-20231124222106534


删除 pos 节点

断言同上,不再赘述

//链表存在
assert(pphead);
//pos节点不为空
assert(pos);
//链表不为空
assert(*pphead);

删除节点还是分为两种情况
pos = head,调用头删
pos != head,找 pos 的前一节点,然后重新连接,删除 pos 节点

image-20231124223135135

void SLTErase(SLNode** pphead, SLNode* pos)
{
	//链表存在
	assert(pphead);
	//pos节点不为空
	assert(pos);
	//链表不为空
	assert(*pphead);

	// pos = head
	if (pos == *pphead)
	{
		// 调用头删
		SLTPopFront(pphead);
	}
	// pos != head
	else
	{
        // 找前一节点
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
        // 重新连接
		prev->next = pos->next;
		free(pos);
	}
}

测试:

接着上面的测试,删除 10 节点,删除 20 节点

void test3()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 前面插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsert(&head, pos, 10);
	SLTPrint(head);
	// 在 2 前面插入 20
	pos = SLTFind(head, 2);
	SLTInsert(&head, pos, 20);
	SLTPrint(head);

	//删除 10 
	pos = SLTFind(head, 10);
	SLTErase(&head, pos);
	SLTPrint(head);
	//删除 20
	pos = SLTFind(head, 20);
	SLTErase(&head, pos);
	SLTPrint(head);
}
int main()
{
	test3();
	return 0;
}

运行结果:image-20231124223744051


在 pos 后插入节点

void SLTInsertAfter(SLNode* pos, SLNDataType val);

既然要在 pos 后插入节点,那么 pos 不能为空

assert(pos);

这里只需要访问 pos 节点和它的下一节点,不需要访问 pos 的前一节点,所以只需传 pos 的地址

image-20231124224503945

void SLTInsertAfter(SLNode* pos, SLNDataType val)
{
	assert(pos);

	SLNode* newnode = CreateNode(val);
	newnode->next = pos->next;
	pos->next = newnode;
}

测试:

链表已有数据 1 2 3 4,在 1 的后面插入 10,在 4 的后面插入 40

void test4()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 后插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsertAfter(pos, 10);
	SLTPrint(head);
	// 在 4 后插入 40
	pos = SLTFind(head, 4);
	SLTInsertAfter(pos, 40);
	SLTPrint(head);
}
int main()
{
	test4();
	return 0;
}

运行结果:image-20231124225210828


删除 pos 的后一个节点

void SLTEraseAfter(SLNode* pos);

要删 pos 的下一结点的话,那么 pos 和它的下一节点都不能为空

assert(pos);
assert(pos->next);

将 pos 的下一节点存起来,pos 的 next 指向下一节点的 next,最后删除下一节点

image-20231124230540752

void SLTEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLNode* tmp = pos->next;
	pos->next = pos->next->next;
	free(tmp);
	tmp = NULL;
}

测试:

接着上面的测试数据,删除 1 的下一节点 和 4 的下一节点

void test4()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 后插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsertAfter(pos, 10);
	SLTPrint(head);
	// 在 4 后插入 40
	pos = SLTFind(head, 4);
	SLTInsertAfter(pos, 40);
	SLTPrint(head);
	// 删除1的下一节点
	pos = SLTFind(head, 1);
	SLTEraseAfter(pos);
	SLTPrint(head);
	// 删除4的下一节点
	pos = SLTFind(head, 4);
	SLTEraseAfter(pos);
	SLTPrint(head);
}
int main()
{
	test4();
	return 0;
}

运行结果:image-20231124231059379


代码

Slist.h

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

typedef int SLNDataType;

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

// 打印
void SLTPrint(SLNode* phead);
// 尾插
void SLTPushBack(SLNode** pphead, SLNDataType val);
// 尾删
void SLTPopBack(SLNode** pphead);
// 头插
void SLTPushFront(SLNode** pphead, SLNDataType val);
// 头删
void SLTPopFront(SLNode** pphead);
// 查找某个数据
SLNode* SLTFind(SLNode* phead, SLNDataType val);
// 在 pos 前插入节点
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType val);
// 删除 pos 节点
void SLTErase(SLNode** pphead, SLNode* pos);
// 在 pos 后插入节点
void SLTInsertAfter(SLNode* pos, SLNDataType val);
// 删除 pos 的后一个节点
void SLTEraseAfter(SLNode* pos);
// 销毁
void SLTDestroy(SLNode** pphead);

Slist.c

#include "SList.h"

// 创建节点
SLNode* CreateNode(SLNDataType val)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = val;
	newnode->next = NULL;
	return newnode;
}

// 尾插
void SLTPushBack(SLNode** pphead, SLNDataType val)
{
	assert(pphead != NULL);

	SLNode* newnode = CreateNode(val);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}


// 销毁
void SLTDestroy(SLNode** pphead)
{

	SLNode* cur = *pphead;
	while (cur != NULL)
	{
		SLNode* tmp = cur;
		cur = cur->next;
		free(tmp);
	}
	*pphead = NULL;
}
// 打印
void SLTPrint(SLNode* phead)
{
	SLNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}


// 尾删
void SLTPopBack(SLNode** pphead)
{
	// 链表必须存在
	assert(pphead);
	// 链表不能为空
	assert(*pphead);

	// 只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	// 多个节点
	else
	{
		// 找尾节点的前一个节点
		SLNode* prev = *pphead;
		while (prev->next->next != NULL)
		{
			prev = prev->next;
		}
		free(prev->next);
		prev->next = NULL;
	}
}


// 头插
void SLTPushFront(SLNode** pphead, SLNDataType val)
{
	assert(pphead);

	// 新节点
	SLNode* newnode = CreateNode(val);
	// 连接
	newnode->next = *pphead;
	*pphead = newnode;
}


// 头删
void SLTPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);

	SLNode* tmp = *pphead;
	*pphead = (*pphead)->next;
	free(tmp);
}


// 查找
SLNode* SLTFind(SLNode* phead, SLNDataType val)
{
	SLNode* cur = phead;

	while (cur)
	{
		if (cur->data == val)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}


// 在 pos 前插入节点
void SLTInsert(SLNode** pphead, SLNode* pos, SLNDataType val)
{
	//链表存在
	assert(pphead);
	//pos节点不为空
	assert(pos);
	//链表不为空
	assert(*pphead);

	// pos = head
	if (pos == *pphead)
	{
		// 调用头插
		SLTPushFront(pphead, val);
	}
	// pos != head
	else
	{
		SLNode* newnode = CreateNode(val);
		// 找前一个节点
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		// 重新连接
		prev->next = newnode;
		newnode->next = pos;
	}
}


// 删除 pos 节点
void SLTErase(SLNode** pphead, SLNode* pos)
{
	//链表存在
	assert(pphead);
	//pos节点不为空
	assert(pos);
	//链表不为空
	assert(*pphead);

	// pos = head
	if (pos == *pphead)
	{
		// 调用头删
		SLTPopFront(pphead);
	}
	// pos != head
	else
	{
		SLNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;

		free(pos);
	}
}


// 在 pos 后插入节点
void SLTInsertAfter(SLNode* pos, SLNDataType val)
{
	assert(pos);

	SLNode* newnode = CreateNode(val);
	newnode->next = pos->next;
	pos->next = newnode;
}


// 删除 pos 的后一个节点
void SLTEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLNode* tmp = pos->next;
	pos->next = pos->next->next;
	free(tmp);
	tmp = NULL;
}

test.c

#include "SList.h"


void test1()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	printf("尾插:\n");
	SLTPushBack(&head, 1);
	SLTPrint(head);
	SLTPushBack(&head, 2);
	SLTPrint(head);
	SLTPushBack(&head, 3);
	SLTPrint(head);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 尾删
	printf("尾删:\n");
	// 删除4
	SLTPopBack(&head);
	SLTPrint(head);	
	// 删除3
	SLTPopBack(&head);
	SLTPrint(head);
	// 删除2
	SLTPopBack(&head);
	SLTPrint(head);
	// 删除1
	SLTPopBack(&head);
	SLTPrint(head);
	// 报错
	//SLTPopBack(&head);

	SLTDestroy(&head);
}

void test2()
{
	SLNode* head = NULL;

	// 头插
	printf("头插:\n");
	SLTPushFront(&head, 1);
	SLTPrint(head);
	SLTPushFront(&head, 2);
	SLTPrint(head);
	SLTPushFront(&head, 3);
	SLTPrint(head);
	SLTPushFront(&head, 4);
	SLTPrint(head);
	
	// 尾删
	printf("头删:\n");
	// 删4
	SLTPopFront(&head);
	SLTPrint(head);
	// 删3
	SLTPopFront(&head);
	SLTPrint(head);
	// 删2
	SLTPopFront(&head);
	SLTPrint(head);
	// 删1
	SLTPopFront(&head);
	SLTPrint(head);
	// 报错
	//SLTPopFront(&head);

	// 销毁
	SLTDestroy(&head);
}

void test3()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 前面插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsert(&head, pos, 10);
	SLTPrint(head);
	// 在 2 前面插入 20
	pos = SLTFind(head, 2);
	SLTInsert(&head, pos, 20);
	SLTPrint(head);

	//删除 10 
	pos = SLTFind(head, 10);
	SLTErase(&head, pos);
	SLTPrint(head);
	//删除 20
	pos = SLTFind(head, 20);
	SLTErase(&head, pos);
	SLTPrint(head);
}

void test4()
{
	SLNode* head = NULL;

	// 尾插:1 2 3 4
	SLTPushBack(&head, 1);
	SLTPushBack(&head, 2);
	SLTPushBack(&head, 3);
	SLTPushBack(&head, 4);
	SLTPrint(head);

	// 在 1 后插入 10
	SLNode* pos = SLTFind(head, 1);
	SLTInsertAfter(pos, 10);
	SLTPrint(head);
	// 在 4 后插入 40
	pos = SLTFind(head, 4);
	SLTInsertAfter(pos, 40);
	SLTPrint(head);
	// 删除1的下一节点
	pos = SLTFind(head, 1);
	SLTEraseAfter(pos);
	SLTPrint(head);
	// 删除4的下一节点
	pos = SLTFind(head, 4);
	SLTEraseAfter(pos);
	SLTPrint(head);
}
int main()
{
	test4();
	return 0;
}

总结

单链表的优点

  • 链表增加和删除数据不用对数据进行挪动,更加高效
  • 链表每增加一个节点都会开辟一块单独的空间,删除时也会释放空间,不存在空间的浪费

单链表的缺点

尾插尾删都需要遍历找前一节点,效率低

结束,再见 😄

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

阿洵Rain

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

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

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

打赏作者

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

抵扣说明:

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

余额充值