【数据结构】单链表详解

2023-11-06

当我们学完顺序表的时候,我们发现了好多问题如下:

  1. 中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们
    再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

如何解决上面的问题呢??今天就跟着小张一起学习单链表吧!!

链表的概念及结构

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

注意:1.链式结构在逻辑上是连续的,但是在物理上不一定连续
2.结点一般都是在堆上申请出来的(malloc)
3.从堆上申请的空间,是按照一定策略来分配的,两次申请的空间可能连续,也可能不连续

单链表

在这里插入图片描述

单链表的接口实现

void  printdata(info* phead)//打印单链表
info* BuySListNode(int x)//创建新结点
void pushback(info** pphead, int x)//尾插
void pushFront(info** pphead, int x)//头插
void popFront(info** pphead)//头删
void popBack(info** pphead)//尾删
info* SListFind(info* pphead, int x)//单链表查找
SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
void SeqListErase(info** pphead, info* pos)//删除pos位置的值
void SListInsertAfter(info* pos, int x)//单链表在pos位置之后插入x
void SListEraseAfter(info* pos)//删除pos的后一个位置

0.结构体定义单个结点

typedef struct info {
	int data;//data存数据
	struct info* next;//info*next存放下一个结点的地址

}info;

1.创建新结点

info* BuySListNode(int x)
{
	info* newnode = (info*)malloc(sizeof(info));//空间申请
	assert(newnode);//断言,新结点是否申请到了
	newnode->data = x;//数据赋值
	newnode->next = NULL;//指向的地址赋值
	return newnode;//将申请好的空间首地址返回回去



}

在这里插入图片描述

断言,如果没申请到空间,mallo返回空指针,断言newnode就会报错

为什么要用二级指针传参在尾插,头插,尾删,头删中

分析:在这里插入图片描述

2.尾插

void pushback(info** pphead, int x)//尾插
{
	info* newnode = BuySListNode(x);//将创建好的新结点的地址保存在newnode变量中
	

	if (*pphead == NULL)//链表无结点
	{
		*pphead = newnode;// 将创建好的头节点的地址给给*pphead,作为新头节点的地址
	}
	else
	{
		info* tail = *pphead;//定义一个指针,先指向头结点的地址
		while (tail->next != NULL)//循环遍历找尾结点
		{
			tail = tail->next;//指针指向下一个结点

		}
		tail->next = newnode;//找到尾结点,将尾结点的next存放新接结点的地址

	}

}

分析:
在这里插入图片描述

文字解释:大体上就是直接将最后一个结点的next存入新结点的地址,然后将新结点的next存入空(NULL)。
申请新的结点,如果pphead为空地址,链表还没有结点,将新结点的地址传给pphead,新结点的地址就是头结点的地址,如果该链表中已经存在结点,定义一个指针先存放头节点的地址,然后遍历整个链表,循环寻找哪个结点的next为NULL,不是的话就将tail指向下一个结点,对应操作为tail = tail->next;结点的next为NULL,那个结点就是尾结点,找到尾结点之后将尾结点的next存入要插入新结点的地址,新结点的next已经在 BuySListNode(int x)置为空地址

3.打印链表

void  printdata(info* phead)
{
	info* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");


}

分析在这里插入图片描述
文字描述:定义一个指针cur指向头结点,使用这个指针循环遍历,这个指针指向的不为空的话就继续循环,在循环中打印cur->data,对应的操作是printf(“%d->”, cur->data);打印%d后面加->是为了方便观察;然后将cur指针移动到下一个结点的位置对应操作是cur = cur->next;,继续打印。

4.头插

void pushFront(info** pphead, int x)//头插
{
	info* newnode = BuySListNode(x);//创建新结点,将新结点的地址存放在newnode指针变量中
	newnode->next = *pphead;//新结点的下一个结点应该为旧头结点的地址
	*pphead = newnode;//新头结点的地址更新为newnode指针所指向的地址


}

分析
在这里插入图片描述

文字描述:将创建的新结点的地址存放在newnode指针变量中,pphead为原先头结点的地址,头插一个新结点后,应该将新结点的next存放原先头结点的地址,对应操作为newnode->next = pphead;然后保存在pphead应该为新的头结点的地址,也就是newnode所指向的地址,对应操作为pphead = newnode;

5.头删

void popFront(info** pphead)
{
	info* p =(*pphead)->next;//定义指针存放头结点的下一个结点的地址
	free(*pphead);//释放头结点
	*pphead = p;//头结点地址更新为原先头结点的下一个结点的地址
}

分析:
在这里插入图片描述

6.尾删

void popBack(info** pphead)
{
	info* tailprev = NULL;
	info* tail = *pphead;


	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
    }
	else
	{while (tail->next != NULL)
		{
			tailprev = tail;
			tail = tail->next;
       }
		free(tail);
		tailprev->next = NULL;
	}
}

