不带头节点的单链表如何头插(多图易懂)

2023-05-16

文章目录

    • 缘起
    • 带头节点的头插
    • 不带头节点的头插
      • 错误的代码
      • 为什么错误
      • 如何修改
        • 返回新的头指针
        • 二级指针

缘起

本文想说的是单向非循环链表的头插。单向非循环链表,可以是带头节点的,也可以是不带头节点的。

对于前者,代码比较简单,后文会说。对于后者,不带头节点的单向链表的头插,我发现即使有多年工作经验的老鸟,也可能写出错误的代码。

带头节点的头插

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


typedef struct node{
    int data;
    struct node *next;
}node_t;


node_t *create_list(void)
{
    node_t *head = malloc(sizeof(node_t));
    if(head == NULL){
        printf("create list failed\n");
        return NULL;
    }

    head->next = NULL;
    return head;
}

int insert_head(node_t *head,  int num)
{
    assert(head != NULL);
    node_t *node = malloc(sizeof(node_t));
    if(node == NULL)
        return -1;

    node->data = num;
    node->next = head->next;
    head->next = node;
    return 0;
}

void printf_list(node_t *head)
{
    assert(head != NULL);
    if(head->next == NULL){
        printf("the list is empty\n");
        return;
    }

    node_t *temp = head->next;
    while(temp){
        printf("%d\n",temp->data);
        temp = temp->next;
    }
}


