4.2 Java实现双向链表(完整代码及详细注释)

1.什么是双向链表?

双向链表由多个节点组成,每个 节点(用于存储数据) 通过prev,next指针和前,后节点相互连接,
下一个节点的引用存放在上一个节点的next指针中, 上一个节点的引用存放在下一个节点的prev指针中,
从而构成了一个线性的链表 (它与数组的区别就是它可以不具空间连续性)
在java中一般通过定义节点Node类和API类的方式来实现链表

JDK中对链表LinkedList的节点的实现结构如下:

可以看到链表节点组成分为三部分:
存储数据的元素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;
        }
    }

可以知道JDK的链表实现就是双向链表.
节点定义一般就放在API内部作为内部静态类使用,我们使用链表数据结构都是通过LinkedList API对外开放的接口进行调用的,
我们不允许直接对链表内部属性直接访问。

1.1 双向链表基本结构

下面我们学习最基本的链表结构,我们默认链表中的元素就是int类型数字。

如图所示:

prev.node1.next
prev.node2.next
prev.node3.next
node1
node2
node3
null

其中节点的数据结构为:

/**
 * 双向链表节点数据结构
 */
public class Node {
  /**
   * 存储节点数据
   */
  public int data;
  /**
   * 存储下一个节点引用
   */
  public Node next;
  /**
   * 本节点名称
   */
  public String nodeName;
  /**
   * 上一个节点的引用
   */
  public Node previous;

  /**
   * 节点构造方法,仅构造出其存储的数据,并不构造下一节点的引用
   * @param data 节点数据
   */
  public Node(int data, String nodeName){
    this.data = data;
    if(nodeName==null){
      nodeName=""+data;
    }
    this.nodeName = nodeName;
  }
}

我们该如何实现操作双向链表的方法来让我们正确双向链表存储数据呢?

1.2 实现对双向链表基本的增删改查

1.2.1 插入方法

给出一个节点nodeToInsert和指定位置position,在链表指定位置插入该节点
主要思路分为正向插入或反向插入,这两种插入的方法的不同点在于使用不同的起始位置和指针进行遍历查找,
正向插入使用的是head为起始位置next指针查找,反向插入使用的是tail为起始位置previous指针查找。

具体流程如下:

  • 首先判断链表是否为空,如果为空则直接放入第一个节点

  • 再先判断链表中存不存在position位置,

    若不存在输出错误信息并返回

    若存在则进行插入操作:(正向插入逻辑)

    1. 首先判断position是不是为0,因为这种情况不同于其他的,如果是则直接进行头节点插入
    2. 否则,先找到第position-1个节点和position个节点。
    3. 将前一个节点的下一节点设为nodeToInsert ,将nodeToInsert的下一个节点设置为position节点
  • 这样就完成了元素插入

代码实现如下:

class Test{
  /**
   * 正向插入
   * 主要思路是:
   *      先判断链表中存不存在position位置,若没有输出错误信息并返回头节点;
   *      若存在则进行插入操作:首先判断position是不是为0,因为这种情况不同于其他的。
   *      否则,先找到第position-1个节点和position个节点,将前一个节点的下一节点设为nodeToInsert
   *      将nodeToInsert的下一个节点设置为position节点,这样完成插入操作
   * @param headNode 链表头节点
   * @param nodeToInsert 要插入的节点
   * @param position 指定插入的位置
   * @return 插入后的链表头节点
   */
  static Node insertByHead(Node headNode, Node nodeToInsert, int position) {
    if(headNode==null) {
      return nodeToInsert;
    }
    //获得输入链表的长度
    int size = ListLengthByHead(headNode);
    //判断链表内是否存在此位置
    if(position > size || position < 0) {
      System.out.println("位置异常!! position不能超过 "+size);
      return headNode;
    }
    //在链表开头插入
    if(position == 0) {
      nodeToInsert.next = headNode;
      headNode.previous = nodeToInsert;
      return nodeToInsert;
    }
    //在中间或末尾插入
    else {
      Node prev = headNode;
      int count = 0;
      //找到那个位置的前一个节点
      while(count < position-1) {
        //获得第position-1位置的节点
        prev = prev.next;
        count++;
      }
      //获得第position位置的节点
      Node currentNode = prev.next;
      // 在cur不为空的时候才进行insert和cur之间的关系改变
      if(currentNode!=null) {
        nodeToInsert.next = currentNode;
        currentNode.previous = nodeToInsert;
      }
      prev.next =nodeToInsert;
      nodeToInsert.previous = prev;
    }
    return headNode;
  }