分析:在这里插入图片描述

7.修改

info* SListFind(info* pphead, int x)
{
	info* cur = pphead;//cur指针保存pphead指针接收的地址
	while (cur)
	{
		if (cur->data == x)
		{  //cur->data==y可以将查找出来的值修改为y,不用单独写修改函数,传参也要多一个y
			return cur;//找到的话返回该结点的地址
		}
		cur = cur->next;//cur指向下一个结点的地址
    }
	return NULL;

}

分析:
在这里插入图片描述

8.pos指针指向结点的地址的前一个位置前插插入(随便插)

SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
{
	assert(pphead);//头结点地址不能为空
	if (pos == *pphead)//pos指针指向结点的地址为头结点,相当于头插
	{
		pushFront(*pphead, x);//调用头插函数

	}
	else
	{
		info* prev = *pphead;//定义一个prev指针,先让他保存头结点的地址
		while (prev->next != pos)//循环遍历找pos指针指向结点的前一个结点
		{
			prev = prev->next;

        }
		info* newnode=BuySListNode(x);//申请新结点,将申请好的结点地址存放在newnode指针变量里面
		prev->next = newnode;//使之前pos指针指向的结点的前一个结点的next存入插入新结点的地址,
		newnode->next = pos;//然后新结点的next存入pos指向结点的地址
}
}

分析:在这里插入图片描述

9.删除pos位置的值

void SeqListErase(info** pphead, info* pos)
{
	if (pos == *pphead)//删除结点是否为头结点
	{
		popFront(*pphead);//头删
    }
	else
	{
		info* prev = *pphead;//定义一个prev指针,先让他存放头结点的地址
		while (prev->next != pos)//循环遍历寻找哪个结点的next存放的是pos指针指向的结点的地址
		{
			prev = prev->next;//prev指针指向下一个结点
        }
		prev->next = pos->next;//pos指针指向的结点的上一个结点的next存放pos指针指向的结点的下一个结点的地址
		free(pos);//释放掉pos指针指向的那块空间

    }
}

分析:在这里插入图片描述

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

void SListInsertAfter(info* pos, int x)
{
	assert(pos);//防止在空指针
	info* newnode=BuySListNode(x);//申请新结点
	newnode->next = pos->next;
	pos->next = newnode;

}

分析:在这里插入图片描述
那么1,2是否可以反过来

	pos->next = newnode;
	newnode->next = pos->next;
	

不行,d3的地址会丢失
出现下图的情况:在这里插入图片描述

11.删除pos的后一个位置

void SListEraseAfter(info* pos)
{
	assert(pos);//防止pos指向空地址
	if (pos->next == NULL)//如果pos指针指向最后一个结点
		return;//直接退出,不往下执行
	info* del = pos->next;//保存pos指针指向结点的下一个结点的地址
	pos->next = del->next;//更新pos指针指向的结点的下一个结点为pos指针指向结点下下一个结点的地址
	free(del);//释放掉保存pos指针指向结点的下一个结点的地址的空间。

}

分析:在这里插入图片描述

12.完整源码