int main(void)
{
    int a[]={1,2,3,4,5,6};
    node_t *head = create_list();
    if(head == NULL)
        return -1;
    for(int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
    {
        insert_head(head, a[i]);
    }

    printf_list(head);
    return 0;
}

运行结果是:

6
5
4
3
2
1

不带头节点的头插

错误的代码

把上面的代码改动一下,就可以得到一份错误的代码!

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


typedef struct node{
    int data;
    struct node *next;
}node_t;

node_t *head = NULL; // 全局变量,链表的头指针


int insert_head(node_t *head,  int num)
{
    node_t *node = malloc(sizeof(node_t));
    if(node == NULL)
        return -1;

    node->data = num;
    node->next = head;
    head  = node;  // !!!!!! 这里是有问题的
    return 0;
}

void printf_list(node_t *head)
{  
    if(head == NULL){
        printf("the list is empty\n");
        return;
    }

    node_t *temp = head;
    while(temp){
        printf("%d\n",temp->data);
        temp = temp->next;
    }
}


int main(void)
{
    int a[]={1,2,3,4,5,6};
    
    for(int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
    {
        insert_head(head, a[i]);
    }

    printf_list(head);
    return 0;
}

你猜运行结果是什么?

结果是:

the list is empty

为什么错误

插入操作失败了。罪魁祸首就是这个函数

int insert_head(node_t *head,  int num)
{
    node_t *node = malloc(sizeof(node_t));
    if(node == NULL)
        return -1;

    node->data = num;
    node->next = head;
    head  = node;  // !!!!!! 这里是有问题的
    return 0;
}

为什么不对呢?画图说明。

假设有一个单向链表,头指针 head 的值是 0x200,也就是第一个节点的地址。
在这里插入图片描述

调用 insert_head 函数,假设调用者传递的参数是头指针 head 和数字 3(实参),用蓝色表示。

在这里插入图片描述

在调用函数时,系统会把实参的值传递给被调用函数的形参(单向拷贝);

int insert_head(node_t *head, int num) 中的 head 和 num 就是形参;

这里的 head 和全局变量 head 同名,但不是一回事:全局变量 head 是实参,这里的 head 是形参,当然也可以取别的名字;

形参属于函数内的局部变量,在函数被调用的时候,形参的内存在栈上分配,函数执行完毕后,分配的内存被释放,即栈帧被销毁。

假设已经完成了值的拷贝,咱们继续分析:

在这里插入图片描述

代码中有三行我标成红色。

首先分配节点的内存,在堆上分配(假设地址是 0x300),用深蓝色表示;然后用 num 和 head 给节点的成员赋值,这都没有问题。继续执行。

注意这句话: head = node;

在这里插入图片描述

这导致栈上的 head = 0x300;似乎达到了目的,让 head 指向新插入的节点,但是,函数返回后,栈上的变量都会消失。

在这里插入图片描述

最后剩下什么?

在这里插入图片描述

链表还是原来的链表,头指针 head 依然指向 0x200;

唯一剩下的就是分配了一个新节点,且这个新节点指向旧的第一个节点。

如何修改

如果要修改错误的代码,我提供两个思路,第一个就是根据上面的图,返回新的头指针,第二个是用二级指针。

返回新的头指针

node_t *insert_head_2(node_t *head,  int num)
{
    node_t *node = malloc(sizeof(node_t));
    if(node == NULL)
	{
        printf("malloc failed\n");
		return head;
	}

    node->data = num;
    node->next = head;
    return node; // 返回新的头指针
}

主函数修改为:

int main(void)
{
    int a[]={1,2,3,4,5,6};
    
    for(int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
    {
        head = insert_head_2(head, a[i]); 
    }

    printf_list(head);
    return 0;
}

也就是说每次插入都要刷新头指针。

二级指针

再看第二个方法。

在这里插入图片描述

咱们深挖一下这张图,我们的目的不是修改 head 的值,因为他只是头指针的一份拷贝,无论怎么修改,都无法修改函数外面的那个头指针。

那怎么办?我们可以传进来头指针的地址,跑得了和尚跑不了庙,既然拿到了你的内存地址,还怕修改不了你的值?这就是 C 语言的威力,给我一根指针,我能戳动地球!

代码可以这样写:

int insert_head_3(node_t **head,  int num)
{
    node_t *node = malloc(sizeof(node_t));
    if(node == NULL)
	{
        printf("malloc failed\n");
		return -1;
	}

    node->data = num;
    node->next = *head;
	*head = node; // 修改头指针的值
	return 0;
	
}

主函数修改为

int main(void)
{
    int a[]={1,2,3,4,5,6};
    
    for(int i = 0; i < sizeof(a)/sizeof(a[0]); ++i)
    {
        insert_head_3(&head, a[i]);
    }

    printf_list(head);
    return 0;
}

再用图解释一下:

假设头指针 head 的地址是 0x800

在这里插入图片描述

传参完成后(下图中的黄色部分),分配新节点的内存,在堆上分配(假设地址是 0x300),用深蓝色表示;然后用 num*head 给节点的成员赋值;

注意,*head 表示地址 0x800 中的内容,即 0x200

还有,栈上的变量 head 指向了全局变量——头指针 head

在这里插入图片描述

最后一步:修改头指针的值,*head = node;

也就是说,不是把新节点的地址 0x300 赋值给栈上的变量 head,而是赋值给 head 指向的内存。

在这里插入图片描述

可以看到,修改成功,头指针确实指向了新节点。

【end】

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

不带头节点的单链表如何头插(多图易懂) 的相关文章

随机推荐

  • 【Node】Buffer 与 Stream

    node 为什么会出现 Buffer 这个模块 在最初的时候 xff0c JavaScript 只运行在浏览器端 xff0c 对于处理 Unicode 编码的字符串很容易 xff0c 但是对于处理二进制以及非 Unicode 编码的数据便无
  • ROS的tf包中坐标变换的方法

    1 setRotation函数的参数 在坐标变换的时候常有这样的写法 xff1a tfTutorialsAdding a frame C 43 43 transform setOrigin span class hljs symbol tf
  • 卡尔曼滤波的理解以及参数调整

    一 前言 卡尔曼滤波器是一种最优线性状态估计方法 xff08 等价于 在最小均方误差准则下的最佳线性滤波器 xff09 xff0c 所谓状态估计就是通过数学方法寻求与观测数据最佳拟合的状态向量 在移动机器人导航方面 xff0c 卡尔曼滤波是
  • ROS自定义msg类型及使用

    一 创建msg消息 参考 xff1a CreatingMsgAndSrv 首先创建一个空的package单独存放msg类型 xff08 当然也可以在任意的package中自定义msg类型 xff09 这里为便于说明 xff0c 建立一个名为
  • WebRTC-集成qsv硬解码实现

    1 Window下QSV硬解码配置 在libavcodec codec list c下添加 amp ff h264 qsv decoder 在ffmpeg generate gni下加入 34 libavcodec h264idct c 3
  • [ROS] ROS基础,创建工作空间,创建功能包,ros功能包相关命令,Catkin编译系统,catkin_make的编译方式

    1 工作空间 工作空间 xff08 work space xff09 是ROS系统中存放工程开发相关的文件夹 xff0c 其目录结构如下 xff1a src xff1a 代码空间 xff08 Source Space xff09 xff0c
  • ijkplayer-添加播放截图功能

    应用播放的时候需要截图 xff0c 可以在上层使用TexturView来使用截图 xff0c 不过太具有局限性呢 xff0c 还是在底层处理比较好 那么先分析下可以在哪里加截图呢 xff1f 看到网上很多做的都不能支持硬解截图 xff0c
  • avformat_seek_file及其flag含义

    我们从ijk中seek的处理流程来看ffmpeg的这个问题 int ffp seek to l FFPlayer ffp long msec assert ffp VideoState is 61 ffp gt is int64 t sta
  • 单例模式

    单例模式 xff1a include lt iostream gt using namespace std class Singleton public Singleton cout lt lt 34 Singleton虚构函数 34 lt
  • ffmpeg系列-解决ffmpeg获取aac音频文件duration不准

    这个问题是这样产生的 xff0c 一同事反应会随机出现ijk获取到的aac文件的duration不准 xff0c 发来一看 xff0c 确实不准 xff0c 在AE或者系统mediaplayer中得到的都是8 4秒 xff08 准确时间是M
  • 基于librtmp的推流实现

    1 推流 配置好rtmpdump库后 xff0c 我们可以先用命令行来推流看下效果 2 流程图 使用librtmp发布RTMP流的可以使用两种API xff1a RTMP SendPacket 和RTMP Write 使用RTMP Send
  • ijkplayer-音视频变速播放实现

    本文主要分析变速播放框架实现细节 xff0c 不分析sonic以及soundtouch变速算法 在我的sonic变速变调原理一文中会详细讲解基于基音周期来实现变速变调的原理 1 变速入口分析 从jni层的 setPropertyFloat函
  • Android_WakeLock使用

    1 前言与WakeLock简介 1 1 前言 一些手机app xff08 如微信 QQ等 xff09 有新消息来到达 xff0c 手机屏幕即使在锁屏状态下也会亮起 xff0c 并提示用户有新消息 但是 xff0c 一般情况下手机锁屏后 xf
  • ContentResolver.query详解

    1 查询手机的联系人 public void getContacts ContentResolver contentResolver 61 this getContentResolver Cursor cursor 61 contentRe
  • [ROS] 安装Gazebo 使用Gazebo 实现摄像头仿真 雷达仿真 Kinect仿真

    目录 安装Gazebo 1 添加源 2 安装gazebo 使用Gazepo 实现摄像头仿真 1 工作空间与功能包的创建 2 xff09 Gazebo配置文件 3 车体urdf建模与控制程序 4 launch文件 5 执行launch文件运行
  • jni开发-GetMethodID与CallObjectMethod的坑

    在java层中声明一个方法用于创建一个audiotrack xff0c 在C层中调用这个方法并获取audiotrack对象 先看下面的代码 xff1a SuPlayer java public AudioTrack createAudioT
  • 基于电信行业的AIOps应用与实践

    欢迎关注 程序杂货铺 公众号 xff0c 里面有精彩内容 xff0c 欢迎大家收看 1 摘要 xff1a 在大型互联网架构中 xff0c 为提升平台的计算能力及资源利用率 xff0c 普遍采用分布式技术 然而使用分布式技术也会带来一些潜在问
  • 关于解耦的理解

    在程序设计过程中 xff0c 最头痛的不是逻辑的编写过程 xff0c 更不是算法的设计 xff0c 最头痛的是如何设计出一个容易维护 xff0c 扩展性好的东西 而耦合问题是最令人烦躁的 xff0c 它的存在很多人发现不了 xff0c 所以
  • OFFICER: A general optimization framework for OpenFlow rule allocation and endpoint policy enforceme

    OFFICER gt 转发规则放置问题 gt 什么是规则放置问题 xff1f 一组规则如何放置到容量有限的交换机上 xff0c 以满足上层应用的策略 xff08 ACL 流转发 xff09 规则用来匹配流 xff0c 其action是策略的
  • 不带头节点的单链表如何头插(多图易懂)

    文章目录 缘起带头节点的头插不带头节点的头插错误的代码为什么错误如何修改返回新的头指针二级指针 缘起 本文想说的是单向非循环链表的头插 单向非循环链表 xff0c 可以是带头节点的 xff0c 也可以是不带头节点的 对于前者 xff0c 代