首页 > 编程语言 >数据结构与算法-单链表

数据结构与算法-单链表

时间:2025-01-07 19:04:44浏览次数:3  
标签:NODE head 单链 next 链表 算法 数据结构 data 节点

单链表

链表的介绍

在这里插入图片描述

既然顺序存储中的数据因为挤在一起而导致需要成片移动,那很容易想到的解决方案是将数据离散地存储在不同内存块中,然后在用来指针将它们串起来。这种朴素的思路所形成的链式线性表,就是所谓的链表。

在这里插入图片描述

上图中,所有的节点均保存一个指针,指向其逻辑上相邻的下一个节点(末尾节点指向空)。另外注意到,整条链表用一个所谓的头指针 head 来指向,由 head 开始可以找到链表中的任意一个节点。head 通常被称为头指针

链表的基本操作,一般包括:

  1. 节点设计
  2. 初始化空链表
  3. 增删节点
  4. 链表遍历
  5. 销毁链表

单链表的节点设计

单向链表的节点非常简单,节点中除了要保存用户数据之外,只需要增加一个指向本类节点的指针即可,如下所示:

typedef int DATA;

// 创建链表的节点结构体
typedef struct node
{
    DATA         data; // 节点数据
    struct node *next; // 指向下一个同类节点的指针
} NODE;

单链表初始化

空链表有两种常见形式,一种是带头节点的,一种是不带头节点的。所谓的头节点是不存放有效数据的节点,头节点可选,为了方便某些操作。

注: 头节点可选,头指针head必须,是链表的入口。


/**
 * 链表的创建
 * @param **head 链表,默认指向头节点
 * @param data 节点数据
 */
int slist_create(NODE **head, DATA data)
{
    // 创建一个新节点
    NODE *p = (NODE *)malloc(sizeof(NODE));
    if (!p)
    {
        return -1;
    }
    // 初始化节点
    p->data = data;
    p->next = NULL;

    // 将创建的节点作为链表的头结点
    *head = p;

    return 0;
}

单链表增加节点

相对于顺序表需要整片移动数据,链表增删节点只需要修改几个相关指针的指向,动作非常快速。此处详细列出了单链表通过头插法、尾插法和任意位置插入的方法。


/**
 * 向链表头部插入一个节点数据(头插法)
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param data 存储在新节点的数据
 */
int slist_addHead(NODE **head, DATA data)
{
    // 创建一个节点(就是在内存中申请空间)
    NODE *p = (NODE *)malloc(sizeof(NODE));
    if (!p)
        return -1;

    // 初始化节点
    p->data = data;
    p->next = *head;

    // 将当前的新节点作为头结点
    *head = p;

    return 0;
}

/**
 * 向链表尾部插入一个节点数据(尾插法)
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param data 存储在新节点的数据
 */
int slist_addTail(NODE **head, DATA data)
{
    // 创建一个新节点
    NODE *pNew = (NODE *)malloc(sizeof(NODE));
    if (!pNew)
        return -1;
    // 初始化
    pNew->data = data;
    pNew->next = NULL; // 因为新节点要作为尾结点,所以要指向NULL

    // 通过指针尾随法,实现尾结点的查找
    NODE *p = *head, *q = NULL;
    // 如果*p不存在,也就是空链表
    if (!p)
    {
        // 如果是空链表,就将新节点作为链表的头节点
        *head = pNew;
        return 0;
    }

    // 使用指针尾随找尾结点
    while (p)
    {
        q = p;
        p = p->next;
    }
    // 此时循环结束,p = NULL,q = 尾结点
    // 改变指向,让尾结点的next指向新节点
    q->next = pNew;

    return 0;
}

/**
 * 向链表节点值为pos的位置插入一个节点数据(中间插法)
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param pos  插入节点(目标节点)的位置的节点数据
 * @param data 存储在新节点的数据
 */
