【数据结构】手撕单链表


📙 作者简介 :RO-BERRY

📗 学习方向:致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识

📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持



单链表

  • 1.链表的概念及结构
    • [1.1 概念](#1.1 概念)
    • [1.2 结构](#1.2 结构)
  • 2.链表的分类
    • [2.1 单向或者双向](#2.1 单向或者双向)
    • [2.2 带头或者不带头](#2.2 带头或者不带头)
    • [2.3 循环或者非循环](#2.3 循环或者非循环)
    • [2.4 无头单向非循环链表:](#2.4 无头单向非循环链表:)
    • [2.5 带头双向循环链表:](#2.5 带头双向循环链表:)
  • 3.链表的实现
    • [3.1 头文件以及初始定义](#3.1 头文件以及初始定义)
    • [3.2 打印函数](#3.2 打印函数)
    • [3.3 创建新节点](#3.3 创建新节点)
    • [3.4 头插头删尾插尾删](#3.4 头插头删尾插尾删)
    • [3.5 查找](#3.5 查找)
    • [3.6 插入](#3.6 插入)
    • [3.7 删除](#3.7 删除)
  • 4.源代码

1.链表的概念及结构

1.1 概念

  • 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
  • 使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。

1.2 结构

链表从某种意义上来说就像一个火车一样,有火车头和火车上个各个节点车厢以及火车尾。

链表从某种意义上来说就像一个火车一样,有火车头和火车上个各个节点车厢以及火车尾。

2.链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

2.1 单向或者双向

2.2 带头或者不带头

2.3 循环或者非循环

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

2.4 无头单向非循环链表:

结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

2.5 带头双向循环链表:

结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3.链表的实现

我们需要多去尝试多文件实现程序,以便以后进行实际化,主要有一个头文件SList.h和两个源文件SList.c(实现链表主要功能)和test.c(执行测试函数)

3.1 头文件以及初始定义

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

在一开始主要包含了头文件、类型的定义以及最关键的链表结构体定义,我们定义SLTDataType的作用是,为了方便以后进行更改,我们在之后如果不存储int类型的数据的时候,我们可以直接在一开头对于SLTDataType指向的内容进行修改,即不需要在程序内部每一个int出现都需要进行修改。而结构体名称自然是自取之,因为我们写的是一个单链表,即内容只有一个存储内容data,还有指向下一个结点的结构体指针next。

3.2 打印函数

我们先在SList.h头文件里面进行定义

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

再在SList.c源文件里进行实现:

void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;     //将头结点的地址拷贝给cur
	while (cur)               //cur为NUIL终止
	{
		printf("%d->", cur->data);
		cur = cur->next;      //遍历下一个结点
	}
	printf("NULL\n");     
}

3.3 创建新节点

我们先在SList.h头文件里面进行定义

SLTNode* BuyListNode(SLTDataType x);      //创建新节点

再在SList.c源文件里进行实现:

SLTNode* BuyListNode(SLTDataType x)
{
	SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));  //动态开辟空间
	if (newnode == NULL)             //如果开辟失败
	{
		perror("malloc fail");
		exit(-1);
	} 
	newnode->data = x;
	newnode->next = NULL;       //只是进行创建,next在后面进行赋值,插入链表里,具体问题具体操作
}

3.4 头插头删尾插尾删

我们先在SList.h头文件里面进行定义

void SLTPushBack(SLTNode* phead, SLTDataType x);   //尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);  //头插
void SLTPopback(SLTNode** pphead);    //尾删
void SLTPopFront(SLTNode* pphead);    //头删

再在SList.c源文件里进行实现:

void SLTPushBack(SLTNode** pphead, SLTDataType x)  //这里用结构体二级指针改变一级指针
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)              //如果头链表为空
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* list = *pphead;
		while (list->next != NULL)    //遍历到最后一个元素
		{
			list = list->next;
		}
		list->next = newnode;    //改变的是结构体
	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x) //改的是结构体指针
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLTPopback(SLTNode** pphead)
{
	//1.为空
	//2.只有一个结点
	//3.一个以上结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tailPrev = tail;
			tail = tail->next;
		}
		tailPrev->next = NULL;
		free(tail);
		// tail = NULL;
	}
}
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}

3.5 查找

我们先在SList.h头文件里面进行定义

SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

再在SList.c源文件里进行实现:

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)   //查找X
{
	SLTNode* cur = phead;            //将头结点赋值给cur临时变量
	while (cur)
	{
		if (cur->data == x)           //遍历
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.6 插入

我们先在SList.h头文件里面进行定义

void SLTInsert(SLTNode* pphead, SLTNode* pos, SLTDataType x);   //在pos之前插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x); //在pos之后插入x

再在SList.c源文件里进行实现:

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)   //在pos之前插入x
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushBack(*pphead,x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyListNode(x);
		newnode->next = prev->next;
		prev -> next = newnode;
	}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x) //在pos之后插入x
{
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

3.7 删除

我们先在SList.h头文件里面进行定义

void SLTErase(SLTNode** pphead, SLTNode* pos); //删除pos位置
void SLTEraseAfter(SLTNode* pos);   //删除pos的后一个位置

再在SList.c源文件里进行实现:

void SLTErase(SLTNode** pphead, SLTNode* pos)//删除pos位置
{
	assert(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;
	}
}
void SLTEraseAfter(SLTNode* pos)   //删除pos的后一个位置
{
	assert(pos);
	//检查pos是否是尾结点
	assert(pos->next);
	SLTNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}

4.源代码

SList.h

#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* BuyListNode(SLTDataType x);      //创建新节点
void SLTPushBack(SLTNode* phead, SLTDataType x);   //尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);  //头插
void SLTPopback(SLTNode** pphead);    //尾删
void SLTPopFront(SLTNode* pphead);    //头删
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);   //查找X
void SLTInsert(SLTNode* pphead, SLTNode* pos, SLTDataType x);   //在pos之前插入x
void SLTInsertAfter(SLTNode* pos, SLTDataType x); //在pos之后插入x
void SLTErase(SLTNode** pphead, SLTNode* pos); //删除pos位置
void SLTEraseAfter(SLTNode* pos);   //删除pos的后一个位置

SList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
	SLTNode* cur = phead;     //将头结点的地址拷贝给cur
	while (cur)               //cur为NUIL终止
	{
		printf("%d->", cur->data);
		cur = cur->next;      //遍历下一个结点
	}
	printf("NULL\n");     
}
SLTNode* BuyListNode(SLTDataType x)
{
	SLTNode* newnode =(SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	} 
	newnode->data = x;
	newnode->next = NULL;
}
void SLTPushBack(SLTNode** pphead, SLTDataType x)  //这里用结构体二级指针改变一级指针
{
	SLTNode* newnode = BuyListNode(x);
	if (*pphead == NULL)              //如果头链表为空
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* list = *pphead;
		while (list->next != NULL)    //遍历到最后一个元素
		{
			list = list->next;
		}
		list->next = newnode;    //改变的是结构体
	}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x) //改的是结构体指针
{
	SLTNode* newnode = BuyListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void SLTPopback(SLTNode** pphead)
{
	//1.为空
	//2.只有一个结点
	//3.一个以上结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tailPrev = tail;
			tail = tail->next;
		}
		tailPrev->next = NULL;
		free(tail);
		// tail = NULL;
	}
}
void SLTPopFront(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = newhead;
}
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)   //查找X
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)   //在pos之前插入x
{
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushBack(*pphead,x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuyListNode(x);
		newnode->next = prev->next;
		prev -> next = newnode;
	}
}
void SLTInsertAfter(SLTNode* pos, SLTDataType x) //在pos之后插入x
{
	assert(pos);
	SLTNode* newnode = BuyListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SLTErase(SLTNode** pphead, SLTNode* pos)//删除pos位置
{
	assert(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;
	}
}
void SLTEraseAfter(SLTNode* pos)   //删除pos的后一个位置
{
	assert(pos);
	//检查pos是否是尾结点
	assert(pos->next);
	SLTNode* posNext = pos->next;
	pos->next = posNext->next;
	free(posNext);
	posNext = NULL;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
void TestSLTlist1()
{
	int n;
	printf("请输入链表的长度:\n");
	scanf("%d", &n);
	printf("请依次输入每个结点的值:\n");
	SLTNode* plist = NULL;          // 头结点
	for (int i = 0; i < n; i++)
	{
		int val;
		scanf("%d", &val);
		SLTNode* newnode = BuyListNode(val);
		//头插
		newnode->next = plist;
		plist = newnode;
	}
	SLTPushBack(&plist, 1000);
	SLTPrint(plist);
}
TestSLTlist2()  //尾插
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPushFront(&plist, 5);
	SLTPrint(plist);

}
TestSLTlist3()//尾删
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);

	SLTPopback(&plist);
	SLTPrint(plist);

	SLTPopback(&plist);
	SLTPrint(plist);

	SLTPopback(&plist);
	SLTPrint(plist);

	SLTPopback(&plist);
	SLTPrint(plist);


}
TestSLTlist4() //头删
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);
}
TestSLTlist5()  //查找测试
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos= SLTFind(plist, 10);
	if (pos)
	{
		pos->data *= 10;
	}
	SLTPrint(plist);
}
TestSLTlist6() //前插测试
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 1000);
	if (pos)
	{
		int x;
		scanf("%d", &x);
		SLTInsert(&plist, pos, x);
	}
	SLTPrint(plist);
}
TestSLTlist7() //后插测试
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 1000);
	if(pos)
	{
		int x;
		scanf("%d", &x);
		SLTInsertAfter(pos, x);
	}
	SLTPrint(plist);
}
TestSLTlist8() //删除测试
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 1000);
	if (pos)
		SLTErase(&plist, pos);
	SLTPrint(plist);
}
TestSLTlist9() //后一个节点删除测试
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 10);
	SLTPushBack(&plist, 1000);
	SLTPushBack(&plist, 10000);
	SLTPushBack(&plist, 100000);
	SLTPushBack(&plist, 1000000);
	SLTPrint(plist);
	SLTNode* pos = SLTFind(plist, 1000);
	if (pos)
		SLTEraseAfter(pos);
	SLTPrint(plist);
}

int main()
{
	TestSLTlist9(); 
	return 0;
}
相关推荐
是小Y啦6 分钟前
leetcode 106.从中序与后续遍历序列构造二叉树
数据结构·算法·leetcode
万河归海42824 分钟前
C语言——二分法搜索数组中特定元素并返回下标
c语言·开发语言·数据结构·经验分享·笔记·算法·visualstudio
秋夫人2 小时前
B+树(B+TREE)索引
数据结构·算法
代码雕刻家2 小时前
数据结构-3.1.栈的基本概念
c语言·开发语言·数据结构
AlexMercer10123 小时前
【C++】二、数据类型 (同C)
c语言·开发语言·数据结构·c++·笔记·算法
^^为欢几何^^5 小时前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
ahauedu5 小时前
案例分析-Stream List 中取出值最大的前 5 个和最小的 5 个值
数据结构·list
X同学的开始7 小时前
数据结构之二叉树遍历
数据结构
AIAdvocate10 小时前
Pandas_数据结构详解
数据结构·python·pandas
jiao0000111 小时前
数据结构——队列
c语言·数据结构·算法