【数据结构与算法】5、循环链表、约瑟夫问题、静态链表

简介: 【数据结构与算法】5、循环链表、约瑟夫问题、静态链表


一、单向循环链表

🌿 单向循环链表在单链表的基础上,尾节点的 next 指向头节点

(1) add()

🌿 只用考虑添加头节点的情况

🌿 要考虑一个节点都没有,插入第一个节点的情况

@Override
  public void add(int index, E element) {
      rangeCheck4Add(index);
      if (index == 0) { // 把元素插入到头节点的位置
          Node<E> head = new Node<>(element, first);
          // 拿到尾节点
          Node<E> tail = (size == 0) ? head : node(size - 1);
          tail.next = head;
          first = head; // 头指针指向头节点
      } else {
          // 拿到【index - 1】位置的节点
          Node<E> prev = node(index - 1);
          prev.next = new Node<>(element, prev.next);
      }
      size++;
  }

🌿 假如链表中一个节点都没有的时候,插入的第一个节点的 next 指向它本身

(2) remove()

🌿 只用考虑删除头节点的情况

🌿 假如只有一个节点,且要删除该节点的时候,只需要让头指针 first 指向为 null 即可

@Override
  public E remove(int index) {
      rangeCheck(index);
      Node<E> node = first;
      if (index == 0) { // 删除头节点
          if (size == 1) {
              first = null;
          } else {
              // 拿到尾节点
              // node() 方法中使用到了 first 头指针
              Node<E> tail = node(size - 1);
              first = node.next;
              // 尾节点指向头节点
              tail.next = first;
          }
      } else {
          Node<E> prev = node(index - 1);
          node = prev.next;
          prev.next = node.next;
      }
      size--;
      return node.element;
  }

🌿 当要删除的链表中只有一个节点的时候,直接让头指针 first 等于 null

(3) 单向循环链表特点

🌱 相比单链表的尾节点的 next 指向 null 而言,单向循环链表的尾节点的 next 是指向头节点

🌱 这样一来的话,从单向循环链表的任意一个节点出发都可以获取到整个链表中的全部节点

二、双向循环链表

🍀 双向循环链表本身也是双向链表

🍀 双向循环链表的头节点的 prev 指向尾节点

🍀 双向循环链表的尾节点的 next 指向头节点

代码太恶心了: 🤮🤧

/**
 * 双向循环链表
 */
public class DoubleCircleLinkedList<E> extends AbstractList<E> {
    private Node<E> first; // 头指针
    private Node<E> last; // 尾指针
    @Override
    public E get(int index) {
        return node(index).element;
    }
    @Override
    public E set(int index, E element) {
        Node<E> node = node(index);
        E old = node.element;
        node.element = element;
        return old;
    }
    @Override
    public void add(int index, E element) {
        rangeCheck4Add(index);
        if (size == 0 || (first == null && last == null)) { // 添加第一个节点
            Node<E> head = new Node<>(element, null, null);
            head.prev = head;
            head.next = head;
            first = last = head;
        } else {
            if (index == size) { // 往最后插入新节点
                Node<E> oldLast = last;
                last = new Node<>(element, oldLast, null);
                oldLast.next = last;
                // 新节点的 next 指向头节点
                last.next = first;
                // 头节点的  prev 指向新节点
                first.prev = last;
            } else {
                Node<E> next = node(index);
                Node<E> prev = next.prev;
                Node<E> newNode = new Node<>(element, prev, next);
                next.prev = newNode;
                prev.next = newNode;
                if (index == 0) {
                    first = newNode; // 头指针指向新节点
                }
            }
        }
        size++;
    }
    @Override
    public E remove(int index) {
        rangeCheck(index);
        Node<E> node = null;
        if (size == 1) { // 删除的链表中只有一个节点的时候
            node = first;
            first = null;
            last = null;
        } else {
            node = node(index);
            Node<E> prev = node.prev;
            Node<E> next = node.next;
            prev.next = next;
            next.prev = prev;
            if (index == 0) { // 删除头节点
                first = next;
            }
            if (index == (size - 1)) { // 删除尾节点
                last = prev;
            }
        }
        size--;
        return node.element;
    }
    @Override
    public int indexOf(E element) {
        return 0;
    }
    @Override
    public void clear() {
        size = 0;
        first = null;
        last = null;
    }
    private static class Node<E> {
        E element;
        Node<E> prev;
        Node<E> next;
        Node(E element, Node<E> prev, Node<E> next) {
            this.element = element;
            this.prev = prev;
            this.next = next;
        }
        @Override
        public String toString() {
            if (prev == null) {
                return "null" + " ←【" + element + "】→ " + next.element + ", ";
            }
            if (next == null) {
                return prev.element + " ←【" + element + "】→ " + "null";
            }
            return prev.element + " ←【" + element + "】→ " + next.element + ", ";
        }
    }
    /**
     * 根据索引找节点
     */
    private Node<E> node(int index) {
        rangeCheck(index);
        if (index < (index >> 1)) { // 找左边的节点
            Node<E> node = first;
            for (int i = 0; i < index; i++) {
                node = node.next;
            }
            return node;
        } else {
            Node<E> node = last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
            return node;
        }
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("{size=").append(size).append(", [");
        Node<E> moveNode = first;
        for (int i = 0; i < size; i++) {
            sb.append(moveNode);
            moveNode = moveNode.next;
        }
        sb.append("]}");
        return sb.toString();
    }
}

三、约瑟夫问题(Josephus Problem)

❄️ 在双向循环链表的基础上增加1个成员变量、3个方法,以便解决约瑟夫问题

❄️ current 成员变量:用于指向某个节点

❄️void reset():让 current 指向头节点 first

❄️ E next():让 current 往后走一步(current = current.next)

❄️E remove():删除 current 指向的节点,删除成功后让 current 指向下一个节点,并返回被删除的节点值

四、静态链表

🌱 前面所学习的链表,是依赖于指针(引用)实现的

🌱 有些编程语言是没有指针的,比如早期的 BASIC、FORTRAN 语言

🌱 没有指针的情况下,如何实现链表?

  • 可以通过数组来模拟链表,称为静态链表
  • 数组的每个元素存放 2 个数据:① 值、② 下个元素的索引
  • 数组 0 位置存放的是头节点信息

❓ 如果数组的每个元素只能存放 1 个数据呢 ❓

🌱 使用 2 个数组,1 个数组存放索引关系,1 个数组存放值

基于这种思路可以对 ArrayList 做进一步优化


🌿如有错误,请不吝赐教🌿

相关文章
|
29天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
53 4
|
22天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
76 4
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
29天前
|
算法
数据结构之购物车系统(链表和栈)
本文介绍了基于链表和栈的购物车系统的设计与实现。该系统通过命令行界面提供商品管理、购物车查看、结算等功能,支持用户便捷地管理购物清单。核心代码定义了商品、购物车商品节点和购物车的数据结构,并实现了添加、删除商品、查看购物车内容及结算等操作。算法分析显示,系统在处理小规模购物车时表现良好,但在大规模购物车操作下可能存在性能瓶颈。
47 0
|
1月前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
56 0
|
2月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
92 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
2月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
40 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
2月前
|
搜索推荐 算法
数据结构与算法学习十四:常用排序算法总结和对比
关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
29 0
数据结构与算法学习十四:常用排序算法总结和对比
下一篇
DataWorks