第27讲:单链表专题

本文介绍了单链表的基本概念、特点,以及如何在C语言中实现单链表的创建、节点操作(如前后增删和指定位置插入/删除),并强调了链表在内存管理上的优势。
摘要由CSDN通过智能技术生成

1.单链表是什么?

2.实现单链表

1.单链表是什么?

单链表是什么?

和顺序表类似,单链表是用来存储数据的。

单链表有什么特点?

逻辑上是连续的,物理结构上却不是

为什么要用单链表?是有什么好处吗?

还记得之前的顺序表吧?

1.realloc扩大空间时,当前位置空间不足时,会将该位置数据拷贝,在其位置开辟空间并将数据放进去,最后还要将原位置的空间释放,消耗时间巨大。而链表不同,链表时用malloc申请空间的,在屋里结构上不连续,逻辑上会通过某种方式连接,使其连续。开辟空间时,只需在任意位置开辟,再将其连接就可以了。

2.在顺序表的前面或中间插入数据时,要将所有数据往后移一位,消耗时间巨大。单链表不同,单链表只需让需插入的节点与插入位置的两个节点相连接就可以了,消耗时间不大。

可见,单链表优点多,值得我们去学习!

2.实现单链表

链表图解:

先看代码(代码后面有详解)

Linked lists.h

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

typedef int linkdatatype;//初始化首节点指针

typedef struct node//节点
{
	linkdatatype data;//节点数据
	struct node* next;//下一个节点地址
}node;

void initlist(node** pphead);//初始化首元素指针

void printlist(node* phead);//打印链表

node* mall(node* phead);//创建节点

void pushback(node** pphead);//后增

void pushfront(node** pphead);//前增

void popback(node** pphead);//后删

void popfront(node** pphead);//前删

void pushpos(node** pphead, int pos);//指定位置插入

void poppos(node** pphead, int pos);//指定位置删除

void cheak(node** pphead, int data);//找出对应数据的位置

void breaklist(node** pphead);//销毁链表

Linked lists.c

#define _CRT_SECURE_NO_WARNINGS
#include "Linked lists.h"

void initlist(node** pphead) //初始化首节点指针
{
	*pphead = NULL;
}

void printlist(node* phead)//打印链表
{
	if (phead == NULL)
	{
		return;
	}
	while (phead)
	{
		printf("%d ", phead->data);
		phead = phead->next;
	}
	putchar('\n');
}

node* mall(node* phead)//创建节点
{
	node* newnode = (node*)malloc(sizeof(*phead));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->next = NULL;
	scanf("%d", &newnode->data);//在该节点装入数据
	return newnode;//返回节点地址
}

void pushback(node** pphead)//后增
{
	assert(pphead);
	node* newnode = mall(*pphead);
	if (*pphead == NULL)//如果没有节点
	{
		*pphead = newnode;
		return;
	}
	node* ptail = *pphead;
	while (ptail->next)//至少有一个节点
	{
		ptail = ptail->next;
	}
	ptail->next = newnode;
}

void pushfront(node** pphead)//前增
{
	assert(pphead);
	node* newnode = mall(*pphead);
	newnode->next = *pphead;//将新节点与旧的首节点连接
	*pphead = newnode;//让新节点成为首节点
}

void popback(node** pphead)//后删
{
	assert(pphead);
	if (*pphead = NULL)//没有节点,不用删
	{
		return;
	}
	node* ptail = *pphead;
	if (ptail->next == NULL)
	{
		free(ptail);
		*pphead = NULL;
		return;
	}
	node* prev = NULL;
	while (ptail->next)//多个节点
	{
		prev = ptail;
		ptail = ptail->next;
	}
	free(prev->next);
	prev->next = NULL;
	ptail = NULL;
}

void popfront(node** pphead)
{
	assert(pphead);
	if (*pphead = NULL)//没有节点,不用删
	{
		return;
	}
	node* ptail = *pphead;
	*pphead = ptail->next;
	free(ptail);
	ptail = NULL;
}

void pushpos(node** pphead, int pos)//指定位置插入
{
	assert(pphead);
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	if (pos <= 0 || pos > count + 1)//输入错误,直接return
		return;
	if (*pphead == NULL || pos == 1)//没有节点或需要首插时
	{
		pushfront(pphead);
		return;
	}
	ptail = *pphead;
	node* newnode = mall(*pphead);
	node* pafter = NULL;
	int i;
	for (i = 0; i < pos - 2; i++)//有节点时
	{
		ptail = ptail->next;//被插入位置左边的元素
		pafter = ptail->next;//被插入位置的元素
	}
	ptail->next = newnode;//插入新节点
	newnode->next = pafter;
}