int slist_insert(NODE **head, DATA pos, DATA data)
{
    // 创建一个新节点
    NODE *pNew = (NODE *)malloc(sizeof(NODE));
    if (!pNew)
        return -1;
    // 初始化
    pNew->data = data;
    pNew->next = NULL;

    // 找位置,永远的指针尾随法
    NODE *p = *head, *q = NULL;
    // 情景1:如果是空链表
    if (!p)
    {
        *head = pNew;
        return 0;
    }

    // 情景2:如果链表中只有一个头结点
    if (memcmp(&(p->data), &pos, sizeof(DATA)) == 0) // 此时等价于 p->data == pos
    {
        // 插入到目标节点前面(头插法)
        pNew->next = *head;
        *head = pNew;

        // 插入到目标节点后面
        // p->next = pNew;
        return 0;
    }

    // 情景3:如果链表中有两个或以上节点
    while (p)
    {
        if (memcmp(&(p->data), &pos, sizeof(DATA)) == 0) // 此时等价于 p->data == pos
        {
            // 插入到目标节点前面
            pNew->next = p;
            q->next = pNew;
            return 0;
        }
        q = p;
        p = p->next;
    }
    
    // 情景4:找不到目标节点的位置(就执行尾插法)
    q->next = pNew;

    return 0;
}

单链表删除节点


/**
 * 删除链表中节点值为data的节点
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param data 待删除节点数据
 */
int slist_delete(NODE **head,DATA data)
{
    NODE *p = *head,*q = NULL;
    // 情景1:空链表
    if(!p)
        return -1;
    // 情景2;只有一个节点
    if(memcmp(&(p->data),&data,sizeof(DATA))==0)
    {
        *head = p->next; // p -> next = NULL;
        free(p);
        return 0;
    }
    // 情景3:超过两个节点的链表
    while(p)
    {
         if(memcmp(&(p->data),&data,sizeof(DATA))==0)
         {
             q->next = p->next;
             free(p);
             return 0;
         }
         q = p;
         p = p->next;
    }
    return -1;
}

注意:删除链表的节点并不意味着释放其内存,而是将其剔除出链表

链表节点数据的修改

修改链表中的数据(不改变链表结构,只修改节点中的数据)


/**
 * 根据数据查询节点(一般给修改用)
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param data 需要查询节点的数据
 * 
 * @return 返回查询到的节点
 */
NODE* slist_find(const NODE* head,DATA data)
{
    const NODE* p = head;

    while (p)
    {
        if(memcmp(&(p->data),&data,sizeof(DATA))== 0)
        {
            return (NODE*)p;
        }
        p = p->next;
    }
    
    return NULL;  
}


/**
 * 更新链表节点数据old位newdata
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 * @param old 旧数据
 * @param newdata 新数据
 */
int slist_update(const NODE* head,DATA old,DATA newdata)
{
    NODE* p = NULL;

    if(!(p = slist_find(head,old)))
        return -1;
    // 更新数据
    p->data = newdata;

    return 0;
}

单链表的遍历

遍历的意思就是逐个访问每一个节点,对于线性表而言,由于路径唯一的选择就是从头走到尾。


/**
 * 遍历链表数据
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 */
void slist_showAll(const NODE *head)
{
    // 首先使用一个变量来接收参数
    const NODE* p = head;

    while(p)
    {
        printf("%d\t",p->data);
        p = p->next; // 循环的最后一次:p = p->next = NULL
    }
    printf("\n");
}

单链表的销毁

由于链表中的各个节点被离散地分布在各个随机的内存空间,因此销毁链表必须遍历每一个节点,释放每一个节点。


/**
 * 回收链表
 * @param head 指向头指针变量的地址,用来接收首节点地址(头结点)
 */
void slist_destroy(NODE **head)
{
    NODE *p = *head,*q = NULL;
    while (p)
    {
        q = p;
        p = p->next;
        free(q);
    }
    *head = NULL;
}

标签:NODE,head,单链,next,链表,算法,数据结构,data,节点
From: https://blog.csdn.net/Are_pro_bald/article/details/144991496

