C语言数据结构超详细讲解单向链表

目录
  • 1.链表概况
    • 1.1 链表的概念及结构
    • 1.2 链表的分类
  • 2. 单向链表的实现
    • 2.1 SList.h(头文件的汇总,函数的声明)
    • 2.2 SList.c(函数的具体实现逻辑)
      • 2.2.1 打印链表
      • 2.2.2 搞出一个新节点(为其他函数服务)
      • 2.2.3 链表尾插
      • 2.2.4 链表头插
      • 2.2.5 链表尾删
      • 2.2.6 链表头删
      • 2.2.7 查找节点
      • 2.2.8 在pos位置之前插入
      • 2.2.9 在pos位置之后插入
      • 2.2.10 删除pos位置
      • 2.2.11 删除pos之后位置
      • 2.2.12 链表销毁

1.链表概况

1.1 链表的概念及结构

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

1.2 链表的分类

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

单向或者双向

带头或者不带头

循环或者非循环

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

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了

2. 单向链表的实现

单向链表结构

2.1 SList.h(头文件的汇总,函数的声明)

#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDataType;

typedef struct SListNode
{
	int data;//数据
	struct SListNode* next;//下一个节点的地址
}SListNode;

void SListPrint(SListNode* phead);//打印

SListNode* BuySListNode(SLTDataType x);//搞出一个新节点

void SListPushBack(SListNode** pphead, SLTDataType x);//尾插
void SListPushFront(SListNode** pphead, SLTDataType x);//头插

void SListPopBack(SListNode** pphead);//尾删
void SListPopFront(SListNode** pphead);//头删

SListNode* SListFind(SListNode* phead, SLTDataType x);//查找

void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x);//在pos位置之前插入
void SListInsertAfter(SListNode* pos, SLTDataType x);//在pos位置之后插入

void SListErase(SListNode** pphead, SListNode* pos);//删除pos位置
void SListEraseAfter(SListNode* pos);//删除pos之后位置

void SListDestroy(SListNode** pphead);//销毁

2.2 SList.c(函数的具体实现逻辑)

2.2.1 打印链表

//打印
void SListPrint(SListNode* phead)
{
	//assert(phead);//没必要断言,空链表也能打印
	SListNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//test.c
void TestSList1()
{
	SListNode* slist;//结构体指针,用于存节点的地址
	SListNode* n1 = (SListNode *)malloc(sizeof(SListNode));
	SListNode* n2 = (SListNode*)malloc(sizeof(SListNode));
	SListNode* n3 = (SListNode*)malloc(sizeof(SListNode));
	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n1->next = n2;
	n2->next = n3;
	n3->next = NULL;
	slist = n1;

	SListPrint(slist);
}

2.2.2 搞出一个新节点(为其他函数服务)

//搞出一个新节点
SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}

	return newnode;
}

2.2.3 链表尾插

//尾插
void SListPushBack(SListNode** pphead,SLTDataType x)//会改变实参slist,要传二级指针
{
	assert(pphead);//就算链表是空,它的二级指针也不为空
	SListNode* newnode = BuySListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//遍历找尾
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
void TestSList2()
{
	SListNode* slist = NULL;
	for (int i = 0; i < 6; i++)
	{
		SListPushBack(&slist,i);
	}
	SListPrint(slist);
}

2.2.4 链表头插

//头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);//就算链表是空,它的二级指针也不为空

	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}
void TestSList2()
{
	SListNode* slist = NULL;
	for (int i = 0; i < 6; i++)
	{
		SListPushFront(&slist,i);
	}
	SListPrint(slist);
}

2.2.5 链表尾删

方法1(用两个指针,分别找最后一个和倒数第二个):

方法2(直接找倒数第二个):

