文章目录

  • 1.什么是双向链表?
  • 1.1 双向链表基本结构
  • 1.2 实现对双向链表基本的增删改查
  • 1.2.1 插入方法
  • 1.2.2 如何删除链表的元素
  • 1.2.3 如何得到指定位置的值
  • 2 项目地址(内含完整测试实例)


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