void poppos(node** pphead, int pos)
{
	assert(pphead);
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	if (pos <= 0 || pos > count)//不符合要求,return
	{
		return;
	}
	if (pos == 1)//需要删除首节点时
	{
		*pphead = (*pphead)->next;
		return;
	}
	ptail = *pphead;
	node* pafter = NULL;
	int i;
	for (i = 0; i < pos - 2; i++)
	{
		ptail = ptail->next;//ptail是被删除节点的左节点
	}
	pafter = ptail->next;
	pafter = pafter->next;//pafter是被删除节点的右节点
	free(ptail->next);
	ptail->next = pafter;//让被删除节点的左节点连接被删除节点的右节点
}

void cheak(node** pphead, int data)//找出对应数据的位置
{
	node* ptail = *pphead;
	int count = 0;
	while (ptail)//遍历链表,找元素
	{
		if (ptail->data == data)
			printf("%d ", count + 1);
		ptail = ptail->next;
		count++;
	}
	putchar('\n');
}

void breaklist(node** pphead)//销毁链表
{
	assert(pphead);
	if (*pphead == NULL)
	{
		return;
	}
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	int i;
	ptail = *pphead;
	while (count)//元素个数就是销毁次数
	{
		for (i = 0; i < count - 1; i++)//销毁非首节点
		{
			ptail = ptail->next;
		}
        free(ptail);
		ptail = NULL;
		count--;
	}
	*pphead = NULL;
}

test.c 

#define _CRT_SECURE_NO_WARNINGS
#include "Linked lists.h"
int main()
{
	node* phead;
	initlist(&phead);
	breaklist(&phead);
	breaklist(&phead);
	pushback(&phead);
	pushback(&phead);
	printlist(phead);
	return 0;
}

下面由我来详细解说:

每个节点里都存储一个数据,并存储下一个节点的指针。

这样,在首节点就能通过直接找到下一个节点,在第二个节点就能通过直接找到第三个节点。以此类推,就可以顺藤摸瓜,通过首节点地址访问所有的节点。

创建首节点地址,并将其初始化为NULL。如果不初始化,那么phead就是野指针

void printlist(node* phead)//打印链表
{
	if (phead == NULL)
	{
		return;
	}
	while (phead)
	{
		printf("%d ", phead->data);
		phead = phead->next;
	}
	putchar('\n');
}

1.首节点指针为空,说明没有节点,直接返回。

2.首节点指针不为空,打印首节点数据,并让首节点指针指向下一个节点,直到phead为空,停止打印。

node* mall(node* phead)//创建节点
{
	node* newnode = (node*)malloc(sizeof(*phead));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->next = NULL;
	scanf("%d", &newnode->data);//在该节点装入数据
	return newnode;//返回节点地址
}

1.开辟空间,使newnode指向该空间,并判断是否开辟成功。

2.为防止野指针,让节点内的指针变量为NULL

3.输入数据

4.返回节点地址

void pushback(node** pphead)//后增
{
	assert(pphead);
	node* newnode = mall(*pphead);
	if (*pphead == NULL)//如果没有节点
	{
		*pphead = newnode;
		return;
	}
	node* ptail = *pphead;
	while (ptail->next)//至少有一个节点
	{
		ptail = ptail->next;
	}
	ptail->next = newnode;
}

1.pphead为NULL,说明链表不存在,需assert断言

2.创建节点,用newnode接受该节点地址

3.如果链表没有节点,直接让该节点成为首节点即可

4.如果至少有一个节点,找到最后一个节点,并让该节点的指针变量指向下一个节点

5.由于创建节点时,将其指针变量初始化为NULL,不需要后续处理。

void pushfront(node** pphead)//前增
{
	assert(pphead);
	node* newnode = mall(*pphead);
	newnode->next = *pphead;//将新节点与旧的首节点连接
	*pphead = newnode;//让新节点成为首节点
}

1.断言

2.创建节点,用newnode接受该节点地址

3.让newnode的指针变量指向首节点

4.让首节点指针指向刚创建的节点

void popback(node** pphead)//后删
{
	assert(pphead);
	if (*pphead = NULL)//没有节点,不用删
	{
		return;
	}
	node* ptail = *pphead;
	if (ptail->next == NULL)
	{
		free(ptail);
		*pphead = NULL;
		return;
	}
	node* prev = NULL;
	while (ptail->next)//多个节点
	{
		prev = ptail;
		ptail = ptail->next;
	}
	free(prev->next);
	prev->next = NULL;
	ptail = NULL;
}