//尾删
void SListPopBack(SListNode** pphead)//如果只有一个节点但还要尾删,就会改变实参slist,建议传二级指针
{
	assert(pphead);//就算链表是空,它的二级指针也不为空
	//三种情况:
	//1.空
	//2.一个节点
	//3.多个节点
	if (*pphead == NULL)
	{
		return;
	}

	else if ((*pphead)->next == NULL)//优先级,记得加括号
	{
		free(*pphead);
		*pphead = NULL;
	}

	//第一种写法(用两个指针,分别找最后一个和倒数第二个)
	else
	{
		SListNode* prev = NULL;//找倒数第二个元素,用于将它指向NULL
		SListNode* tail = *pphead;//找尾
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;//习惯性free掉就将其置空

		prev->next = NULL;
	}

	//方法二(直接找倒数第二个)
	//else
	//{
		//SListNode* tail = *pphead;//找尾
		//while (tail->next->next != NULL)
		//{
		//	tail = tail->next;
		//}
		//free(tail->next);
		//tail->next = NULL;
	//}
}

2.2.6 链表头删

//头删
void SListPopFront(SListNode** pphead)
{
	assert(pphead);
	//1.空
	//2.非空
	if (*pphead == NULL)
	{
		return;
	}
	else
	{
		SListNode* next = (*pphead)->next;
		free(*pphead);
		*pphead = next;
	}
}

2.2.7 查找节点

//查找
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (phead != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2.2.8 在pos位置之前插入

//在pos位置之前插入(一般跟find配合使用)
void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);//间接检测链表是否为空,因为空链表找不到pos
	//1.pos是第一个节点(头插)
	//2.pos不是第一个节点
	if (pos == *pphead)
	{
		SListPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SListNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

2.2.9 在pos位置之后插入

方法1:两个指针,先连接pos和newnode还是先连接newnode和next都行

方法2:只有一个指针,一定要先通过pos连接newnode和下一个,再连接pos和newnode,否则会找不到下一个的地址。

//在pos位置之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	SListNode* newnode = BuySListNode(x);
	SListNode* next = pos->next;
	newnode->next = next;//顺序无所谓
	pos->next = newnode;
	//或者不定义next
	//newnode->next = pos->next;
	//pos->next = newnode;//顺序要定好,否则会找不到
}

2.2.10 删除pos位置

//删除pos位置
void SListErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;//好习惯
	}
}

2.2.11 删除pos之后位置

//删除pos之后位置
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	SListNode* next = pos->next;
	if (next)//防止pos是最后一个元素
	{
		pos->next = next->next;
		free(next);
		next = NULL;
	}
}

2.2.12 链表销毁

一个个找,一个个销毁,最终将slist置空。

