【数据结构与算法】5.详解双向链表的基本操作(Java语言实现)

简介: 【数据结构与算法】5.详解双向链表的基本操作(Java语言实现)


0. 前言

上一篇【数据结构与算法】4.自主实现单链表的增删查改 我们自主实现了单链表的操作,在Java的集合类中LinkedList底层实现是无头双向循环链表。所以今天我们模拟LinkedList的实现。

1. 双链表的定义

学习双链表之前,做个回顾。

单链表的特点:

  1. 我们可以轻松的到达下一个节点,但是回到前一节点是很难的。
  2. 只能从头遍历到尾或者从尾遍历到头(一般是从头到尾)

双链表的特点:

  1. 每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现起来要困难一些
  2. 相对于单向链表, 必然占用内存空间更大一些.
  3. 既可以从头遍历到尾, 又可以从尾遍历到头

双链表的定义:

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

指针域(prev):用于指向当前节点的直接前驱节点;

数据域(data):用于存储数据元素;

指针域(next):用于指向当前节点的直接后继节点。

2. LinkedList 模拟实现

2.1 接口

public interface IList {
    //头插法
    public void addFirst(int data);
    //尾插法
    public void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key);
    //删除第一次出现关键字为key的节点
    public void remove(int key);
    //删除所有值为key的节点
    public void removeAllKey(int key);
    //得到单链表的长度
    public int size();
    // 清空链表
    public void clear();
    // 打印链表
    public void display();
}

2.2 定义双向链表类

static class ListNode {
  public int val; // 数值域 - 存放当前节点的值
  public ListNode next; // next域 指向下一个节点
  public ListNode prev; // prev域 指向上一个节点
  public ListNode(int val) {
    this.val = val;
  }
}

2.3 定义两个指针,分别指向头节点和尾节点

// 链表的属性 链表的头节点
public ListNode head; 
// 链表的属性 链表的尾节点
public ListNode last;

2.4 头插法

  1. 判断链表是否为空,如果为空,将新节点的node设置为头节点,将新节点的node设置为尾节点
head = node;
last = node;
  1. 如果链表不为空,将新节点的nodenext域设置为头节点,将当前头节点的prev设置为新节点的node,更新头节点为新节点的node
node.next = head;
head.prev = node;
head = node;

动画演示:

代码:

/**
* 头插法
* @param data
*/
@Override
public void addFirst(int data) {
  ListNode node = new ListNode(data);
  if (head == null) {
    head = node;
    last = node;
  }else {
    node.next = head;
    head.prev = node;
    head = node;
  }
}

2.5 尾插法

  1. 判断链表是否为空,如果为空,将新节点的node设置为头节点,将新节点的node设置为尾节点
head = node;
last = node;
  1. 如果链表不为空,将最后一个节点lastnext域指向新节点,新节点的prev域指向最后一个节点,更新尾节点为新节点
last.next = node;
node.prev = last;
last = node;

动画演示:

代码:

/***
     * 尾插法
     * @param data
     */
    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }

2.6 指定位置插入元素

  1. 判断索引idnex是否合法,如果不合法则抛出异常。
if (index < 0 || index > size()) {
  throw new IndexException("index不合法:" + index);
}
  1. 判断链表是否为空,如果为空则将新节点设置为头节点和尾节点
if (head == null) {
  head = node;
    last = node;
    return;
}
  1. 如果索引index == 0,则使用头插法,如果索引index = 链表长度,则使用尾插法
if (index == 0) {
    addFirst(data);
  return;
}
if (index == size()) {
    addLast(data);
  return;
}
  1. 找到索引节点(当前节点)
private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
  1. 将新节点的next域指向当前节点,新节点的prev域指向当前节点的前一个节点,当前节点的prev域指向新节点,更新新节点的上一个节点的next域指向当前节点。
ListNode cur = findIndex(index);
node.next = cur;
node.prev = cur.prev;
cur.prev = node;
node.prev.next = node;

动画演示:

2.7 查找指定元素

  1. 从头节点开始遍历链表,如果当前节点的值与要查找的key相等,则返回ture,如果不相等则移动下一个节点继续查找。如果遍历完链表都没有找到key则返回false.

代码:

@Override
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

2.8 删除指定元素

  1. 从头节点开始遍历链表,找到要删除的节点
  2. 情况一:删除的节点为头节点,更新头节点为下一个节点,更新下一个节点的prev域置为空。

  3. 情况二:链表中只有一个元素,且正好要删除这个元素。
  4. 情况三:删除的节点为尾节点,更新尾节点为当前节点的上一个节点,上一个节点的next域置为空

  5. 情况四:删除中间节点,当前节点的上一个节点的next域指向当前节点的下一个节点,更新下一个节点的prev域指向当前节点的上一个节点

  6. 删除了节点就结束方法的执行

代码:

@Override
    public void remove(int key) {
         ListNode cur = head;
         while (cur != null) {
             if (cur.val == key) { // 找到要删除的元素了
                 if (cur == head) { // 删除头节点
                     head = head.next;
                     if (head != null) {
                         head.prev = null;
                     } else { // 链表中只有一个元素,且这个正好删除这个元素
                         last = null;
                     }
                 } else { // 删除中间节点
                     cur.prev.next = cur.next;
                     if (cur.next != null) {
                        cur.next.prev = cur.prev;
                     } else {
                         // 删除尾节点
                         last = cur.prev;
                     }
                 }
                 return;// 删除了节点就结束方法
             }
             cur = cur.next;
         }
    }

2.9 删除链表中所有指定元素

从头节点遍历链表,与删除指定元素的方式一样,删除节点后继续遍历链表,直到遍历完链表,删除所有指定的元素即可。

代码:

@Override
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) { // 找到要删除的元素了
                if (cur == head) { // 删除头节点
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else { // 链表中只有一个元素,且这个正好删除这个元素
                        last = null;
                    }
                } else { // 删除中间节点
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;
                    } else {
                        // 删除尾节点
                        last = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }

2.10 统计链表元素个数

代码:

@Override
    public int size() {
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

2.11 清空链表

将头节点和尾节点置为空,没有引用指向直接被JVM回收

@Override
    public void clear() {
        head = null;
        last = null;
    }

2.12 打印链表

@Override
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

2.13 测试

public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        // 头插法
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(2);
        myLinkedList.addFirst(3);
        // 打印链表
        myLinkedList.display();
        System.out.println("=========");
        // 尾插法
        myLinkedList.addLast(4);
        myLinkedList.addLast(5);
        myLinkedList.addLast(6);
        // 打印链表
        myLinkedList.display();
        System.out.println("=========");
        // 在4 位置插入7
        myLinkedList.addIndex(4,7);
        // 打印链表
        myLinkedList.display();
        System.out.println("=========");
        // 查找元素 7 8
        System.out.println(myLinkedList.contains(7));
        System.out.println(myLinkedList.contains(8));
        System.out.println("=========");
        // 删除3 6 4
        myLinkedList.remove(3);
        myLinkedList.display();
        System.out.println("=========");
        myLinkedList.remove(6);
        myLinkedList.display();
        System.out.println("=========");
        myLinkedList.remove(4);
        myLinkedList.display();
        System.out.println("=========");
        // 删除全部的2
        myLinkedList.addLast(2);
        myLinkedList.addLast(2);
        myLinkedList.addLast(2);
        myLinkedList.display();
        myLinkedList.removeAllKey(2);
        myLinkedList.display();
        System.out.println("=========");
        // 统计个数
        System.out.println(myLinkedList.size());
        System.out.println("=========");
        // 清空链表
        myLinkedList.clear();
        myLinkedList.display();
        System.out.println("=========");
        
        // 统计个数
        System.out.println(myLinkedList.size());
    }
}
// 运行结果
3 2 1 
=========
3 2 1 4 5 6 
=========
3 2 1 4 7 5 6 
=========
true
false
=========
2 1 4 7 5 6 
=========
2 1 4 7 5 
=========
2 1 7 5 
=========
2 1 7 5 2 2 2 
1 7 5 
=========
3
=========
=========
0

3.代码

MyLinkedList类:

public class MyLinkedList implements IList{
    static class ListNode {
        public int val; // 数值域 - 存放当前节点的值
        public ListNode next; // next域 指向下一个节点
        public ListNode prev; // prev域 指向上一个节点
        public ListNode(int val) {
            this.val = val;
        }
    }
    // 链表的属性 链表的头节点
    public ListNode head;
    // 链表的属性 链表的尾节点
    public ListNode last;
    /**
     * 头插法
     * @param data
     */
    @Override
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        }else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }
    /***
     * 尾插法
     * @param data
     */
    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        } else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }
    @Override
    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) {
            throw new IndexException("index不合法:" + index);
        }
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
            return;
        }
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode cur = findIndex(index);
        node.next = cur;
        node.prev = cur.prev;
        cur.prev = node;
        node.prev.next = node;
    }
    private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    @Override
    public void remove(int key) {
         ListNode cur = head;
         while (cur != null) {
             if (cur.val == key) { // 找到要删除的元素了
                 if (cur == head) { // 删除头节点
                     head = head.next;
                     if (head != null) {
                         head.prev = null;
                     } else { // 链表中只有一个元素,且这个正好删除这个元素
                         last = null;
                     }
                 } else { // 删除中间节点
                     cur.prev.next = cur.next;
                     if (cur.next != null) {
                        cur.next.prev = cur.prev;
                     } else {
                         // 删除尾节点
                         last = cur.prev;
                     }
                 }
                 return;
             }
             cur = cur.next;
         }
    }
    @Override
    public void removeAllKey(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) { // 找到要删除的元素了
                if (cur == head) { // 删除头节点
                    head = head.next;
                    if (head != null) {
                        head.prev = null;
                    } else { // 链表中只有一个元素,且这个正好删除这个元素
                        last = null;
                    }
                } else { // 删除中间节点
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        cur.next.prev = cur.prev;
                    } else {
                        // 删除尾节点
                        last = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }
    @Override
    public int size() {
        int count = 0;
        ListNode cur = head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    @Override
    public void clear() {
        head = null;
        last = null;
    }
    @Override
    public void display() {
        ListNode cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}

接口:

public interface IList {
    //头插法
    public void addFirst(int data);
    //尾插法
    public void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data);
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key);
    //删除第一次出现关键字为key的节点
    public void remove(int key);
    //删除所有值为key的节点
    public void removeAllKey(int key);
    //得到单链表的长度
    public int size();
    // 清空链表
    public void clear();
    // 打印链表
    public void display();
}