1.断言

2.如果首节点指针为NULL,说明没有节点,直接返回

3.如果只有一个节点,直接释放

4.如果有多个节点,找到倒数第二个节点,释放最后一个节点的空间,并让倒数第二个节点的指针变量为空

5.ptail此时为野指针,要置为空指针

void popfront(node** pphead)//前删
{
	assert(pphead);
	if (*pphead = NULL)//没有节点,不用删
	{
		return;
	}
	node* ptail = *pphead;
	*pphead = ptail->next;
	free(ptail);
	ptail = NULL;
}

1.断言

2.如果首节点指针为NULL,说明没有节点,直接返回

3.让首节点指针指向第二个节点

4.将原来的首节点空间释放

5.ptail此时为野指针,要置为空指针

void pushpos(node** pphead, int pos)//指定位置插入
{
	assert(pphead);
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	if (pos <= 0 || pos > count + 1)//输入错误,直接return
		return;
	if (*pphead == NULL || pos == 1)//没有节点或需要首插时
	{
		pushfront(pphead);
		return;
	}
	ptail = *pphead;
	node* newnode = mall(*pphead);
	node* pafter = NULL;
	int i;
	for (i = 0; i < pos - 2; i++)//有节点时
	{
		ptail = ptail->next;//被插入位置左边的元素
		pafter = ptail->next;//被插入位置的元素
	}
	ptail->next = newnode;//插入新节点
	newnode->next = pafter;
}

1.断言

2.计算元素个数

3.如果要插入的位置不存在,直接返回

4.没有节点或需要首插时,直接用之前写好的函数就行了

5.至少有一个节点时,找到插入位置旁的两个节点,按顺序连接就好

6.该函数支持在最后面插入元素,比如链表只有5个节点,在第六个位置插入。

为什么可以呢?

在该情况下,ptail指向最后一个节点,pafter指向NULL,将元素嵌入,恰好组成了一条链表。

void poppos(node** pphead, int pos)//指定位置删除
{
	assert(pphead);
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	if (pos <= 0 || pos > count)//不符合要求,return
	{
		return;
	}
	if (pos == 1)//需要删除首节点时
	{
		*pphead = (*pphead)->next;
		return;
	}
	ptail = *pphead;
	node* pafter = NULL;
	int i;
	for (i = 0; i < pos - 2; i++)
	{
		ptail = ptail->next;//ptail是被删除节点的左节点
	}
	pafter = ptail->next;
	pafter = pafter->next;//pafter是被删除节点的右节点
	free(ptail->next);
	ptail->next = pafter;//让被删除节点的左节点连接被删除节点的右节点
}

1.断言

2.统计元素个数

3.判断需删除的节点是否存在

4.若需删除首节点时,可以采用代码中的方法,也可以直接使用写好的函数

5.若删除的不是首节点,找到需删除节点的左节点指针和右节点指针,连接左右节点

6.为什么代码能删除最后一个节点?

答:找到的右节点指针为NULL,将左节点的指针变量指向NULL,刚好删除了最后一个节点

7.将需删除的节点的空间释放

void cheak(node** pphead, int data)//找出对应数据的位置
{
	node* ptail = *pphead;
	int count = 0;
	while (ptail)//遍历链表,找元素
	{
		if (ptail->data == data)
			printf("%d ", count + 1);
		ptail = ptail->next;
		count++;
	}
	putchar('\n');
}

1.计算节点数目

2.遍历链表,一一比较,指出该节点是第几个节点

void breaklist(node** pphead)//销毁链表
{
	assert(pphead);
	if (*pphead == NULL)
	{
		return;
	}
	int count = 0;
	node* ptail = *pphead;
	while (ptail)//计算元素个数
	{
		ptail = ptail->next;
		count++;
	}
	int i;
	ptail = *pphead;
	while (count)//元素个数就是销毁次数
	{
		for (i = 0; i < count - 1; i++)//销毁非首节点
		{
			ptail = ptail->next;
		}
        free(ptail);
		ptail = NULL;
		count--;
	}
	*pphead = NULL;
}

1.断言

2.如果没有节点,直接返回

3.计算元素个数,计算销毁次数

4.外循环每循环一次,就销毁当前的尾节点

5.内循环循环完,找到最后一个节点,销毁

  • 33
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

INUYACHA

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

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

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

打赏作者

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

抵扣说明:

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

余额充值