当前位置: 首页 > article >正文

【数据结构】Java实现双向链表

目录

1. 接口的实现

2. 动手实现双链表

2.1 重写SeqList接口方法

2.2 在当前链表尾部添加节点(尾插)

2.3 在当前链表头部添加节点(头插)

2.4 检验index是否合法

2.5 在 第index位置添加节点(任意位置)

2.6  删除第index个节点

2.7 删除第一个值element的节点

2.8 删除所有值element的节点

2.9 修改第index个节点的值为element

2.10 获取第index个节点的值

2.11 判断链表中是否存在element

2.12  获取element在链表中的位置

2.13 打印链表

2.14  获取链表长度以及清空链表

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

3.2 Test类

3.3 测试结果


LinkedList 的底层是双向链表结构 ,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

1. 接口的实现

(这个接口在前面的单链表,动态数组中也可以用到)

public interface SeqList<E> {
    //    尾插
    void add(E element);
    //    将 element 插入到 index 位置
    void add(int index,E element);
    //    删除 index 位置元素<返回删除的值
    E removeByIndex(int index);
    //    删除第一个值element的元素
    void removeByValue(E element);
    //    删除所有值element的元素
    void removeAllValue(E element);
    //    将下标 index 位置元素设置为 element,返回替换前的值
    E set(int index,E element);
    E get(int index);
    //    判断 o 是否在其中
    boolean contains(E element);
    int indexOf(E element);
    int size();
    void clear();
}

2. 动手实现双链表

2.1 重写SeqList接口方法

定义双向链表类,实现SeqList方法,重写同String方法。

(Alt + insert 快速实现方法重写)

package seqlist.link;

import seqlist.SeqList;

public class DoubleLinkedList<E> implements SeqList<E> {
    private DoubleNode head;//头节点
    private DoubleNode tail;//尾节点

    private int size; // 车厢节点个数,保存的元素个数

    //车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
    private class DoubleNode {
        E val;//保存的元素
        DoubleNode prev;
        DoubleNode next;
        DoubleNode(E val) {
            this.val = val;
        }
        public DoubleNode(E val, DoubleNode prev, DoubleNode next) {
            this.val = val;
            this.prev = prev;
            this.next = next;
        }
    }
    public void addFrist(E val){ }
    @Override
    public void add(E element) { }
    @Override
    public void add(int index, E element) { }
    @Override
    public E removeByIndex(int index) { }
    @Override
    public void removeByValue(E element) { }
    @Override
    public void removeAllValue(E element) { }
    @Override
    public E set(int index, E element) { }
    public boolean rangeCheck(int index){ }
    @Override
    public E get(int index) { }
    @Override
    public boolean contains(E element) { }
    @Override
    public int indexOf(E element) { }
    @Override
    public int size() { }
    @Override
    public void clear() { }
    @Override
    public String toString() { }
}

2.2 在当前链表尾部添加节点(尾插)

(1)size++

(2)如果链表为空,这个新插入的节点就是头节点

(3)链表不为空,将当前链表的尾节点的next指向新节点,新节点的前驱prev指向尾节点

(4)更新当新节点为尾节点

public void add(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if (head == null){
            head = node;
        }else {
            node.prev = tail;
            tail.next = node;
        }
        tail = node;
    }

2.3 在当前链表头部添加节点(头插)

这里头插不是重写方法!(因为前面的动态数组没又头插这一说法,所以这个方法就不放在接口里了)

(1)size++

(2)如果链表尾空,那么新节点就是头节点和尾节点

(3)链表不为空,将新节点的next指向head,head的前驱prev指向新节点

(4)更新新节点为头节点head

public void addFirst(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if(head == null){
            tail = node;
        }else {
            node.next = head;
            head.prev = node;
        }
        head = node;
    }

2.4 检验index是否合法

 private boolean rangeCheck(int index) {
        if (index < 0 ||index >= size) {
            return false;
        }
        return true;
    }

2.5 在 第index位置添加节点(任意位置)

