单链表

Posted 「已注销」

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单链表相关的知识,希望对你有一定的参考价值。

文章目录


如果觉得文章不错, 点个关注你就是老粉了!!

单链表

一.什么是单链表

单链表, 双链表, 静态链表, 循环链表…

链表: 链式存储结构, 用于存储逻辑关系为 “一对一” 的数据

与顺序表不同在于: 链表的物理地址是不一定连续的

链表的节点

节点分为: 头结点,尾结点,普通节点,首元节点(详细见图1)

一般由结构体组成一个节点(成员: 数据 , 结构体指针)

节点类型一般都是自定义的

头节点: 第一个节点

尾结点: 最后一个节点

首元节点: 第一个真正存储数据的节点(有时第一个节点并不存储数据,仅仅作为头来使用)

头指针,尾指针

头指针指向头节点,尾指针指向尾节点_头指针是找到链表的关键(详细见图1)

图1:

二 单链表的基本操作(C语言代码实现)

一. 创建一个单链表

以图1中的情况2为例编写代码

思路:

首先, 定义一个结构体用来存储节点的相关信息(数据域,指针域)

然后,在创建一个头节点(不存任何数据_哑节点),之后在头节点后面不断添加节点

开始代码实现:

// 方便灵活改变数据类型
typedef int DateType;
// 自定义一个节点类型
struct node

	DateType num;           // 数据域
    struct node* pnext;     // 指针域
;
// 给结构体类型取别名
typedef struct node Node;
// **创建一个链表并且给初始值
// 参数: 长度
// 返回值: 头指针
Node* CreateList(int length)

	// 判断长度
	if (length <= 0)
	
		printf("Length Error!\\n");
		return NULL;
	

	// 1 创建头尾两个指针
	Node* phead, * prear;
	phead = prear = NULL; // 初始化防止野指针

	// 2 申请内存, 头节点(哑节点)
	phead = (Node*)malloc(sizeof(Node));

	// 3 处理异常情况
	if (phead == NULL) 
		perror("malloc failed!\\n");
		exit(EXIT_FAILURE);
	
	/*  perror函数和EXIT_FAILURE解释(内容来自C库函数,stdlib.h)
	* 
	*   void perror(const char* _ErrMsg): 用于弹出异常(输出错误描述)
	*   exit(0);   正常执行   结束程序
	*   exit(1);   非正常执行 结束程序
	*   #define EXIT_SUCCESS 0;
	*   #define EXIT_FAILURE 1;
	* 
	*/

	// 4 初始化头节点
	phead->pnext = NULL;   // 没有其他节点暂时指向NULL
	phead->num = 0;        // 按图1情况二所示,头节点不存放数据,随便给个初值

	// 5 初始化尾节点
	prear = phead;         // 初始只有一个节点,头节点也是尾节点

	// 6 通过循环,添加节点
	Node* pNewNode = NULL;
	for (size_t i = 0; i < length; i++)
	
		// 6.1 申请一个节点,检测是否申请成功,给值
		pNewNode = (Node*)malloc(sizeof(Node));  // malloc如果申请内存失败返回NULL
		if (NULL == pNewNode) 
			perror("malloc failed!\\n");
			exit(EXIT_FAILURE);
		
		int n = 0;
		printf("输入节点数据: ");
		scanf("%d", &n);
		pNewNode->num = n;
		pNewNode->pnext = NULL;

		// 6.2 将节点添加到链表(尾插法)
		prear->pnext = pNewNode; //尾指针指向新节点
		prear = pNewNode;        //更新尾节点
	
	return phead;

二.遍历一个单链表

// **遍历一个单链表
// 参数: 链表头指针
// 返回值: 无
void TraverseList(Node* const pList)   // 遍历链表不希望被改值加上一个const
    // pList->pnext : 避开哑节点
	Node* ptemp = pList->pnext;
	if (NULL == ptemp) 
		printf("链表为空!\\n");
	
	while (ptemp) 
		printf("%d ", ptemp->num);     //输出
		ptemp = ptemp->pnext;          //移位
	
    printf("\\n");

测试:

Node* p = CreateList(5);
TraverseList(p);

三.插入一个元素

思路:先插后拆(如果先拆会找不到节点位置)

// **插入一个节点
// 参数:头指针,位置,值
// 返回值:头指针
Node* InsertElement(Node* pList, Node* Position, int val) 
    //创建
	Node* ptemp = (Node*)malloc(sizeof(Node));    //申请内存
    //判断异常
	if (NULL == ptemp) 
		perror("malloc failed!\\n");
		exit(EXIT_FAILURE);
	
    //初始化并插入
	ptemp->num = val;
	ptemp->pnext = Position->pnext;
	Position->pnext = ptemp;
	return pList;

测试:(插入并输出)

Node* p = CreateList(5);                        //创建链表
TraverseList(p);                                //遍历输出
TraverseList(InsertElement(p, p->pnext, 666));  //插入节点+遍历输出  (首元节点后插入:666)
// InsertElement函数返回头指针,所以可以采用链式操作.

四.清空链表