异常类:

public class IndexException extends RuntimeException{
    public IndexException() {
    }
    public IndexException(String msg) {
        super(msg);
    }
}

4. ArrayList和LinkedList的区别

不同点 ArrayList LinkedList
存储空间上 物理上一定连续 逻辑上连续,但物理上不一定连续
随机访问 支持O(1) 不支持O(n)
头插 需要搬移元素,效率低O(n) 只需要修改引用的指向,时间复杂度为O(1)
插入 空间不够时需要扩容 没有容量的概念
应用场景 元素高效存储 + 频繁访问 任意位置插入和删除频繁

相关文章
|
7月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
7月前
|
人工智能 算法 NoSQL
LRU算法的Java实现
LRU(Least Recently Used)算法用于淘汰最近最少使用的数据,常应用于内存管理策略中。在Redis中,通过`maxmemory-policy`配置实现不同淘汰策略,如`allkeys-lru`和`volatile-lru`等,采用采样方式近似LRU以优化性能。Java中可通过`LinkedHashMap`轻松实现LRUCache,利用其`accessOrder`特性和`removeEldestEntry`方法完成缓存淘汰逻辑,代码简洁高效。
310 0
|
2月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
9月前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
276 15
|
9月前
|
监控 算法 安全
基于 PHP 语言深度优先搜索算法的局域网网络监控软件研究
在当下数字化时代,局域网作为企业与机构内部信息交互的核心载体,其稳定性与安全性备受关注。局域网网络监控软件随之兴起,成为保障网络正常运转的关键工具。此类软件的高效运行依托于多种数据结构与算法,本文将聚焦深度优先搜索(DFS)算法,探究其在局域网网络监控软件中的应用,并借助 PHP 语言代码示例予以详细阐释。
206 1
|
10月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
11月前
|
存储 算法 安全
探究‘公司禁用 U 盘’背后的哈希表算法与 Java 实现
在数字化办公时代,信息安全至关重要。许多公司采取“禁用U盘”策略,利用哈希表算法高效管理外接设备的接入权限。哈希表通过哈希函数将设备标识映射到数组索引,快速判断U盘是否授权。例如,公司预先将允许的U盘标识存入哈希表,新设备接入时迅速验证,未授权则禁止传输并报警。这有效防止恶意软件和数据泄露,保障企业信息安全。 代码示例展示了如何用Java实现简单的哈希表,模拟公司U盘管控场景。哈希表不仅用于设备管理,还在文件索引、用户权限等多方面助力信息安全防线的构建,为企业数字化进程保驾护航。
|
9月前
|
存储 算法 安全
企业员工数据泄露防范策略:基于 C++ 语言的布隆过滤器算法剖析[如何防止员工泄密]
企业运营过程中,防范员工泄密是信息安全领域的核心议题。员工泄密可能致使企业核心数据、商业机密等关键资产的流失,进而给企业造成严重损失。为应对这一挑战,借助恰当的数据结构与算法成为强化信息防护的有效路径。本文专注于 C++ 语言中的布隆过滤器算法,深入探究其在防范员工泄密场景中的应用。
214 8
|
9月前
|
存储 监控 算法
基于 PHP 语言的滑动窗口频率统计算法在公司局域网监控电脑日志分析中的应用研究
在当代企业网络架构中,公司局域网监控电脑系统需实时处理海量终端设备产生的连接日志。每台设备平均每分钟生成 3 至 5 条网络请求记录,这对监控系统的数据处理能力提出了极高要求。传统关系型数据库在应对这种高频写入场景时,性能往往难以令人满意。故而,引入特定的内存数据结构与优化算法成为必然选择。
252 3
|
10月前
|
算法 安全 Go
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了
本文探讨了如何利用 Go 语言中的 Bloom Filter 算法提升公司局域网管理系统的性能。Bloom Filter 是一种高效的空间节省型数据结构,适用于快速判断元素是否存在于集合中。文中通过具体代码示例展示了如何在 Go 中实现 Bloom Filter,并应用于局域网的 IP 访问控制,显著提高系统响应速度和安全性。随着网络规模扩大和技术进步,持续优化算法和结合其他安全技术将是企业维持网络竞争力的关键。
220 2
公司局域网管理系统里的 Go 语言 Bloom Filter 算法,太值得深挖了

热门文章

最新文章