前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Java 数据结构与算法》第1章:链表

《Java 数据结构与算法》第1章:链表

作者头像
小傅哥
发布2022-12-12 21:28:22
5430
发布2022-12-12 21:28:22
举报

作者:小傅哥 博客:https://bugstack.cn

?沉淀、分享、成长,让自己和他人都能有所收获!? ?

  • 一、前言
  • 二、链表数据结构
  • 三、链表分类类型
    • 1. 单向链表
    • 2. 双向链表
    • 3. 循环链表
  • 四、实现一个链表
    • 1. 链表节点
    • 2. 头插节点
    • 3. 尾插节点
    • 4. 拆链操作
    • 5. 删除节点
  • 五、链表使用测试
  • 六、常见面试问题

一、前言

链表的历史

于1955-1956年,由兰德公司的Allen Newell、Cliff Shaw和Herbert A. Simon开发了链表,作为他们的信息处理语言的主要数据结构。链表的另一个早期出现是由 Hans Peter Luhn 在 1953 年 1 月编写的IBM内部备忘录建议在链式哈希表中使用链表。

到 1960 年代初,链表和使用这些结构作为主要数据表示的语言的实用性已经很好地建立起来。MIT 林肯实验室的 Bert Green于 1961 年 3 月在 IRE Transactions on Human Factors in Electronics 上发表了一篇题为“用于符号操作的计算机语言”的评论文章,总结了链表方法的优点。1964 年 4 月,Bobrow 和 Raphael 的一篇评论文章“列表处理计算机语言的比较”发表在 ACM 通讯中。

二、链表数据结构

在计算机科学中,链表是数据元素的线性集合,元素的线性顺序不是由它们在内存中的物理地址给出的。它是由一组节点组成的数据结构,每个元素指向下一个元素,这些节点一起,表示线性序列。

在最简单的链表结构下,每个节点由数据和指针(存放指向下一个节点的指针)两部分组成,这种数据结构允许在迭代时有效地从序列中的任何位置插入或删除元素。

链表的数据结构通过链的连接方式,提供了可以不需要扩容空间就更高效的插入和删除元素的操作,在适合的场景下它是一种非常方便的数据结构。但在一些需要遍历、指定位置操作、或者访问任意元素下,是需要循环遍历的,这将导致时间复杂度的提升。

三、链表分类类型

链表的主要表现形式分为;单向链表、双向链表、循环链表,接下来我们分别介绍下。

1. 单向链表

单链表包含具有数据字段的节点以及指向节点行中的下一个节点的“下一个”字段。可以对单链表执行的操作包括插入、删除和遍历。

2. 双向链表

在“双向链表”中,除了下一个节点链接之外,每个节点还包含指向序列中“前一个”节点的第二个链接字段。这两个链接可以称为'forward('s')和'backwards',或'next'和'prev'('previous')。

3. 循环链表

在列表的最后一个节点中,链接字段通常包含一个空引用,一个特殊的值用于指示缺少进一步的节点。一个不太常见的约定是让它指向列表的第一个节点。在这种情况下,列表被称为“循环”或“循环链接”;否则,它被称为“开放”或“线性”。它是一个列表,其中最后一个指针指向第一个节点。

四、实现一个链表

像 Java 的源码中本身就提供了非常多的数据结构,包括我们所学习的链表 LinkedList 在日常的开发使用中也是非常频繁。

所以我们在学习的过程中,以使用 Java 程序员本身常用的语言来分析学习,并通过简化结构的方式把 LinkedList 手写实现,让读者更能方便的理解链表。

  • 源码地址:https://github.com/fuzhengwei/java-algorithms - Java 算法与数据结构
  • 源码详见:https://github.com/fuzhengwei/java-algorithms/data-structures/LinkedList.java

1. 链表节点

