【数据结构】手撕单链表

这篇具有很好参考价值的文章主要介绍了【数据结构】手撕单链表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一,链表的概念及结构

二,接口实现

        1,单链表的创建

        2,接口函数

        3,动态创立新结点

        4,打印

        5,头插

        6,头删

        7,尾插

        8,尾删

        9,查找

        10,单链表在pos位置之后插入x

        11,单链表删除pos位置之后的值

        12,销毁

三,源代码

LKList.h

LKList.c

四,总结


一,链表的概念及结构

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

 【数据结构】手撕单链表,数据结构,c语言,链表

 【数据结构】手撕单链表,数据结构,c语言,链表

 而在数据结构中:

【数据结构】手撕单链表,数据结构,c语言,链表

注意:

1,从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

 2,现实中的结点一般都是从堆上申请出来的

 3,从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续;

 实际中链表的结构非常多样,今天我们来写一下单链表,此表一会其他的自然水到渠成!

二,接口实现

        1,单链表的创建

//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{
	LKLDataType data;
	struct LinKedListNode* next;
}LKLNode;

首先创建一个结构体表示单链表data是存储的值,LKLDataType是储存的值的数据类型,next是结点----指向下一个;

这里的LKLDataTypeint的重命名,也可以说是数据类型的重命名,这样统一化方便后续更改;

        2,接口函数

// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);

 这是以上要实现的接口函数;

        3,动态创立新结点

//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{
	LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

后面创立新节点时直接调用此函数,一定要向堆区申请空间,这样函数结束空间会保留不会被回收;

        4,打印

//打印
void LKLPrint(LKLNode* phead)
{
	assert(phead);
	LKLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

打印也就是打印data的值,用cur=phead然后每次打印完都让cur走向下一个直到为空结束;

        5,头插

//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	LKLNode* newnode = BuyLKLNode(x);
	newnode->next = *phead;
	*phead = newnode;
}

先断言一下,既然插入数据那就要申请一个新节点,然后令新节点的next指向phead然后再令phead指向新节点;

        6,头删

//头删
void LKLNodeBackFront(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//非空
	LKLNode* newnode = (*phead)->next;
	free(*phead);
	*phead = newnode;
}

还是先断言,有人会问为什么要断言两次?其实很好判断,哪个需要解引用那个就需要断言;

令一个变量newnode等于头结点的下一个,在释放头结点,在令头结点指向newnode即可;

        7,尾插

//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	assert(*phead);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = *phead;
	//为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//非空
	else
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

还是先断言判断,然后要插入一个新的数据先申请一个新结点,如果头结点为空则直接让头结点指向新结点即可,如果头结点不为空,则需要找到next为空的结点,这里用一个循环搞定,然后再直接让next为空的结点指向新节点即可;

        8,尾删

//尾删
void LKLNodePopBack(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//一个
	if ((*phead)->next==NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//两个及以上
	else
	{
		LKLNode* tail = *phead;

		/*LKLNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(prev->next);
		prev->next = NULL;*/

		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

还是先断言一下,然后这里有两种情况链表只有一个结点,两个以上结点的时候

当链表只有一个结点也就是头结点,直接头删即可;

两个以上结点的时候,我这里有两种解决方案;

方案一常规法:先用循环找到next为空的结点,并且在循环里保留上一个结点prev,然后释放next为空的结点再让prevnext指向空即可;

方案二:不需要标记上一个结点,直接原地判断,判断结点的nextnext是否为空,否则继续向后推进,是则释放结点的next然后再令自己的next指向空也就相当于变成了尾结点

        9,查找

// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{
	assert(phead);
	LKLNode* pos = phead;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}

老样子先断言一下,然后直接用循环遍历链表找到datax的值然后返回此结点即可;

        10,单链表在pos位置之后插入x

// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{
	assert(pos);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = pos;
	newnode->next = cur->next;
	cur->next = newnode;
}

先断言,要插入数据先申请一个新结点,然后令新结点的next指向posnext,再返回来让posnext指向新结点;

        11,单链表删除pos位置之后的值

// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{
	assert(pos);
	assert(pos->next);
	LKLNode* cur = pos;
	LKLNode* newnode = cur->next->next;
	free(cur->next);
	cur->next = newnode;
}

