链表

单链表的基本设计及实际操作 单链表是我们学习数据结构 的最基本的内容 阅读详情

链表

链表中各对象按线性顺序排列。数组的线性顺序是由数组下标决定,链表的顺序是由各个对象中的指针决定。链表为动态集合提供一种简单而灵活的表示方法

双向链表的每个元素都是一个对象,每个对象有一个关键字key和两个指针:next和prev。对象中还可以包含其他辅助数据。设x为链表的一个元素,x.next指向它在链表中的后续元素,x.prev指向它的前驱元素。如果x.prev = NIL,则元素x没有前驱,因此是链表第一个元素,即链表头。如果x.next = NIL,则元素x没有后续,因此是链表的最后一个元素,即链表的尾。属性L.head指向链表第一个元素。如果L.head=NIL,则链表为空

链表可以有多种形式,可以是单链接的或双链接的,可以是已排序或未排序的,可以是循环的或非循环的。如果链表是单链接的,则省略每个元素的prev指针,如果链表是已排序的,则链表的线性顺序与链表中元素的关键字的线性顺序一致,最小元素就是表头元素,最大元素则为表尾元素。在循环链接表中,表头的prev指向表尾元素,表尾的next指向表头元素

链表的搜索

LIST-SEARCH(L, k)采用简单的线性搜索方法,用于查找链表L中第一个关键字为k的的元素,并返回该元素指针。如果链表中没有关键字k的对象,则该过程返回NIL,最坏情况下运行时间为θ(n)

LIST-SEARCH(L, k)
1   x = L.head
2   while x != NIL and x.key != k
3       x = x.next
4   return x

链表的插入

LIST-INSERT将已设置好关键字key的元素x,插入到链表的前端。运行时间为O(n)

LIST-INSERT(L,x)
1   x.next = L.head
2   if L.head != NIL
3       L.head.prev = x
4   L.head = x
5   x.prev = NIL

链表的删除

LIST-DELETE从链表中删除指定元素,时间复杂度为O(n)

LIST-DELETE(L, x)
1   if x.prev != NIL
2       x.prev.next = x.next
3   else L.head = x.next
4   if x.next != NIL
5       x.next.prev = x.prev

哨兵

哨兵是一个哑对象,其作用是简化边界条件处理。假设在链表L中设置一个对象L.nil,该对象代表NIL,但也具有和其他对象相同的各个属性。对链表中每一处对NIL的引用,都改为对哨兵L.nil的引用,这样便将一个常规的双向链表转变为一个有哨兵的双向循环链表,哨兵位于头部和尾部之间。属性L.nil.next指向表头,L.nil.prev指向表尾,表尾的next属性和表头的prev属性同时指向L.nil,因为L.nil.next指向表头,我们可以去掉属性L.head,并把对它的引用改为对L.nil.next的应用。一个空的链表只由一个哨兵构成,L.nil.next和L.nil.prev同时指向L.nil

哨兵基本不能降低数据结构相关操作的渐近时间界,但可以降低常数因子,在循环语句中使用哨兵在于可以是代码结构更简洁,而非提高速度。但哨兵应该被慎用,对于较短的链表而言哨兵所占的空间无疑是浪费

带哨兵链表的搜索

LIST-SEARCH(L,k)
1   x = L.nil.next
2   while x != L.nil and x.key != k
3       x = x.next
4   return x

带哨兵链表的插入

插入到链表前端

LIST-INSERT(L, x)
1   x.next = L.nil.next
2   L.nil.next.prev = x
3   L.nil.next = x
4   x.prev = L.nil

带哨兵链表的删除

LIST-DELETE(L, x)
1   x.prev.next = x.next
2   x.next.prev = x.prev

JDK中链表的实现

JDK中提供了丰富的链表逻辑实现,我们通过LinkedList来看一下JDK中对链表的具体操作

属性介绍

LinkedList包含三个属性

  • size:链表元素个数

  • first:链表头,数据类型为Node

  • last:链表尾,数据类型为Node

transient int size = 0;

/**
 * Pointer to first node.
 * Invariant: (first == null && last == null) ||
 *            (first.prev == null && first.item != null)
 */
transient Node<E> first;

/**
 * Pointer to last node.
 * Invariant: (first == null && last == null) ||
 *            (last.next == null && last.item != null)
 */
transient Node<E> last;

LinkedList中包含静态类Node的定义,Node也包含三个元素

  • item:数据对象

  • next:下一个节点

  • prev:上一个节点

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

初始化