  /**
   * 反向插入,以尾节点尾开始节点进行插入操作
   * @param tailNode 链表尾节点
   * @param nodeToInsert 要插入的节点
   * @param position 指定插入的位置
   * @return 插入后的链表头节点
   */
  static Node insertByTail(Node tailNode, Node nodeToInsert, int position) {
    if(tailNode == null) {
      return nodeToInsert;
    }
    //获得输入链表的实际长度
    int size = ListLengthByTail(tailNode);
    //判断链表内是否存在此位置
    if(position > size || position < 0) {
      System.out.println("位置异常!! position不能超过 "+size);
      return tailNode;
    }
    //在链表结尾插入
    if(position == size) {
      nodeToInsert.previous = tailNode;
      tailNode.next = nodeToInsert;
      return nodeToInsert;
    }
    //在中间或开头插入
    else {
      Node previousNode = tailNode;
      int count = 0;
      //找到那个位置的前一个节点
      while(count < size-position-1) {
        //获得第position-1位置的节点
        previousNode = previousNode.previous;
        count++;
      }
      //获得第position位置的节点
      Node currentNode = previousNode.previous;
      //插入操作
      if(currentNode!=null) {
        currentNode.next = nodeToInsert;
        nodeToInsert.previous = currentNode;
      }
      nodeToInsert.next = previousNode;
      previousNode.previous = nodeToInsert;
    }
    return tailNode;
  }



  /**
   * 以输入节点为头,计算出链表长度
   * @param head 头节点
   * @return 链表长度
   */
  static int ListLengthByHead(Node head) {
    int length = 0;
    Node cur = head;
    while(cur!=null){
      length++;
      cur = cur.next;
    }
    return length;
  }

  /**
   * 以输入节点为尾,计算出链表长度
   * @param tail 尾节点
   * @return 链表长度
   */
  static int ListLengthByTail(Node tail) {
    int length = 0;
    Node cur = tail;
    while (cur!=null){
      length++;
      cur = cur.previous;
    }
    return length;
  }
}
1.2.2 如何删除链表的元素

主要思路是找到position的前一个节点和后一个节点,然后将他们连接,
这里展示头节点处理,尾节点处理方法可以结合上下文自己试着写一下

代码实现:

class Test{
  /**
   * 方法和前面的插入方法有异曲同工之妙:
   *  主要思想是找到position的前一个节点和后一个节点,然后将他们连接
   * @param headNode 头节点
   * @param position 删除的位置
   * @return 删除后的链表头节点
   */
  static Node deleteByHead(Node headNode, int position) {
    int size = ListLengthByHead(headNode);
    if(position>=size||position<0) {
      System.out.println("位置异常!! position位置不能超过 "+(size-1));
      return headNode;
    }
    //删除表头
    if(position==0) {
      //将第二个节点的上一节点引用删除
      headNode.next.previous = null;
      return headNode.next;
    }
    //删除中间或结尾节点
    else {
      Node previousNode = headNode;
      int count = 0;
      //获得目标节点的上一个节点
      while(count < position-1) {
        previousNode = previousNode.next;
        count++;
      }
      //要删除目标节点
      Node currentNode = previousNode.next;
      previousNode.next = currentNode.next;
      if(currentNode.next!=null)
        currentNode.next.previous = previousNode;
    }
    return headNode;
  }

    /**
     * 以输入节点为头,计算出链表长度
     * @param head 头节点
     * @return 链表长度
     */
    static int ListLengthByHead(Node head) {
        int length = 0;
        Node current = head;
        while(current!=null){
            length++;
            current = current.next;
        }
        return length;
    }
}
1.2.3 如何得到指定位置的值
class Test{
  /**
   * 获取链表指定位置的元素
   * @param head
   * @param position
   * @return
   */
  static Node getByHead(Node head,int position) {
    int size = ListLengthByHead(head);
    if( position < 0 || position >= size) {
      System.out.println("链表不存在该位置: "+ position +"链表最大位置索引: "+(size-1));
      return null;
    }
    Node cur = head;
    for (int index = 0;index < position;index++){
      cur = cur.next;
    }
    return cur;
  }