相关文章

  • 基于决策树的机器学习算法实现足球比赛预测分析推荐
    决策树是一种常用的机器学习算法,它可以用于分类和回归任务。在足球比赛中预测“大小球”(即比赛的总进球数是否超过某个阈值)可以看作是一个分类问题。以下是一个使用决策树预测足球大小球的代码实现流程解析:1.数据准备首先,需要准备训练和测试数据集。这些数据集应该包含与比......
  • 自定义加密算法
    常见的哈希算法如Caesar,Base64,MurmurHash等已经被安全研究人员盯上了,经常使用这些算法作为特征定位恶意软件,因此最好使用自定义算法或不常见算法。base58加密cmd.exe#include<winsock2.h>#include<string.h>#include<stdio.h>#include<stdlib.h>constchar*const......
  • 椭圆曲线ECC算法
    基于“单向”数学问题,在一个方向上很简单,但在另一个方向上很困难,例如RSA是分解素数,ECC则是计算y2=x3+ax+bC语言在不使用第三方库情况下实现ECC算法比较麻烦,这里使用python和第三方库tinyec实现ECC算法安装库sudopip3installpycryptodomesudopip3installtinye......
  • 卡尔曼滤波(Kalman Filter) 从理论到实战详解 附算法源码
    目录一、卡尔曼滤波的引入二、状态观测器三、最优状态估计四、最优状态估计算法和方程五、热成像仪使用卡尔曼滤波器案例一、卡尔曼滤波的引入卡尔曼滤波用于优化估算我们感兴趣的量,当这些量无法直接测量但可以间接测量的时候,他们还用于估算系统状态,通过组合各种可能......
  • 算法基础 -二叉树遍历
    文章目录1.二叉树概念2.层序遍历2.1.复杂度2.2.示例12.3.示例23.层次遍历23.1.层次遍历规则3.2.层次遍历举例3.3.层次遍历代码4.先序遍历4.1.先序遍历规则4.2.先序遍历举例4.3.先序遍历代码(递归)4.4.先序遍历代码(非递归)5.中序遍历5.1.中序遍历规则5.2.......
  • 代码随想录算法训练营第五十六天|KM108.冗余连接|KM109.冗余连接Ⅱ
    108.冗余连接本题光看题目没理解具体什么意思;看了题解有点明白了;(个人觉得还是力扣的题目描述比较容易理解)题目意思:大概就是加一条边使树结构有环,然后再环中去掉一条边(如果环中多条边可取,则去掉最后一条边),仍然变成一颗树结构;思路:观察两个节点是否再一个集合,如果不在,也可以将......
  • 高效驰骋棋盘:马踏棋盘算法优化攻略与实践
    文章目录......
  • 【优选算法】Bit-Samurai:位运算的算法之道
    文章目录1.常见位运算总结1.1基础位运算符号1.2给一个数n,确定它的二进制表示中的第x位是0还是11.3将一个数n的二进制表示的第x位修改成11.4将一个数n的二进制表示的第x位修改成01.5位图的思想1.6提取一个n二进制表示中最右侧的11.7干掉一个数......
  • 代码随想录算法训练营第二十八天-贪心算法-122. 买卖股票的最佳时机II
    有奇妙的解法分析要获得利润,就是当天卖出前一天买入的的利润,也就是当天价格减去前一天的价格通过这样的运算,可以得到一个新的序列,这个序列就是上一道53的最大子序和的应用了而且把这些子序和中所有正数值都加到一起就是最大利润了#include<iostream>#include<vector>c......
  • 双指针算法专题
    目录1.移动零1.1算法原理1.2算法代码 2.复写零2.1算法原理  2.2算法代码3.快乐数3.1算法原理3.2算法代码4.盛水最多的容器4.1算法原理 4.2算法代码5.有效三角形的个数5.1算法原理5.2算法代码6. 剑指offer:和为s的两个数(原)6.1算法......