LinkedList提供了两个构造函数,LinkedList()和LinkedList(Collection<? extends E> c),前者构造出一个空的链表,后者可以使用用户设置的集合元素构造一个链表

public LinkedList() {
}

// 使用指定集合初始化构造链表
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}

查询指定元素

LinkedList提供了两个方法搜索链表中的元素

查询指定元素第一次出现的索引

indexOf(Object o)方法提供了查询参数o在链表中第一次出现的索引

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}
查询指定元素最后一次出现的索引

lastIndexOf(Object o)方法提供查询参数o在链表中最后一次出现的索引

public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

插入元素

LinkedList提供了多种方式插入元素,包括插入到头部、插入到尾部、及指定插入位置、批量插入等,我们这里介绍几种插入方式

插入元素到链表末尾

addLast(E e)方法将元素插入到链表末尾,但插入元素到链表末尾的方法有很多。addLast方法也是调用linkLast方法实现往链表尾部插入元素的,其实很多暴露给用户插入元素到链表末尾的方法都是调用的linkLast方法。linkLast方法插入元素的过程与上述算法思想类似

// 插入元素到链表末尾
public boolean add(E e) {
    linkLast(e);
    return true;
}


void linkLast(E e) {
    final Node<E> l = last;
    // 构建节点,第一个参数是新节点的上一个节点,在构造方法中会关联上新节点。第二个参数为下一个节点,置空
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    // 如果最后一个节点为空,则表示此链表为空
    if (l == null)
        // 设置链表头
        first = newNode;
    else
        // 设置最后一个节点的下一个节点为加入的新节点
        l.next = newNode;
    size++;
    modCount++;
}
插入元素到链表头

addFirst(E e)提供插入元素到链表头的功能,实现逻辑类似与插入元素到链表尾部,这里不再赘述

public void addFirst(E e) {
    linkFirst(e);
}

private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

删除元素

LinkedList提供了多种删除元素的方法,既可以从头删除,也可从尾删除,及删除指定元素等

从头部删除元素

removeFirst()提供从头部删除元素

// 从头删除元素,并返回删除元素
public E removeFirst() {
    final Node<E> f = first;
    // 空链表抛出异常
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}


private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}
从尾删除元素

removeLast()提供从尾部删除元素,其实现与从头部删除元素类似

public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

有根树的表示

我们可以使用链表的结构表示有根树

二叉树

二叉树中每个节点包含属性p、left和right存放指向父节点、左孩子、右孩子的指针。如果x.p=NIL,则x是根节点,若x.left=NIL,则x没有左孩子,右孩子同理。属性T.root指向整棵树T的根节点。若T.root=NIL,则为空树

无限分支有根树

二叉树表示方法可以推广到每个节点孩子树至多为常数k的任意类型的树,只需将left、right属性用child1,child2,…,childk代替,但这种方式可能会造成空间的浪费,因为有的节点可能只有一个孩子节点。并且无法解决无限分支的树的表示

通过左孩子右兄弟表示法可以表示无限分支有根树,并且只需O(n)的存储空间。每个节点包含一个父节点指针p,T.root指向树T的根节点,每个节点还包含一下两个指针

  • x.left-child指向节点x最左侧孩子节点

  • x.right-sibling指向x右侧相邻的兄弟节点

如果节点没有孩子节点,则x.left-child=NIL;如果节点x是其父节点的最右孩子,则x.right-sibling=NIL

树的其他表示方法

之前我们在二叉堆算法时使用过数组来表示一颗树

链表详解(单链表、双向链表、链表逆序) 单链表 想了解单链表的一些概念可以看我另一篇博客: 链表基础 单链表结构体定义 //单链表的结构定义 typedef struct LNode { int data; //data存放结点数据域 struct LNode *next; //指向后继结点的指针 }LNode; 单链表的建立 (1)定义单链表的数据结构; (2)读取数据; (3)生成新结点; (4)将数据存入结点的成员变量中; (5)将新结点连接到表头或表尾。 重复(2)~(5)直到输入结束。 //头插法建立单链表 void c 阅读详情

相关推荐

C++实现单向链表,双向链表,单向环形链表,双向环形链表

本文讲解了链表的四种类型以及数据结构,同时给出了四种链表的C++代码实现。

ttug22的博客 1384

c语言void append,C++指针编程你要小心的陷阱——如何优雅的释放指针void*