  static Node getByTail(Node tail,int position) {
    int size = ListLengthByTail(tail);
    if(position<0 || position >= size){
      System.out.println("链表不存在该位置: "+ position +"链表最大位置索引: "+(size-1));
      return null;
    }
    Node cur = tail;
    for(int index = 0;index< (size-position-1);index++){
      cur = cur.previous;
    }
    return cur;
  }
    
}

2 项目地址(内含完整测试实例)

https://gitee.com/yan-jiadou/algorithm-study/tree/master/algorithmStudy/src/main/java/course/p4_list/s2_TwoNextList

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
下面是Java实现双向链表的完整源码: ```java public class DoublyLinkedList<T> { private Node head; private Node tail; private int size; public DoublyLinkedList() { head = null; tail = null; size = 0; } private class Node { T data; Node prev; Node next; public Node(T data) { this.data = data; prev = null; next = null; } } public int size() { return size; } public boolean isEmpty() { return size == 0; } public void addFirst(T data) { Node newNode = new Node(data); if (isEmpty()) { tail = newNode; } else { head.prev = newNode; newNode.next = head; } head = newNode; size++; } public void addLast(T data) { Node newNode = new Node(data); if (isEmpty()) { head = newNode; } else { tail.next = newNode; newNode.prev = tail; } tail = newNode; size++; } public void add(int index, T data) { if (index < 0 || index > size) { throw new IndexOutOfBoundsException("Invalid index!"); } if (index == 0) { addFirst(data); return; } if (index == size) { addLast(data); return; } Node current = head; for (int i = 0; i < index - 1; i++) { current = current.next; } Node newNode = new Node(data); newNode.prev = current; newNode.next = current.next; current.next.prev = newNode; current.next = newNode; size++; } public T removeFirst() { if (isEmpty()) { throw new RuntimeException("Empty list!"); } T data = head.data; head = head.next; if (head == null) { tail = null; } else { head.prev = null; } size--; return data; } public T removeLast() { if (isEmpty()) { throw new RuntimeException("Empty list!"); } T data = tail.data; tail = tail.prev; if (tail == null) { head = null; } else { tail.next = null; } size--; return data; } public T remove(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Invalid index!"); } if (index == 0) { return removeFirst(); } if (index == size - 1) { return removeLast(); } Node current = head; for (int i = 0; i < index; i++) { current = current.next; } current.prev.next = current.next; current.next.prev = current.prev; size--; return current.data; } public T get(int index) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Invalid index!"); } Node current = head; for (int i = 0; i < index; i++) { current = current.next; } return current.data; } public void set(int index, T data) { if (index < 0 || index >= size) { throw new IndexOutOfBoundsException("Invalid index!"); } Node current = head; for (int i = 0; i < index; i++) { current = current.next; } current.data = data; } public boolean contains(T data) { Node current = head; while (current != null) { if (current.data.equals(data)) { return true; } current = current.next; } return false; } public void clear() { head = null; tail = null; size = 0; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("["); Node current = head; while (current != null) { sb.append(current.data); if (current.next != null) { sb.append(", "); } current = current.next; } sb.append("]"); return sb.toString(); } } ``` 代码中使用了嵌套Node表示双向链表中的每个节点,包含一个数据域和两个指针域(prev和next),分别指向前驱节点和后继节点。 程序首先定义了一个DoublyLinkedList,包含头节点head、尾节点tail和链表长度size三个属性。然后,程序实现了以下方法: - size:返回链表长度 - isEmpty:判断链表是否为空 - addFirst:在链表头部插入一个元素 - addLast:在链表尾部插入一个元素 - add:在指定位置插入一个元素 - removeFirst:删除链表头部元素 - removeLast:删除链表尾部元素 - remove:删除指定位置的元素 - get:获取指定位置的元素 - set:设置指定位置的元素 - contains:判断链表中是否包含指定元素 - clear:清空链表 - toString:将链表转换为字符串形式 以上是Java实现双向链表的完整源码,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小牧之

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值