#include<stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct info {
	int data;
	struct info* next;

}info;
void  printdata(info* phead)
{
	info* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");


}
info* BuySListNode(int x)
{
	info* newnode = (info*)malloc(sizeof(info));
	assert(newnode);
	newnode->data = x;
	newnode->next = NULL;
	return newnode;



}
void pushback(info** pphead, int x)//尾插
{
	info* newnode = BuySListNode(x);
	

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		info* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;

		}
		tail->next = newnode;

	}













}
void pushFront(info** pphead, int x)//头插
{
	info* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;


}
void popFront(info** pphead)//头删
{
	info* p =(*pphead)->next;
	free(*pphead);
	*pphead = p;







}
void popBack(info** pphead)//尾删
{
	info* tailprev = NULL;
	info* tail = *pphead;


	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;





	}
	else
	{

		while (tail->next != NULL)
		{
			tailprev = tail;
			tail = tail->next;


		}
		free(tail);
		tailprev->next = NULL;
	}
}
info* SListFind(info* pphead, int x)//查找
{
	info* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;





	}
	return NULL;
}
SeqListInsert(info** pphead,info* pos,int x)//pos指针指向结点的地址的前一个位置前插插入
{
	assert(*pphead);
	if (pos == *pphead)
	{
		pushFront(*pphead, x);

	}
	else
	{
		info* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;

        }
		info* newnode=BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;








	}










}
void SeqListErase(info** pphead, info* pos)
{
	if (pos == *pphead)
	{
		popFront(*pphead);

    
	}
	else
	{
		info* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;



		}
		prev->next = pos->next;
		free(pos);

    }





}
void SListInsertAfter(info* pos, int x)
{
	assert(pos);
	info* newnode=BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
void SListEraseAfter(info* pos)
{
	assert(pos);
	if (pos->next == NULL)
		return;
	info* del = pos->next;
	pos->next = del->next;
	free(del);

}

int main()
{

	info* n1 = (info*)malloc(sizeof(info));
	info* n2 = (info*)malloc(sizeof(info));
	info* n3 = (info*)malloc(sizeof(info));
	info* n4 = (info*)malloc(sizeof(info));
	assert(n1 && n2 && n3 && n4);

	n1->data = 1;
	n2->data = 2;
	n3->data = 3;
	n4->data = 4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;



	printf("原链表:");
	printdata(n1);
	printf("\n");
	printf("尾插:");
	pushback(&n1, 5);
	pushback(&n1, 6);
	pushback(&n1, 7);
	pushback(&n1, 8);
	printdata(n1);
	printf("\n");
	printf("头插:");
    pushFront(&n1, 10);
	pushFront(&n1, 20);
	printdata(n1);
	printf("\n");
	printf("头删:");
	popFront(&n1);
	printdata(n1);
	printf("\n");
	printf("尾删:");
	popBack(&n1);
	popBack(&n1);
	printdata(n1);
	printf("\n");
	printf("查找到并修改:");
	SListFind(n1, 3)->data = 80;
	printdata(n1);
	printf("\n");
	printf("pos指针指向结点的地址的前一个位置前插插入:");
	SeqListInsert(&n1, n4, 100);
	printdata(n1);
	printf("\n");
	printf("删除pos位置的值:");
	SeqListErase(&n1, n4);
	printdata(n1);
	printf("\n");
	printf("单链表在pos位置之后插入x:");
	SListInsertAfter(n2, 1000);
	printdata(n1);
	printf("\n");
	printf("删除pos的后一个位置:");
	SListEraseAfter(n1);
	printdata(n1);
	printf("\n");

}

13.代码编译运行

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【数据结构】单链表详解 的相关文章

  • Qt常用小部件

    常用小部件 QLabel 常用来显示文本 数字 图片 gif动图 新建桌面应用程序 项目名testQLabel 基类QWidget 类名Widget 勾选创建界面文件 类构造函数中添加如下代码 准备好一个图片及gif格式动图 ui gt s
  • cookie,session,token之间的关系

    今天和大家聊一下关于 Cookie Session Token 的那些事儿 这是我的一个读者朋友面试微信的实习岗位时遇到的 在此和大家分享一下 话不多说 直接开车 1 网站交互体验升级 作为网友的我们 每天都会使用浏览器来逛各种网站 来满足
  • nginx做yum源

    我这边环境是原先有个nginx只是做了代理转发 现在需要在通过nginx做yum源方便后期安装源 1 原先的配置代理转发 为不影响原先配置及端口 在http中最末尾加 include local nginx conf d conf 加载其它

随机推荐

  • (UI)Android自定义图片裁剪

    具体UI效果如下 思路 绘制5个rect 其中四个为半透明深色背景 一个为透明背景的裁剪内容框 之前也考虑过用region 但是自测的时候 发现两个region之间颜色会相互影响 可能是我代码问题 有了解的小伙伴可以指导一下哈 就用了5个R
  • jQuery 入门教程(36): jQuery UI Menu 示例

    jQuery Menu 组件可以应用到任何具有父 子关系的元素 就其变为菜单 但通常使用u gt li 如果你希望使用除 ul li 之外的元素 可以通过menus 来配置 下例使用缺省的 ui和 li 菜单支持选择事件select 因此可
  • YoloV5源码部分注释解读(ultralytics版本)(yolo.py)

    yolo py的主要作用是构建yolov5的模型 而且这个yolo py文件可以单独执行 这里主要对目标检测中的相关类进行了注释解读 分割等没有用到的暂时没有注释 第一部分 导入包 配置路径等 第二部分 程序入口 执行程序 在这部分中 创建
  • el-input输入框涉及到scope的校验问题

    需求描述 在el table中 对每一行数据的数量进行校验 对于数量要用el input输入框进行输入数值 校验主要涉及 每次输入的时候都要清空el input输入框的数值 输入值只能为数字 并且要对输入的数量进行判断是否超过库存的最大数量
  • php双写绕过,PHP preg_系列漏洞小结

    最近看 P 神以前写的文章 其中在 3 个参数的回调函数中提到了 preg replace e 命令执行 对这块不是很熟悉的我特此写这篇文章总结学习一下 preg matchint preg match string pattern str
  • 【C++】【python】【kafka】使用C++调用python函数向kafka发送消息

    1 python操作kafka的代码 import sys import time import json from kafka import KafkaProducer from kafka import KafkaConsumer fr
  • (数据结构)顺序表操作——C实现

    线性表的顺序表示指的是用一组地址连续的存储单元 内存 依次存储线性表的数据元素 特点 逻辑相邻 物理也相邻 支持存放 插入 删除 修改 读取数据等操作 一位数组是一种特殊的顺序表 但顺序表不是数组 插入和删除较链表来说不太方便 固定大小的顺
  • 常见的一些医疗图像处理步骤

    一 数据格式 1 1 dicom DICOM是医学图像中标准文件 这些文件包含了诸多的元数据信息 比如像素尺寸 每个维度的一像素代表真实世界里的长度 此处以kaggle Data Science Bowl 数据集为例 data scienc
  • 一、C++应用:wxWidget绘图基础

    1 wxWidget绘图基础 1 1 实现窗口 wxWidgets窗口程序需要四个必须的部分 1 添加一个继承wxApp的应用程序类 2 添加一个继承wxFrame的框架类 3 重载wxApp OnInit 成员函数 并在其中创建框架类的对
  • 毕业设计-基于深度学习火灾烟雾检测识别系统-yolo

    前言 大四是整个大学期间最忙碌的时光 一边要忙着准备考研 考公 考教资或者实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力 近几年各个学校要求的毕设项目越来越难 有不少课题是研究生级别难度的 对本科同学来说是充满挑战 为帮助大
  • 基于STM32F103单片机的智能农场温室大棚光照温度土壤湿度检测系统

    系统功能设计 末尾附文件 本系统由STM32F103C8T6单片机核心板 LCD1602液晶显示 光照检测 土壤湿度传感器 风扇控制 继电器控制 高亮LED灯补光 按键控制组成 1 通过光敏电阻检测光照强度AD转换后给系统显示 将光照强度值
  • 经典的Python爬虫和网络编程面试题

    1 动态加载又对及时性要求很高怎么处理 Selenium Phantomjs 尽量不使用 sleep 而使用 WebDriverWait 2 分布式爬虫主要解决什么问题 1 ip 2 带宽 3 cpu 4 io 3 什么是 URL URL
  • UG NX导出2D图纸

    创建图纸 1 同时按下ctrl shift D进入制图页面 点击左上角 新建图纸页 选择视图创建向导调整视图 2 方向 gt 定制的视图 然后指定一个面 点击 完成 导出2D图纸 文件 gt 导出AutoCAD DXF DWG 退出制图 同
  • 【Leetcode041】 最大子数组和

    53 最大子数组和 给你一个整数数组 nums 请你找出一个具有最大和的连续子数组 子数组最少包含一个元素 返回其最大和 子数组 是数组中的一个连续部分 示例 1 输入 nums 2 1 3 4 1 2 1 5 4 输出 6 解释 连续子数
  • Maven仓库(仓库配置) 配置好你的仓库~

    文章目录 远程仓库的配置 远程仓库的认证 部署至远程仓库 镜像配置 远程仓库的配置 Repositories元素下 可以用repository 子元素声明一个或者多个远程仓库 id 远程仓库的ID 必须唯一 maven自带的中央仓库的id为
  • 使用 OpenSSH 从 PC 机传送文件到CPU板时,CPU板和虚拟机不在一个ip网段无法通信

    本人也是萌新小白 这里主要分享一下自己调试过程中遇到的问题和解决方法 希望能帮到大家 gt lt 项目场景 从PC机的虚拟机的Ubuntu系统传输文件到CPU板系统 问题描述 1 首先是CPU板系统连接不到PC机的网络 2 CPU板系统和P
  • XML - insert

    XML insert 属性 属性 描述 id 命名空间中的唯一标识符 可被用来代表这条语句 parameterType 将要传入语句的参数的完全限定类名或别名 这个属性是可选的 因为 MyBatis 可以通过 TypeHandler 推断出
  • vue项目报错in ./src/app.vue?vue&type=style&index=0&lang=less

    原因 less和less loader版本号过高 解决 先删除原来的再重新安装 npm uninstall less loader npm uninstall less npm install less loader 4 1 0 D npm
  • flutter 设置状态栏的颜色,背景appBar: AppBar( elevation: 0.5, brightness: Brightness.light,

    在有AppBar的界面 状态栏一般有Brightness dark 和Brightness light两种模式 分别是白色的导航栏字体颜色和黑色的字体颜色 appBar AppBar elevation 0 5 brightness Bri
  • 【数据结构】单链表详解

    当我们学完顺序表的时候 我们发现了好多问题如下 中间 头部的插入删除 时间复杂度为O N 增容需要申请新空间 拷贝数据 释放旧空间 会有不小的消耗 增容一般是呈2倍的增长 势必会有一定的空间浪费 例如当前容量为100 满了以后增容到200