代码语言:javascript
复制
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    
    public Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
  • 链表的数据结构核心根基就在于节点对象的使用,并在节点对象中关联当前节点的上一个和下一个节点。通过这样的方式构建出链表结构。
  • 但也因为在链表上添加每个元素的时候,都需要创建新的 Node 节点,所以这也是一部分耗时的操作。

2. 头插节点

代码语言:javascript
复制
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++;
}
  • 头插的操作流程,先把头节点记录下来。之后创建一个新的节点,新的节点构造函数的头节点入参为null,通过这样的方式构建出一个新的头节点。
  • 原来的头结点,设置 f.prev 连接到新的头节点,这样的就可以完成头插的操作了。另外如果原来就没有头节点,设置设置为新的节点即可。最后记录当前链表中节点的数量,也就是你使用 LinkedList 获取 size 时候就是从这个值获取的。

3. 尾插节点

代码语言:javascript
复制
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++;
}
  • 尾差节点与头插节点正好相反,通过记录当前的结尾节点,创建新的节点,并把当前的结尾节点,通过 l.next 关联到新创建的节点上。同时记录 size 节点数量值。

4. 拆链操作

代码语言:javascript
复制
E unlink(Node<E> x) {
    final E element = x.item;
    final Node<E> next = x.next;
    final Node<E> prev = x.prev;
    if (prev == null) {
        first = next;
    } else {
        prev.next = next;
        x.prev = null;
    }
    if (next == null) {
        last = prev;
    } else {
        next.prev = prev;
        x.next = null;
    }
    x.item = null;
    size--;
    return element;
}
  • unlink 是一种拆链操作,只要你给定一个元素,它就可以把当前这个元素的上一个节点和一个节点进行相连,之后把自己拆除。
  • 这个方法常用语 remove 移除元素操作,因为整个操作过程不需要遍历,拆除元素后也不需要复制新的空间,所以时间复杂读为 O(1)

5. 删除节点

代码语言:javascript
复制
public boolean remove(Object o) {
    if (o == null) {
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null) {
                unlink(x);
                return true;
            }
        }
    } else {
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item)) {
                unlink(x);
                return true;
            }
        }
    }
    return false;
}
  • 删除元素的过程需要 for 循环判断比删除元素的值,找到对应的元素,进行删除。
  • 循环比对的过程是一个 O(n) 的操作,删除的过程是一个 O(1) 的操作。所以如果这个链表较大,删除的元素又都是贴近结尾,那么这个循环比对的过程也是比较耗时的。

五、链表使用测试

代码语言:javascript
复制
public static void main(String[] args) {
    List<String> list = new LinkedList<>();
    // 添加元素
    list.add("a");
    list.addFirst("b");
    list.addLast("c");
    // 打印列表
    list.printLinkList();
    // 头插元素
    list.addFirst("d");
    // 删除元素
    list.remove("b");
    // 打印列表
    list.printLinkList();
}

测试结果

代码语言:javascript
复制
目前的列表,头节点:b 尾节点:c 整体:b,a,c,
目前的列表,头节点:d 尾节点:c 整体:d,a,c,

Process finished with exit code 0
  • 按照我们的测试链表对数据的操作过程,从测试结果可以看到,已经满足了链表数据结构的使用。

六、常见面试问题

  • 描述一下链表的数据结构?
  • Java 中 LinkedList 使用的是单向链表、双向链表还是循环链表?
  • 链表中数据的插入、删除、获取元素,时间复杂度是多少?
  • 什么场景下使用链表更合适?

- END -


你好,我是小傅哥。一线互联网java 工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-07-25,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 bugstack虫洞栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、链表数据结构
  • 三、链表分类类型
    • 1. 单向链表
      • 2. 双向链表
        • 3. 循环链表
        • 四、实现一个链表
          • 1. 链表节点
            • 2. 头插节点
              • 3. 尾插节点
                • 4. 拆链操作
                  • 5. 删除节点
                  • 五、链表使用测试
                  • 六、常见面试问题
                  相关产品与服务
                  消息队列 TDMQ
                  消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                  http://www.vxiaotou.com