数据结构---详解单链表

一、单链表的概念及性质

1、链表的概念

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

    我们看上图,一个链表就很像一节节车厢一样,和顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称这一节节车厢为"结点"。我们看上图会发现结点的组成主要有两个部分:当前结点要保存的数据和保存下一个结点的地址(指针变量)。图中指针变量plist保存的是第一个结点的地址,所以我们称plist此时指向第一个结点。如果我们想让plist指向第二个又或者其他结点,只需要修改plist保存的地址内容即可。链表中每个结点都是独立申请的,我们需要通过指针变量来保存下一个结点的位置才能从当前结点找到下一个结点。

2、性质

  • 链式结构在逻辑上是连续的,在物理结构上不一定连续。
  • 结点一般是从堆上申请的。
  • 从堆上申请来的空间,是按照一定策略分配出来的,每次申请空间可能连续也可能不连续。

二、实现单项链表

1、准备工作

  • 创建三个文件(如下图所示)
  1. 头文件SList.h:用来声明函数,定义链表
    2.源文件SList.c:用来实现函数
    3.测试文件test.c:测试工作(写完一个接口函数测试一下,避免后续的麻烦)

2、实现链表的基本接口

(1)创建一个链表

  • 在头文件中创建链表结点结构,和顺序表不用的是顺序表是创建一个数组arr和有效数据和容量大小,而链表这是储存一个数据和指向下一个数据的地址。
c 复制代码
typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

(2)链表的打印

c 复制代码
//打印
void SLTPrint(SLTNode *phead) 
{
	//重新创建一个pcur指针是为了避免指针指向的改变
	//导致无法重新找到首节点
	SLTNode* pcur = phead;
	//链表遍历和数组有很大区别
	while (pcur != NULL)
	{
		printf("%d -> ", pcur->data);
		//打印完后要指向下一个地址
		pcur = pcur->next;
	}
	printf("NULL\n");
}

这里有一个点需要注意:pcur = pcur->next这里是将pcur的下一个值赋值给pcur这样就能打印出链表的下一个结点了。

(3)尾插

  • 在尾插之前还要说一个点,就是要创建结点的代码如下所示
    创建一个结点
  • 顺序表是扩容,这里是创建结点
c 复制代码
//创建一个节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	//给节点开辟一个空间
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//如果不是NULL,就把x放在新的节点上
	node->data = x;
	node->next = NULL;

	return node;
}

这下面是尾插的代码:

c 复制代码
//尾插
void SLTPushBack(SLTNode **pphead, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	//判断是不是空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//如果链表非空,找尾节点
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL) //可以写成ptail->next
		{
			//遍历找到尾节点
			ptail = ptail->next;
		}
		//找到后跳出循环,将新创建的节点赋值过去
		ptail->next = newnode;
	}
}

(4)头插

c 复制代码
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

头插就相对简单,直接创建新的结点然后插入第一个结点,再把创建的结点作为头节点

(5)尾删

c 复制代码
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//分两种情况,只有一个节点的时候
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//第一个是传参的时候不能为空
	   //第二个人是链表不能为空
		assert(pphead && *pphead);
		//创建两个指针遍历
		SLTNode* ptail = *pphead;
		SLTNode* prec = NULL;
		while (ptail->next)
		{
			prec = ptail;
			ptail = ptail->next;
		}
		prec->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

(6)头删

c 复制代码
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead && *pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

这里注意释放掉删除的结点,记得将下一个结点置为头节点

(7)查找

c 复制代码
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没找到
	return NULL;
}

循环遍历链表,找到就返回,找不到就返回NULL

(8) 在指定之前位置插入数据

c 复制代码
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);

	if (pos == *pphead)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else {
		//创建一个节点
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			//next不是pos的时候继续往下遍历
			prev = prev->next;
		}
		//prev->next是pos的时候
		newnode->next = pos;
		prev->next = newnode;
	}
}

(9) 在指定位置之后插入数据

c 复制代码
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

(10)删除pos结点