// **清空链表
// 参数:头指针
// 返回值:头指针
Node* DeleteList(Node* pList)

    // 保存头节点(由于头节点不保存数据,从首元节点依次释放)
	Node* ptemp = NULL, * pos = NULL;
	pos = pList->pnext;
	pList->pnext = NULL; // 清空pList(只剩头节点(哑节点))
    // 逐一释放内存
	while (pos)            // 原理与上面保存头节点相同
		ptemp = pos->pnext; // ptemp保存了pos(当前要释放节点)后面的全部节点
		free(pos);          // 释放pos
		pos = ptemp;        // 把ptemp保存的节点还给pos,并继续回到while循环判断直到pos==NULL结束
	
	return pList;           // 返回指针

测试:

Node* p = CreateList(5);      //创建链表
TraverseList(p);              //遍历输出
TraverseList(DeleteList(p));  //清空链表+遍历输出

五.删除节点

按值删除单个节点


一.查找

1.找到删除节点的上一个节点_否则不方便解链(所以遍历搜索时通过->next找到删除元素)

已找到!! TempList指向上一节点


二.连接

三.删除释放

// **按值删除单个节点
// 参数:头指针,值
// 返回值:头指针
Node* DeleteElement(Node* pList, int val) 
	Node* TempList = pList;
	Node* TempNode = NULL;
    //遍历查找
	while (TempList->pnext && TempList->pnext->num != val) 
		TempList = TempList->pnext;
	
    //判断是否找到
	if (!TempList->pnext) 
		printf("未找到删除元素,%d不存在!!\\n", val);
	
	else 
		TempNode = TempList->pnext;          //对照图示学习
		TempList->pnext = TempNode->pnext;
		free(TempNode);
		// TempList->pnext = TempNode->pnext->pnext; 这样的操作是不行的,堆区没有释放内存;
	
	return pList;

测试:

Node* p = CreateList(5);            //创建链表
TraverseList(p);                    //遍历输出
DeleteElement(p, 9);                //删除节点        (删除第一个元素为9的节点,找不到)
TraverseList(DeleteElement(p, 2));  //删除节点+遍历数组 (删除第一个元素为2的节点)

六.查找节点

// **查找一个节点(按数值)
// 参数:头指针,数值
// 返回值: 目标节点指针
Node* FindElement(Node* pList, int val) 
	Node* ptr = pList->pnext;   //指向首元节点用于遍历

	// 1 判断
	if (NULL == ptr) 
		printf("链表为空!\\n");
		return NULL;
	

	// 2 查找
	while (ptr != NULL && ptr->num != val) 
		ptr = ptr->pnext;  //移位
	

	// 3 反馈
	if (ptr != NULL) 
		printf("找到 %d 了!\\n", ptr->num);
	
	else 
		printf("未找到 %d !\\n",val);
	
	return ptr;

测试:

Node* p = CreateList(5);                                  //创建链表
TraverseList(p);                                          //遍历输出
TraverseList(InsertElement(p, FindElement(p, 4), 666));   //查找节点+插入节点+遍历输出
// 查找4,并在4后面插入666,遍历输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wGnkWRQ9-1614408675456)(C:\\Users\\admin\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210227144011066.png)]

完整代码

main.c

#include<stdio.h>
#include"myList.h"

int main() 
	
	//创建
	Node* p = CreateList(5);
	//遍历
	TraverseList(p);
	//插入
	InsertElement(p, p->pnext, 666);
	//遍历
	TraverseList(p);
	//删一个
	DeleteElement(p, 666);
	//遍历
	TraverseList(p);
	//查找4并在其后插入666并遍历
	TraverseList(InsertElement(p, FindElement(p, 4), 666));
	//清空
	DeleteList(p);
	//遍历
	TraverseList(p);


myList.h

#ifndef _MYLIST_ // 添加判断_宏定义 避免重复定义
#define _MYLIST_

// 方便灵活改变数据类型
typedef int DateType;
// 节点结构体
struct node

	DateType num;           // 数据域
    struct node* pnext;     // 指针域
;
// 给结构体类型取别名
typedef struct node Node;

// **创建一个链表并且给初始值
// 参数: 长度
// 返回值: 头指针
Node* CreateList(int length);

// **遍历一个单链表
// 参数: 链表头指针
// 返回值: 无
void TraverseList(Node* const pList);

// **插入一个节点
// 参数:头指针,位置,值
// 返回值:头指针
Node* InsertElement(Node* pList, Node* Position, int val);

// **清空链表
// 参数:头指针
// 返回值:头指针
Node* DeleteList(Node* pList);

// **删除单个元素(按数值)
// 参数:头指针,值
// 返回值:头指针
Node* DeleteElement(Node* pList, int val);

// **查找一个节点(按数值)
// 参数:头指针,数值
// 返回值: 目标节点指针
Node* FindElement(Node* pList, int val);

#endif

myList.c

#define _CRT_SECURE_NO_WARNINGS 1   // 解决VS2019 scanf不安全报错问题
#include "myList.h"
#include<stdio.h>
#include<stdlib.h>