(1)判断index是否合法,不合法退出
(2)判断是不是头插或者尾插,调用相应的方法添加后退出
(3)调用node方法index位置节点的找到前驱prev,将prev.next作为后继节点node方法判断index是比较靠近head就从前往后遍历,比较靠近tail就从后往前遍历,使代码效率更高
(4)节点链接,先连左边区域,再连右边区域
(5)size++ 
DoubleNode node = new DoubleNode(element,prev,next);
看前面的构造函数,这时候已经将node.prev = prev,node.next = next
    public void add(int index, E element) {
        if (index < 0 || index > size){
            throw new IllegalArgumentException("add index illegal");
        }
        if(head == null){
            addFirst(element);
            return;
        }
        if(index == size){
            add(element);
            return;
        }

        DoubleNode prev = node(index - 1);
        DoubleNode next = prev.next;
        DoubleNode node = new DoubleNode(element,prev,next);
        // 先处理左边区域
        prev.next = node;
        // 再处理右半区域
        next.prev = node;
        size ++;
    }
    // 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点
    // 内部使用的工具方法
    private DoubleNode node(int index){
        if (index < (size>>1)){
            DoubleNode ret = head;
            for (int i = 0; i < index; i++) {
                ret = ret.next;
            }
            return ret;
        }else {
            DoubleNode ret = tail;
            for (int i = size -1; i > index; i--) {
                ret = ret.prev;
            }
            return ret;
        }
    }

2.6  删除第index个节点

(1)判断index是否合法,不合法退出

(2)调用node方法找到待删除节点

(3)调用unlink(node)方法进行删除,将node的前驱prev和后继next连接,将node的prev和next置空null,再size--。(前驱prev为空时,node是头节点,将新的头节点设为node的下一个节点next;后继next为空时node为尾节点,将node的前驱prev设尾节点)

(4)返回被删除节点的值

public E removeByIndex(int index) {
        if (!rangeCheck(index)){
            throw new IllegalArgumentException("removeByIndex index illegal");
        }
        DoubleNode node = node(index);
        unlink(node);
        return node.val;
    }
    private void unlink(DoubleNode node){
        DoubleNode prev =node.prev;
        DoubleNode next = node.next;
        // 先处理左半区域
        if(prev == null){
            this.head = next;
        }else {
            node.next = null;
            prev.next = next;
        }
        // 在处理右半区域
        if(next == null){
            this.tail = prev;
        }else {
            node.next = null;
            next.prev = prev;
        }
        size--;
    }

2.7 删除第一个值element的节点

遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除
这里的图和2.6 中的图一致
    public void removeByValue(E element) {
        DoubleNode node = head;
        for (int i = 0; i < size; i++) {
            if (node.val.equals(element)){
                unlink(node);
                return;
            }
            node = node.next;
        }
    }

2.8 删除所有值element的节点

(1)遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除

(2)链表有多长就要遍历几次,以防有的节点没有被遍历(此时,每当进行一次删除,size就会减一,直接用size遍历可能导致某些节点漏掉了,因此用length保存初始的size值)

public void removeAllValue(E element) {
        DoubleNode node = head;
        // 因为每次unlink之后都会修改size的值,但是删除所有元素,
        // 要把所有链表节点全部遍历一遍
        int length = this.size;
        for (int i = 0; i < length; i++) {
            DoubleNode next = node.next;
            if (node.val.equals(element)) {
                unlink(node);
            }
            node = next;
        }
    }

2.9 修改第index个节点的值为element

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点

(3)保存原来节点的值

(4)修改该节点的值

(5)返回原来节点的值

    public E set(int index, E element) {
        if(!rangeCheck(index)){
            throw new IllegalArgumentException("set index illeagal");
        }
        DoubleNode node = node(index);
        E oldVal = node.val;
        node.val = element;
        return oldVal;
    }

2.10 获取第index个节点的值

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点并返回

public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return node(index).val;
    }

2.11 判断链表中是否存在element

public boolean contains(E element) {
        DoubleNode node = head;
        while (node.next != null){
            if (node.val.equals(element)){
                return true;
            }
            node = node.next;
        }
        return false;
    }

2.12  获取element在链表中的位置