c 复制代码
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);

	if (pos == *pphead)
	{
		//要删除的节点就是头节点时
		SLTPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

(11)删除pos之后的结点

c 复制代码
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

(12)链表的销毁

  • 有借有还,再借不难
c 复制代码
//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

三、链表的代码总览

1、SList.h

c 复制代码
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//打印
void SLTPrint(SLTNode *phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead,SLTDataType x);
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

2、SList.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

//创建一个节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	//给节点开辟一个空间
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	//如果不是NULL,就把x放在新的节点上
	node->data = x;
	node->next = NULL;

	return node;
}
//打印
void SLTPrint(SLTNode *phead) 
{
	//重新创建一个pcur指针是为了避免指针指向的改变
	//导致无法重新找到首节点
	SLTNode* pcur = phead;
	//链表遍历和数组有很大区别
	while (pcur != NULL)
	{
		printf("%d -> ", pcur->data);
		//打印完后要指向下一个地址
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//尾插
void SLTPushBack(SLTNode **pphead, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	//判断是不是空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//如果链表非空,找尾节点
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL) //可以写成ptail->next
		{
			//遍历找到尾节点
			ptail = ptail->next;
		}
		//找到后跳出循环,将新创建的节点赋值过去
		ptail->next = newnode;
	}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//分两种情况,只有一个节点的时候
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		//第一个是传参的时候不能为空
	   //第二个人是链表不能为空
		assert(pphead && *pphead);
		//创建两个指针遍历
		SLTNode* ptail = *pphead;
		SLTNode* prec = NULL;
		while (ptail->next)
		{
			prec = ptail;
			ptail = ptail->next;
		}
		prec->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead && *pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没找到
	return NULL;
}
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);

	if (pos == *pphead)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else {
		//创建一个节点
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			//next不是pos的时候继续往下遍历
			prev = prev->next;
		}
		//prev->next是pos的时候
		newnode->next = pos;
		prev->next = newnode;
	}
}
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);

	if (pos == *pphead)
	{
		//要删除的节点就是头节点时
		SLTPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3、test.c

c 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void test1()
{
	//手动构建一个链表
	//创建空间
	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;
	SLTPrint(plist);
}
//尾插测试
void test2()
{
	SLTNode* plist = NULL;
	//这里是取指针的地址,所以要二级指针来接收
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	//打印
	SLTPrint(plist);
}
//尾删测试
void test3()
{
	//先插入数据
	SLTNode* plist = NULL;
	
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	//打印
	SLTPrint(&plist);
	//尾删
	SLTPopBack(&plist);
	//打印
	SLTPrint(plist);
}
//头插测试
void test4()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//打印
	SLTPrint(plist);
}
//头删测试
void test5()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//打印
	SLTPrint(plist);
	//头删
	SLTPopFront(&plist);
	//打印
	SLTPrint(plist);
}

//查找测试
int test6()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//查找
	SLTNode* ret = SLTFind(plist, 6);
	if (ret == NULL)
	{
		printf("未找到!");
	}
	else {
		printf("找到了!");
	}
}

//在指定位置之前插入数据测试
void test7()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//查找
	SLTNode* find = SLTFind(plist, 5);
	SLInsert(&plist, find, 99);
	//打印
	SLTPrint(plist);
}
//在指定位置之后插入数据测试
void test8()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//查找
	SLTNode* find = SLTFind(plist, 1);
	SLInsertAfter(find, 88);
	//打印
	SLTPrint(plist);
}
//删除pos结点测试
void test9()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	//查找
	SLTNode* find = SLTFind(plist, 5);
	SLTErase(&plist, find);
	//打印
	SLTPrint(plist);
}
//删除pos之后的结点
void test10()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	//查找
	SLTNode* find = SLTFind(plist, 1);
	SLTEraseAfter(find);
	//打印
	SLTPrint(plist);
}
//销毁链表测试
void test11()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	//打印
	SLTPrint(plist);
	//销毁
	SListDestroy(&plist);
	//打印
	SLTPrint(plist);
}

int main()
{
	//test1();
	//test2();
	//test3();
	//test4();
	//test5();
	//test6();
	//test7();
	//test8();
	//test9();
	//test10();
	test11();

	return 0;
}
相关推荐
韭菜盖饭3 小时前
LeetCode每日一题3261---统计满足 K 约束的子字符串数量 II
数据结构·算法·leetcode
♡喜欢做梦3 小时前
【数据结构】ArrayList与LinkedList详解!!!——Java
java·开发语言·数据结构·链表
好心的小明4 小时前
【深圳大学】数据结构A+攻略(计软版)
数据结构
熬夜学编程的小王4 小时前
【初阶数据结构篇】插入、希尔、选择、堆排序
数据结构·c++·插入排序·选择排序·希尔排序
三小尛4 小时前
快速排序(C语言)
数据结构·算法·排序算法
椅子哥4 小时前
数据结构--排序算法
java·数据结构·算法·排序算法
DDDiccc4 小时前
JAVA学习日记(十五) 数据结构
数据结构·学习
Nydia.J4 小时前
【学习笔记】数据结构(七)
数据结构·考研
2301_799084674 小时前
超全排序C语言实现
c语言·数据结构·算法·排序算法
苏唱!!!!4 小时前
二叉树-堆
数据结构