// **创建一个链表并且给初始值
// 参数: 长度
// 返回值: 头指针
Node* CreateList(int length)

	// 判断长度
	if (length <= 0)
	
		printf("Length Error!\\n");
		return NULL;
	

	// 1 创建头尾两个指针
	Node* phead, * prear;
	phead = prear = NULL;

	// 2 申请内存, 头节点(哑节点)
	phead = (Node*)malloc(sizeof(Node));

	// 3 处理异常情况
	if (phead == NULL) 
		perror("malloc failed!\\n");
		exit(EXIT_FAILURE);
	
	/*  perror函数和EXIT_FAILURE解释(内容来自C库函数,stdlib.h)
	* 
	*   void perror(const char* _ErrMsg): 用于弹出异常(输出错误描述)
	*   exit(0);   正常执行   结束程序
	*   exit(1);   非正常执行 结束程序
	*   #define EXIT_SUCCESS 0;
	*   #define EXIT_FAILURE 1;
	* 
	*/

	// 4 初始化头节点
	phead->pnext = NULL;
	phead->num = 0;

	// 5 初始化尾节点
	prear = phead;

	// 6 通过循环,添加节点
	Node* pNewNode = NULL;
	for (size_t i = 0; i < length; i++)
	
		// 6.1 申请一个节点,检测是否申请成功,给值
		pNewNode = (Node*)malloc(sizeof(Node));
		if (NULL == pNewNode) 
			perror("malloc failed!\\n");
			exit(EXIT_FAILURE);
		
		int n = 0;
		printf("输入节点数据: ");
		scanf("%d", &n);
		pNewNode->num = n;
		pNewNode->pnext = NULL;

		// 6.2 将节点添加到链表
		prear->pnext = pNewNode;
		prear = pNewNode;
	
	return phead;


// **遍历一个单链表
// 参数: 链表头指针
// 返回值: 无
void TraverseList(Node* const pList) 
	// pList->pnext : 避开哑节点
	Node* ptemp = pList->pnext;
	if (NULL == ptemp) 
		printf("链表为空!\\n");
	
	while (ptemp) 
		printf("%d ", ptemp->num);
		ptemp = ptemp->pnext;
	
	printf("\\n");


// **插入一个节点
// 参数:头指针,位置,值
// 返回值:头指针
Node* InsertElement(Node* pList, Node* Position, int val) 
	Node* ptemp = (Node*)malloc(sizeof(Node));
	if (NULL == ptemp) 
		perror("malloc failed!\\n");
		exit(EXIT_FAILURE);
	
	ptemp->num = val;
	ptemp->pnext = Position->pnext;
	Position->pnext = ptemp;
	return pList;


// **清空链表
// 参数:头指针
// 返回值:头指针
Node* DeleteList(Node* pList)

	Node* ptemp = NULL, * pos = NULL;
	pos = pList->pnext;
	pList->pnext = NULL;
	while (pos) 
		ptemp = pos->pnext;
		free(pos);
		pos = ptemp;
	
	return pList;


// **按值删除单个节点
// 参数:头指针,值
// 返回值:头指针
Node* DeleteElement(Node* pList, int val) 
	Node* TempList = pList;
	Node* TempNode = NULL;
	while (TempList->pnext && TempList->pnext->num != val) 
		TempList = TempList->pnext;
	
	if (!TempList->pnext) 
		printf("未找到删除元素,%d不存在!!\\n", val);
	
	else 
		TempNode = TempList->pnext;
		TempList->pnext = TempNode->pnext;
		free(TempNode);
		// TempList->pnext = TempNode->pnext->pnext; //错误
	
	return pList;


// **查找一个节点(按数值)
// 参数:头指针,数值
// 返回值: 目标节点指针
Node* FindElement(Node* pList, int val) 
	Node* ptr = pList->pnext;

	// 1 判断
	if (NULL == ptr) 
		printf("链表为空!\\n");
		return NULL;
	

	// 2 查找
	while (ptr != NULL && ptr->num != val) 
		ptr = ptr->pnext;
	

	// 3 反馈
	if (ptr != NULL) 
		printf("找到 %d 了!\\n", ptr->num);
	
	else 
		printf("未找到 %d !\\n",val);
	
	return ptr;

se 
		TempNode = TempList->pnext;
		TempList->pnext = TempNode->pnext;
		free(TempNode);
		// TempList->pnext = TempNode->pnext->pnext; //错误
	
	return pList;


// **查找一个节点(按数值)
// 参数:头指针,数值
// 返回值: 目标节点指针
Node* FindElement(Node* pList, int val) 
	Node* ptr = pList->pnext;

	// 1 判断
	if (NULL == ptr) 
		printf("链表为空!\\n");
		return NULL;
	

	// 2 查找
	while (ptr != NULL && ptr->num != val) 
		ptr = ptr->pnext;
	

	// 3 反馈
	以上是关于单链表的主要内容,如果未能解决你的问题,请参考以下文章

单链表浅析

循环链表

Python线性表——单链表

408数据结构与算法—单链表的基本操作

单链表的基础操作

循环链表基础