public int indexOf(E element) {
        DoubleNode node = head;
        int i = 0;
        while (node.next != null){
            if (node.val.equals(element)){
                return i;
            }
            i ++;
            node = node.next;
        }
        return -1;
    }

2.13 打印链表

public String toString() {
        StringBuilder sb = new StringBuilder();
        for(DoubleNode x = head; x != null; x = x.next){
            sb.append(x.val);
            sb.append("->");
            if(x.next == null){
                // 此时temp走到了尾结点
                sb.append("NULL");
            }
        }
        return sb.toString();
    }

2.14  获取链表长度以及清空链表

    public int size() {
        return size;
    }

    @Override
    public void clear() {
        while (head.next != null){
            DoubleNode node = head.next;
            head.next =null;
            head.prev = null;
            head = node;
        }
        head = null;
        size = 0;
    }

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

public class DoubleLinkedList<E> implements SeqList<E> {
    private DoubleNode head;//头节点
    private DoubleNode tail;//尾节点

    private int size; // 车厢节点个数,保存的元素个数

    //车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
    private class DoubleNode {
        E val;//保存的元素

        DoubleNode prev;
        DoubleNode next;
        DoubleNode(E val) {
            this.val = val;
        }

        public DoubleNode(E val, DoubleNode prev, DoubleNode next) {
            this.val = val;
            this.prev = prev;
            this.next = next;
        }
    }

//    w尾插
    @Override
    public void add(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if (head == null){
            head = node;
        }else {
            node.prev = tail;
            tail.next = node;
        }
        tail = node;
    }
    public void addFirst(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if(head == null){
            tail = node;
        }else {
            node.next = head;
            head.prev = node;
        }
        head = node;
    }

    @Override
    public void add(int index, E element) {
        if (index < 0 || index > size){
            throw new IllegalArgumentException("add index illegal");
        }
        if(head == null){
            addFirst(element);
            return;
        }
        if(index == size){
            add(element);
            return;
        }

        DoubleNode prev = node(index - 1);
        DoubleNode next = prev.next;
        DoubleNode node = new DoubleNode(element,prev,next);
        // 先处理左边区域
        prev.next = node;
        // 再处理右半区域
        next.prev = node;
        size ++;
    }
    // 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点
    // 内部使用的工具方法
    private DoubleNode node(int index){
        if (index < (size>>1)){
            DoubleNode ret = head;
            for (int i = 0; i < index; i++) {
                ret = ret.next;
            }
            return ret;
        }else {
            DoubleNode ret = tail;
            for (int i = size -1; i > index; i--) {
                ret = ret.prev;
            }
            return ret;
        }
    }


    @Override
    public E removeByIndex(int index) {
        if (!rangeCheck(index)){
            throw new IllegalArgumentException("removeByIndex index illegal");
        }
        DoubleNode node = node(index);
        unlink(node);
        return node.val;
    }

    @Override
    public void removeByValue(E element) {
        DoubleNode node = head;
        for (int i = 0; i < size; i++) {
            if (node.val.equals(element)){
                unlink(node);
                return;
            }
            node = node.next;
        }
    }

    @Override
    public void removeAllValue(E element) {
        DoubleNode node = head;
        // 因为每次unlink之后都会修改size的值,但是删除所有元素,
        // 要把所有链表节点全部遍历一遍
        int length = this.size;
        for (int i = 0; i < length; i++) {
            DoubleNode next = node.next;
            if (node.val.equals(element)) {
                unlink(node);
            }
            node = next;
        }
    }

    private void unlink(DoubleNode node){
        DoubleNode prev =node.prev;
        DoubleNode next = node.next;
        // 先处理左半区域
        if(prev == null){
            this.head = next;
        }else {
            node.next = null;
            prev.next = next;
        }
        // 在处理右半区域
        if(next == null){
            this.tail = prev;
        }else {
            node.next = null;
            next.prev = prev;
        }
        size--;
    }

    private boolean rangeCheck(int index) {
        if (index < 0 ||index >= size) {
            return false;
        }
        return true;
    }