指针操作是C++开发中必备技能。尽管C++11开始引入了智能指针以缓解普通指针的滥用,但是某些场合必须使用普通指针。释放指针在C/C++编程中非常重要,一般推荐释放指针后立即将指针设置为null,防止出现低级的野指针问题(只能避免低级别的野指针)同时方便调试。一、C语言时代在C语言编程中,我们由于没有C++模板,函数重载功能,所以一般定义一个统一的宏来用于释放指针。// 删除指针 #define ...

weixin_29206669的博客 687

CATIA_DELMIA_V5R2019安装包下载及安装教程破解

CATIA_V5R2019/DELMIA_V5R2019安装包下载及安装教程破解

一只机械工装熊的博客 8412

教务管理系统

java编写的简单的教务系统,数据库采用mysql。主要功能是学生选课、教师成绩录入、管理员管理老师和学生等。 文件:n459.com/file/25127180-476895768 以下内容无关: -------------------------------------------分割线--------------------------------------------- 犹豫了几天,看了很多大牛写的关于c语言链表,感触很多,终于下定决心,把自己对于链表的理解随之附上,可用与否,自行裁夺。由于作者水

gumenghua_com1的博客 1340

一篇文章将三极管讲透:三极管从原理到应用,从参数到特性,从入门到精通

晶体三极管(Transistor)是一种半导体电子器件,也是电子工程中最基本的元件之一。它有三个区域,分别是P型半导体,N型半导体和P型半导体,从而形成PNP型晶体三极管或者NPN型晶体三极管。

学习帮的博客 9万+

如何理解Fourier Neural Operator (FNO)

2022年Nvidia发表在arXiv上的的FourcastNet作为几乎是最早的一篇气象大模型的工作(之前Google Research的MetNet主要还是对于降水的预报)。除了Transformer的backbone之外,使用到了2021年发表在ICLR上的Fourier Neural Operator。为了更好的理解模型的理论基础,可以详细学习一下以及他的前期工作GNOFNO作者对这两篇文章的解析blog。

momo的博客 1万+

轻松学会C语言链表系列之尾添加

#include<stdio.h> #include<stdlib.h> //节点结构体 struct Node{ int m; struct Node* pNext; }; //链表头尾指针 struct Node* g_pHead = NULL; struct Node* g_pEnd = NULL; //创建链表,在链表中增加一个数据(尾添加) void ...

小学生stage的博客 477

libopus 实现pcm 编码到opus

opus 是一种音频格式,常用于语音通话、视频会议中。最近做了个pcm 到opus 的编码,踩了不少坑,特此记录一下。

sinat_27720649的博客 9478

图解&代码实现:单向链表的创建(直接添加到链表的尾部,不考虑排序)

链表的基本介绍 链表是有序列表,链表的英文是LinkedList 链表的特点: 链表是以结点方式来存储的,链表是链式存储 链表中的每个结点都分data域和next域 data域 : 用来存放数据 next域:用来指向下一个结点 下面展示的是 链表再内存中的实际结构的布局图: 链表的各个结点不一定是连续存储的 链表分为带头结点的链表和不带头结点的链表,具体要不要带头结点,根据自...

Java码农ing的博客 589

库卡机器人负载设置和自动负载测算

1.1在专家或者以上的权限下—“投入运行”——“工具/基座标管理”—“工具/工件”—添加1.2根据当前的工具所在的tool选择指定工具号;若是没有建立tool需要先建立建立方法看1.3负载数据的填写质量:抓手自重+产品重量总和(需要小于额定负载值)X、Y、Z :总质量的重心位置(基于法兰盘中心的位置计算,如下图的X_FLANGE、Y_FLANGE、Z_FLANGE)【此数据可以根据仿真或者CAD数据得出】A、B、C:是总质量绕着法兰盘中心所安装的角度位置 【此数据可以根据仿真或者CAD数据得出】

weixin_43462131的博客 3117

axi ps读写pl_PL读写DDR:Datamover能干什么

最近发现工程项目中一直在用AXI-DMA。这玩意儿搬数据倒是没问题,就是用axi-lite配置起来非常反人类。。。简单的办法其实是用datamover ip核。这个ip核能干嘛呢。准备写个文章解析一下。由于好多feature没用过,所以仅仅看文档可能理解有误,欢迎指出错误。首先扫个盲:MM2S指的是Memory map to stream. 读DDR. S2MM指的是steam to memory...

weixin_35908791的博客 1598

【Leetcode】反转链表 合并链表 相交链表 链表的回文结构

【Leetcode】反转链表 合并链表 相交链表 链表的回文结构

旋律 | 话语 | 色彩 | 情景 2384