//销毁
void SListDestroy(SListNode** pphead)
{
	assert(pphead);

	SListNode* cur = *pphead;
	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

总结:单链表结构,适合头插头删。尾部或者中间某个位置插入删除不适合。如果要使用链表单独存储数据,那我们之后会学的双向链表更合适。单链表学习的意义:

  • 单链表会作为我们之后学习复杂数据结构的子结构(图的邻接表、哈希桶)
  • 单链表会出很多经典的练习题。

链表系列有很多重点内容,我会花不少时间来跟大家讲解链表,相信大家跟着练习的话编程严谨性会大大提升,加油哦!

到此这篇关于C语言数据结构超详细讲解单向链表的文章就介绍到这了,更多相关C语言 单向链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言之单向链表详解及实例代码

    1,单向链简洁. 单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始:链表是使用指针进行构造的列表:又称为结点列表,因为链表是由一个个结点组装起来的:其中每个结点都有指针成员变量指列表中的下一个结点:列表是由结点构成,由head指针指向第一个成为表头的结点而终止于最后一个指向nuLL的指针: 2,例子要求: 根据示例代码中的例子,完成单向链表(single linked list)中的以字符串为数据的链表的插入.删除以及查找,并支持单向链表的反转

  • C语言解字符串逆序和单向链表逆序问题的代码示例

    字符串逆序 上次面试碰到一个单向链表逆序的题目,幸好对字符串逆序比较熟悉,类比做出来了.字符串逆序比较简单,直接上代码: void stringReverse(char* p1,char* p2) { if(p1==p2)return; //swap the value of p1 ,p2 *p1=(*p1)+(*p2); *p2=(*p1)-(*p2); *p1=(*p1)-(*p2); if(p1==p2-1)return; else stringReverse(++p1,--p2); }

  • C语言实现无头单向链表的示例代码

    目录 一.易错的接口实现 1.1 新节点开辟函数 1.2 尾插 1.3 尾删 二.常见简单接口 2.1 打印链表 2.2 节点计数器 2.3 判断是否为空链表 2.4 通过值查找节点 2.5 头插 2.6 头删 2.7 在任意节点后插入节点 2.8 在任意节点后删除节点 2.9 销毁链表 三.头文件相关内容 3.1 引用的库函数 3.2 结构体声明 一.易错的接口实现 1.1 新节点开辟函数 由于创建一个新节点是频繁的操作,所以封装为一个接口最佳. 链表节点的属性有:(1)数值.(2)指向下一个

  • C语言 单向链表的增删查改快速掌握

    目录 前言 一.创建 二.单向链表的函数声明 三.函数实现 1.创建节点 2.尾插节点 3.头插 4.尾删 5.头删 6.查找节点 7.修改 总结 前言 链表是线性表的链式存储结构,它可以以O(1)的时间复杂度进行插入或者删除,同时由于是链式结构相比顺序表而言,不会存在空间浪费的情况.而链表又分为带头单向链表,不带头单向链表,带头循环链表,不带头循环链表,带头双向循环链表,不带头双向循环链表,带头双向链表,不带头双向链表,总共有八种,其中结构最简单的是不带头单向链表,也是实现起来最容易出错的.并

  • C语言单向链表的表示与实现实例详解

    1.概述: C语言中的单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始. 链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域.这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值. 如下图所示: 一个单向链表包含两个值: 当前节点的值和一个指向下一个节点的链接 一个单向链表的节点被分成两个部分.第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址.单向链表只可向一个方向遍历. 链表最基本的结构是在每个节点

  • C语言数据结构之单向链表详解分析

    链表的概念:链表是一种动态存储分布的数据结构,由若干个同一结构类型的结点依次串连而成. 链表分为单向链表和双向链表. 链表变量一般用指针head表示,用来存放链表首结点的地址. 每个结点由数据部分和下一个结点的地址部分组成,即每个结点都指向下一个结点.最后一个结点称为表尾,其下一个结点的地址部分的值为NULL(表示为空地址). 特别注意:链表中的各个结点在内存中是可以不连续存放的,具体存放位置由系统分配. 例如:int *ptr ; 因此不可以用ptr++的方式来寻找下一个结点. 使用链表的优点

  • C语言数据结构超详细讲解单向链表

    目录 1.链表概况 1.1 链表的概念及结构 1.2 链表的分类 2. 单向链表的实现 2.1 SList.h(头文件的汇总,函数的声明) 2.2 SList.c(函数的具体实现逻辑) 2.2.1 打印链表 2.2.2 搞出一个新节点(为其他函数服务) 2.2.3 链表尾插 2.2.4 链表头插 2.2.5 链表尾删 2.2.6 链表头删 2.2.7 查找节点 2.2.8 在pos位置之前插入 2.2.9 在pos位置之后插入 2.2.10 删除pos位置 2.2.11 删除pos之后位置 2.

  • C++ 数据结构超详细讲解单链表

    目录 前言 一.链表是什么 链表的分类 二.链表的实现 总结 (❁´◡`❁) 单链表 前言 上篇顺序表结尾了解了顺序表的诸多缺点,链表的特性很好的解决了这些问题,本期我们来认识单链表. 一.链表是什么 链表是一种物理存储结构上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接依次实现的. 由图,链式结构在逻辑上是连续的,但是物理上不一定连续 显示中结点一般是从堆上申请出来的 从堆上申请的空间,是按照一定的策略划分的,两次申请的空间,可能连续,可能不连续,见顺序表 链表的分类 链表

  • C++ 数据结构超详细讲解顺序表

    目录 前言 一.顺序表是什么 概念及结构 二.顺序表的实现 顺序表的缺点 几道练手题 总结 (●’◡’●) 前言 线性表是n个具有相同特性的数据元素的有限序列.线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表.链表.栈.队列.字符串. 线性表在逻辑上是线性结构,也就是说连续的一条直线,但是在物理结构并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储. 本章我们来深度初体验顺序表 一.顺序表是什么 概念及结构 顺序表是一段物理地址连续的存储单元依次存储数据元素的线性

  • C++超详细讲解单链表的实现

    目录 单链表的实现(从入门到熟练) 概念和结构 链表的实现 增删查改接口 节点结构体创建 节点开辟 数据打印 链表尾插数据 头删 链表数据查找 链表pos位置前插数据 链表pos位置后插数据 链表pos位置数据删除 链表节点释放 链表与顺序表区别 链表的优缺点 顺序表的优缺点 单链表的实现(从入门到熟练) 概念和结构 概念:链表是一种物理存储结构上非连续.非顺序的存储结构 数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 图示: 注意: 链表结构在逻辑上为连续的,但是物理上(内存中)不一定连

  • C语言指针超详细讲解上篇

    目录 前言 1.指针是什么 1.1 指针变量 1.2 指针是内存中一个最小单元的编号 2.指针和指针类型 2.1 指针±类型 2.2 指针的解引用 2.2.1 int* 类型的解引用 2.2.2 char* 类型的解引用 3.野指针 3.1 野指针成因 3.1.1 指针未初始化 3.1.2 指针越界访问 3.1.3 指针指向的空间释放 3.2 如何规避野指针 总结 前言 本文开始指针相关内容的学习,主要内容包括: 指针是什么 指针和指针类型 野指针 指针运算 指针和数组 二级指针 指针数组 1.

  • C语言操作符超详细讲解下篇

    目录 前言 赋值操作符 单目操作符 单目操作符介绍 sizeof 和 数组 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用与函数调用和结构成员 [ ] 下标引用操作符 ( ) 函数调用操作符 访问一个结构的成员 表达式求值 隐式类型转换-整形提升 算术转换 操作符的属性 总结 前言 本文接着学习操作符的内容. 赋值操作符 赋值操作符就是能够重新赋值 int weight = 120;//体重 weight = 89;//不满意就赋值 double salary = 10000.0; s

  • C语言操作符超详细讲解上篇

    目录 前言 1.操作符的分类 2.算术操作符 3.移位操作符 3.1 左移操作符 3.1.1 正数左移1位 3.1.2 负数左移1位 3.2 右移操作符 3.2.1 正数右移1位 3.2.2 负数右移1位 3.3 移位操作符说明 4.位操作符 4.1 练习 1 4.2 练习 2 总结 前言 操作符主要内容包括:各种操作符的介绍,用表达式求值. 1.操作符的分类 算术操作符 移位操作符 位操作符 赋值操作符 单目操作符 关系操作符 逻辑操作符 条件操作符 逗号表达式 下标引用.函数调用和结构成员

  • C语言指针超详细讲解下篇

    目录 前言 指针运算 指针±整数 4.1 指针±整数 4.2 指针-指针 4.3 指针的关系运算 5.指针和数组 6.二级指针 7.指针数组 7.1 举例 1 7.2 举例 2 总结 前言 本文接着上一篇内容,继续学习指针相关知识点. 指针运算 指针±整数 指针-指针 指针的关系运算 4.1 指针±整数 #define VALUE 5 int main() { float values[VALUE]; float *vp; //指针+-指针,关系运算 for (vp = &values[0];

  • C语言函数超详细讲解下篇

    目录 前言 函数的声明和定义 函数声明 函数定义 举例 简单的求和函数 把加法单独改写成函数 添加函数声明 带头文件和函数声明 静态库(.lib)的生成 静态库文件的使用方法 函数递归 什么是递归? 递归的两个必要条件 练习1 一般方法 递归的方法 练习2 一般方法 递归方法 练习3 一般方法 递归方法 练习4 一般方法 递归方法 递归与迭代 递归隐藏的问题 如何改进 选递归还是迭代 总结 前言 紧接上文,继续学习函数相关内容. 函数的声明和定义 函数声明 告诉编译器有一个函数叫什么,参数是什么

  • C语言数组超详细讲解下篇扫雷

    目录 前言 1.扫雷是什么? 2.程序框架 2.1 主函数 2.2 函数menu 2.3 函数game 2.3.1 函数init_board 2.3.2 函数show_board 2.3.3 函数set_mine 2.3.4 函数find_mine 2.3.5 函数get_mine_count 3.头文件.h 4.游戏试玩 总结 前言 本文接着复习前面所学知识,以扫雷游戏为例. 1.扫雷是什么? 百度百科:<扫雷>是一款大众类的益智小游戏,于1992年发行.游戏目标是在最短的时间内根据点击格子

随机推荐