要删除值首先要确保得有值,所以开始断言;

先定义一个变量newnode指向posnextnext,然后再释放posnext,再令pos指向newnode以达到删除之后的效果;

        12,销毁

// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{
	assert(phead);
	assert(*phead);
	LKLNode* cur = *phead;
	LKLNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}

老样子那个需要解引用那个就先断言一下,然后用循环遍历,先标记下一个结点,然后释放自己,再让自己指向标记的结点直到为空结束;

三,源代码

LKList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//无头 + 单向 + 非循环链表增删查改实现
typedef int LKLDataType;
typedef struct LinKedListNode
{
	LKLDataType data;
	struct LinKedListNode* next;
}LKLNode;

// 动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x);
// 打印
void LKLPrint(LKLNode* phead);
// 头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x);
// 头删
void LKLNodeBackFront(LKLNode** phead);
// 尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x);
// 尾删
void LKLNodePopBack(LKLNode** phead);
// 查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x);
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x);
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos);
// 销毁
void LKLDestroy(LKLNode** plist);

LKList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"LKList.h"

//动态创立新结点
LKLNode* BuyLKLNode(LKLDataType x)
{
	LKLNode* newnode = (LKLNode*)malloc(sizeof(LKLNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//头插
void LKLNodePushFront(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	LKLNode* newnode = BuyLKLNode(x);
	newnode->next = *phead;
	*phead = newnode;
}
//头删
void LKLNodeBackFront(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//非空
	LKLNode* newnode = (*phead)->next;
	free(*phead);
	*phead = newnode;
}
//尾插
void LKLNodePushBack(LKLNode** phead, LKLDataType x)
{
	assert(phead);
	assert(*phead);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = *phead;
	//为空
	if (*phead == NULL)
	{
		*phead = newnode;
	}
	//非空
	else
	{
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
//尾删
void LKLNodePopBack(LKLNode** phead)
{
	assert(phead);
	//为空
	assert(*phead);
	//一个
	if ((*phead)->next==NULL)
	{
		free(*phead);
		*phead = NULL;
	}
	//两个及以上
	else
	{
		LKLNode* tail = *phead;

		/*LKLNode* prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}
		free(prev->next);
		prev->next = NULL;*/

		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
// 单链表查找
LKLNode* LKLFind(LKLNode* phead, LKLDataType x)
{
	assert(phead);
	LKLNode* pos = phead;
	while (pos)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	return NULL;
}
// 单链表在pos位置之后插入x
void LKLInsertAfter(LKLNode* pos, LKLDataType x)
{
	assert(pos);
	LKLNode* newnode= BuyLKLNode(x);
	LKLNode* cur = pos;
	newnode->next = cur->next;
	cur->next = newnode;
}
// 单链表删除pos位置之后的值
void LKLEraseAfter(LKLNode* pos)
{
	assert(pos);
	assert(pos->next);
	LKLNode* cur = pos;
	LKLNode* newnode = cur->next->next;
	free(cur->next);
	cur->next = newnode;
}

// 单链表的销毁
void LKLDestroy(LKLNode** phead)
{
	assert(phead);
	assert(*phead);
	LKLNode* cur = *phead;
	LKLNode* next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
}
//打印
void LKLPrint(LKLNode* phead)
{
	assert(phead);
	LKLNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

四,总结

做数据结构的题目画图很重要,小伙伴们刚开始喜欢用脑子去构图想象,但遇到复杂的情况会紊乱的,画图最为可观方便,可以培养一个良好的画图习惯;

如有不足之处欢迎来补充交流!

完结。。。文章来源地址https://www.toymoban.com/news/detail-686161.html


到了这里,关于【数据结构】手撕单链表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【数据结构】手撕双向链表

    【数据结构】手撕双向链表

    目录 前言 1. 双向链表  带头双向循环链表的结构 2. 链表的实现 2.1 初始化 2.2 尾插 2.3 尾删 2.4 头插 2.5 头删 2.6 在pos位置之前插入 2.7 删除pos位置 3.双向链表完整源码 List.h List.c 在上一期中我们介绍了单链表,也做了一些练习题,在一些题中使用单链表会十分繁琐。因为单链

    2024年02月05日
    浏览(16)
  • 数据结构-链表-单链表(3)

    数据结构-链表-单链表(3)

    目录 1. 顺序表的缺陷 2. 单链表 2.1 单链表的基本结构与接口函数 2.2 重要接口 创建新节点的函数: 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后面删除 3. 链表的缺陷与优势: 4. 链表与顺序表比较 写在最后: 为什么

    2024年01月17日
    浏览(235)
  • 数据结构---手撕图解单链表---phead的多种传参方式对比和辅助理解

    数据结构---手撕图解单链表---phead的多种传参方式对比和辅助理解

    前面我们知道了顺序表,当顺序表的容量到达上限后就需要申请新的空间,而申请新空间就会遇到一些问题 1.当利用realloc函数进行申请新空间时,会涉及到开辟新空间–拷贝原有数据–释放原空间这三个步骤,而这三个步骤会有不小的损耗 2.增容一般是2倍的增长,势必会有

    2024年02月17日
    浏览(10)
  • 数据结构:手撕图解单链表---phead的多种传参方式对比和辅助理解

    数据结构:手撕图解单链表---phead的多种传参方式对比和辅助理解

    前面我们知道了顺序表,当顺序表的容量到达上限后就需要申请新的空间,而申请新空间就会遇到一些问题 1.当利用realloc函数进行申请新空间时,会涉及到开辟新空间–拷贝原有数据–释放原空间这三个步骤,而这三个步骤会有不小的损耗 2.增容一般是2倍的增长,势必会有

    2024年02月15日
    浏览(8)
  • 【数据结构与算法】手撕链表OJ题

    【数据结构与算法】手撕链表OJ题

    给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 思路一 :一种比较普遍的方式,边遍历边找不同。我们可以通过定义两个指针,一个指向头节点,一个置为NULL。当遇到值为相同的时候,直接跳过去。指向下一位

    2024年02月10日
    浏览(10)
  • 【手撕数据结构】(三)顺序表和链表

    【手撕数据结构】(三)顺序表和链表

    🎗️线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 🎗️线性表在逻辑上是线性结构,也就说是一条连续的直线。但是在物理结构上并不一定是连续的,线性表在物理上存储

    2024年02月05日
    浏览(43)
  • 数据结构之手撕链表(讲解➕源代码)

    数据结构之手撕链表(讲解➕源代码)

    我们在学习过顺序表之后,会发现两点不是很优秀的操作: 1.顺序表的 头插和中间的插入,头删和中间的删除:          需要不断的覆盖数据,时间复杂度是O(n),当我们的顺序表存入100w个数据的时候,花费的时间是非常之多的。 2. 动态开辟空间:         a. 一般动态

    2024年02月08日
    浏览(11)
  • 【数据结构】- 链表之单链表(下)

    【数据结构】- 链表之单链表(下)

    未来藏在迷雾中 叫人看来胆怯 带你踏足其中 就会云开雾散 本章是关于数据结构中的链表之单链表(下) 提示:以下是本篇文章正文内容,下面案例可供参考 1.2.1 在pos位置插入(也就是pos位置之前) 流程图 多个节点 一个节点 1.2.2 在pos位置之后插入 流程图: 注意: 下面这种写

    2023年04月23日
    浏览(47)
  • [数据结构]链表之单链表(详解)

    [数据结构]链表之单链表(详解)

    在学习 链表 之前,我们已经学习了 顺序表 了 根据 顺序表 的特点,我们可以发现 顺序表 有优点,也有一些缺陷。 所以根据 顺序表 的缺点,链表就横空出世啦~ **概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次

    2023年04月08日
    浏览(39)
  • 数据结构入门 — 链表详解_单链表

    数据结构入门 — 链表详解_单链表

    数据结构入门 — 单链表详解 * 博客主页链接:https://blog.csdn.net/m0_74014525 关注博主,后期持续更新系列文章 文章末尾有源码 *****感谢观看,希望对你有所帮助***** 第一篇:数据结构入门 — 链表详解_单链表 第二篇:数据结构入门 — 链表详解_双向链表 第三篇:数据结构入门

    2024年02月11日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包