【Agent】AI Agent架构趋势及演进

本文系统介绍了AI Agent架构的发展趋势和技术框架,涵盖单Agent与多Agent系统设计、提示词工程与上下文管理、SpringAI开发框架等关键技术。重点阐述了AI原生应用参考架构,包括Nacos配置管理、Higress AI网关、RocketMQ会话状态管理等核心组件解决方案。同时深入探讨了AI应用可观测性体系,包含OpenTelemetry全链路追踪、关键指标监控以及基于LoongSuite开源工具的数据采集评估方法,为构建高效可靠的AI应用提供了全景式技术参考。

一个内容严谨、方向前沿、极具实践价值的AI大模型技术分享站 1107

数据结构 —— java 单链表、双端链表、双向链表、无序链表、有序链表

文章目录链表不同链表的特点单链表(单端链表)双端链表双向链表 链表 上面是一个单链表的存储原理图,head为头节点,它不存放任何的数据,只是充当一个指向链表中真正存放数据的第一个节点的作用,而每个节点中都有一个next引用,指向下一个节点,就这样一节一节往下面记录,直到最后一个节点,其中的next指向null。 不同链表的特点 普通链表: 节点类保留下一节点的引用。链表类只保留头节点的引用,只能...

猎人在吃肉 4539

YOLOv13添加OREPA模块 准确率提升10.56个点,map提升11.87个点。小目标轻量化

本文提出了一种新的OREPA(Optimal Recursive Edge-Preserving Attention)模块,通过结合递归和边缘保留注意力机制,显著提升了目标检测模型的性能。实验结果表明,添加OREPA模块后,准确率提升了10.56个百分点,mAP提升了11.87个百分点。OREPA模块包含三个核心组件:OREPA1处理特征图并保留边缘信息,Bottleneck_OREPA作为瓶颈模块降低计算量,OREPA类通过堆叠多个Bottleneck_OREPA增强网络表达能力。文章详细介绍了如何将OR

FJN110的博客 2762

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

数据结构01- 单链表、双端链表、双向链表、无序链表、有序链表

数据结构01- 单链表、双端链表、双向链表、无序链表、有序链表 0、节点 节点 是数据结构中的基础,是 构成复杂数据结构的基本组成单位。 public class Node { public long data; public Node next; public Node(long value) { this.data = value; } } 1、链表的定义 链表: 通常由一连串 节点 组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向 上一个 或 下一个

琦彦 7077

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

LeetCode链表(链表操作,反转链表,奇偶链表,排序链表)

本篇文章更注重于链表自身的特点。链表是一种通过指针串联在一起的线性结构。链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。链表的入口节点称为链表的头结点也就是head。(1)单链表每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。(2)双链表每一个节点有两个指针域。

ji_meng的博客 1820

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

详解链表oJ<反转链表,链表的中间节点及链表的回文>

链表OJ

qq_75270497的博客 1039

链表小总结

/ 结点的值int val;// 下一个结点// 节点的构造函数(无参)// 节点的构造函数(有一个参数)// 节点的构造函数(有两个参数)

m0_58257626的博客 1660

Python与链表(含链表概念、链表定义、链表操作、链表相关例题)

Python与链表、链表基础概念、链表与数组区别、链表分类、链表增删改查、链表相关例题

Bala_lala的博客 1404

反转链表、旋转链表算法总结

1. 反转链表(力扣:206) 2. 反转链表 II(力扣:92) 3. 旋转链表(力扣:61)

卜大爷的博客 2万+

链表(单链表、双链表、单向环形链表)

Josephu问题为:设编号为1,2,...n的n个人围坐一圈,约定编号为k(1

weixin_52347460的博客 1173

数据结构之链表操作详解与示例(反转链表,合并链表,旋转链表,对链表排序)

本文介绍了链表的四种常见操作:反转链表、合并链表、旋转链表和对链表排序。每种操作都有其特定的应用场景和算法步骤,通过示例代码展示了如何实现这些操作。理解和掌握这些链表操作对于深入理解数据结构和算法至关重要。

qq_35320456的博客 1348

链表学习反转链表

链表是一种线性数据结构,其中元素不是连续存储在内存中的,而是通过指针连接起来。每个节点包含两部分:数据域和指向下一个节点的指针域。链表分为单向链表和双向链表,本文主要讨论单向链表的反转。

一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身"农奴"把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟! 1248
上一篇: 栈和队列
下一篇: 默认标签解析
炒栗子不加糖
博客等级 码龄10年 68粉丝 123原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值