    @Override
    public E set(int index, E element) {
        if(!rangeCheck(index)){
            throw new IllegalArgumentException("set index illeagal");
        }
        DoubleNode node = node(index);
        E oldVal = node.val;
        node.val = element;
        return oldVal;
    }

    @Override
    public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return node(index).val;
    }

    @Override
    public boolean contains(E element) {
        DoubleNode node = head;
        while (node.next != null){
            if (node.val.equals(element)){
                return true;
            }
            node = node.next;
        }
        return false;
    }

    @Override
    public int indexOf(E element) {
        DoubleNode node = head;
        int i = 0;
        while (node.next != null){
            if (node.val.equals(element)){
                return i;
            }
            i ++;
            node = node.next;
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        while (head.next != null){
            DoubleNode node = head.next;
            head.next =null;
            head.prev = null;
            head = node;
        }
        head = null;
        size = 0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for(DoubleNode x = head; x != null; x = x.next){
            sb.append(x.val);
            sb.append("->");
            if(x.next == null){
                // 此时temp走到了尾结点
                sb.append("NULL");
            }
        }
        return sb.toString();
    }
}

3.2 Test类

public class DoubleNodeTest {
    public static void main(String[] args) {
        DoubleLinkedList<Integer> list = new DoubleLinkedList<>();
        list.add(1);
        list.add(3);
        list.add(5);
        list.add(9);
        list.add(3);
        list.add(3);
        System.out.println(list);
        System.out.println("清空链表");
        list.clear();
        System.out.println(list);
        list.add(6);
        list.add(10);
        list.add(5);
        list.add(7);
        list.add(10);
        list.add(10);

        System.out.println(list);
        System.out.println("------------添加测试-----------");
        System.out.println("从链表尾部添加99,头部添加99999");
        list.add(99);
        list.addFirst(99999);
        System.out.println(list.size());
        System.out.println("添加index为2,element为88");
        list.add(2,88);
        System.out.println(list);
        System.out.println(list.size());
        System.out.println("-----------删除测试------------");
        System.out.println("删除index为0");
        list.removeByIndex(0);
        System.out.println("删除元素为6");
        list.removeByValue(6);
        System.out.println("删除所有10");
        list.removeAllValue(10);
        System.out.println(list);
        System.out.println("-----------其他------------");
        System.out.println("查看是否包含10这个元素");
        System.out.println(list.contains(10));
        System.out.println("修改index为3,element为19");
        list.set(3,19);
        System.out.println("获取index为3的元素");
        System.out.println(list.get(3));
        System.out.println(list);
        System.out.println("获取element为88的index");
        System.out.println(list.indexOf(88));
        System.out.println("获取链表长度");
        System.out.println(list.size());
        System.out.println("清空链表");
        list.clear();
        System.out.println(list + "...");
    }
}

3.3 测试结果


http://www.kler.cn/a/2103.html

相关文章:

  • Godot RPG 游戏开发指南
  • 全志H618 Android12修改doucmentsui鼠标单击图片、文件夹选中区域
  • Layui table不使用url属性结合laypage组件实现动态分页
  • Git实用指南(精简版)
  • 前端小白学习之路-Vben探索 vite 配置 - 1/50
  • gcd 生成4d
  • 个人小站折腾后记
  • Linux命令---设备管理
  • 太强了,英伟达面对ChatGPT还有这一招...
  • 文心一言实际测试——让我们拿实际说好坏
  • QT 如何提高 Qt Creator 的编译速度
  • 【十二天学java】day04-流程控制语句
  • MySQL-存储过程
  • Java实习生------MySQL10道面试题打卡
  • vue3使用vee-validate自定义表单校验,提交实现步骤
  • 收到6家大厂offer,我把问烂了的《Java八股文》打造成3个文档。共1700页!!
  • Java之类与对象(图文结合)
  • C++实现通讯录管理系统
  • async与await异步编程
  • WPF 认识WPF
  • Mybatis的多表操作
  • unity3d游戏运行时lua热重载
  • Kaggle实战入门:泰坦尼克号生还预测(进阶版)
  • Qt cmake 资源文件的加载
  • LeetCode:27. 移除元素
  • ini配置文件