Java实现双向链表

目录

大家好!欢迎大家来到利物浦痛失欧冠冠军的第一期文章。

一、双向链表的简单理解

1、双向链表是什么?

2、双向链表长什么样子?

二、代码演示

1、结点的构建

2、链表的创建

3、打印链表

4、得到链表长度

5、查找元素

 6、插入元素

(1)、头插法

(2)、尾插法

(3)、中间插入

7、删除元素

(1)、删除元素为头结点。

 (2)、删除元素为中间结点或者尾巴结点

三、总结


大家好!欢迎大家来到利物浦痛失欧冠冠军的第一期文章。

一、双向链表的简单理解

1、双向链表是什么?

在前面的文章中,我们仔细讲解了单向链表,并且用代码实现了单向链表。单向链表的好处很多,虽然单链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定结点的前趋结点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项,因此我们就有了双向链表这个东西,双向链表顾名思义就是链表的高级版,他与单向链表有所不同的一点在于在具有尾巴结点,并且双向链表的每一个结点都会有两个指向,一个指向前面的结点,一个指向后面的结点,当然头结点的前端指向为null,后结点的后端指向为null。

2、双向链表长什么样子?

双向链表的一个结点,具有包括三个信息:1、前端指针域。2、数据域。3、后端指针域

如图所示

双向链表正是由这些一个个结点组合而成

接下来我们将会用代码的方式来实现双向链表

二、代码演示

1、结点的构建

和单向链表一样,链表的实现离不开结点,这构建也与单向链表相似,只不过不同的是我们需要加一个前端指针

class ListNode{
    public ListNode prev;
    public ListNode next;
    public int val;
    public ListNode(int num){
        this.val = num;
    }
 public ListNode(){
       
    }
}

2、链表的创建

链表的创建中有个很重要的东西,就是我们需要给链表设置一个头结点指针与尾巴结点指针,因为链表初始化只有一个结点,因此头结点指针 == 尾巴结点指针

  public ListNode head;
    public ListNode last;
    public TowWayNodeList(int num){
        this.head = new ListNode(num);
        this.last = this.head;
    }

3、打印链表

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

4、得到链表长度

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

5、查找元素

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

 6、插入元素

(1)、头插法

当我们想把新的结点插入到第一个结点位置处,可以先建立一个结点,然后把头结点的prev变为我们新建立结点的next值,然后将我们新建立的结点值变为null,最后将头结点指向新的插入的结点。

注意我们需要首先判断这个链表是否为空,假如为空就直接构建链表即可

 //头插法
    public void addFirst(int num){
        ListNode node = new ListNode(num);
        if(head == null){
            this.head = node;
            this.last = node;
        }else{
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }
    }

(2)、尾插法

尾插法顾名思义就是从结尾插入新的结点,这个和头插法过程差不多,只不过一个是改变head的位置,一个是改变last的位置。

和头插法一样,这个同样需要判断链表是否初始为空

   public void addLast(int data){
        ListNode node = new ListNode(data);
        if(head == null){
            this.head = node;
            this.last = node;
        }else{
            this.last.next = node;
            node.prev = this.last;
            this.last = node;
        }
    }

(3)、中间插入

这个是最复杂的一种插入方式,我们需要先找到需要插入位置的结点cur,然后利用cur就可以得出前后两个结点,直接插入即可。

首先我们建立一个方法来查找cur的位置,一个返回值为结点的元素

   public ListNode searchIndex (int index){
        ListNode cur = this.head;
        while(index != 0){
            cur = cur.next;
            index--;
        }
        return cur;
    }

 然后利用cur附近的结点来直接插入即可

 这是cur附近各个指针域的图,我们只需要改变cur附近的结点指针域的指向即可,首先node指向cur的地址也就是cur.prev.next

然后cur.prev.next指向node的地址,然后再开始前端指针的指向 

 

 

 //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if(index < 0|| index > size()){
            System.out.println("非法");
            return;
        }
        //ListNode node = new ListNode(data);
        if(index == 0){
            addLast(data);
        }else if(index == size()){
            addLast(data);
        }else{
            ListNode node = new ListNode(data);
            ListNode cur = searchIndex(index);
            node.next = cur.prev.next;
            cur.prev.next = node;
            node.prev = cur.prev;
            cur.prev = node;
        }
    }

7、删除元素

删除元素的话,我们需要:

(1)、删除元素为头结点。

 假如是头结点的话我们还需要判断这个链表是否只有一个结点,如果是那么last指针也会为空,head指针也会为空,否则,我们只移动头指针结点就可以。

 

 (2)、删除元素为中间结点或者尾巴结点

当删除中间结点的时候我们可以先找到对于位置的结点cur,利用对应位置的cur.prev和cur.next确定附近两个结点,然后进行删除即可,这个删除与链表相似,只是多了一个删除头结点而已。

 

 首先跳过后端结点cur.prev.next = cur.next 

 

然后跳过前端结点cur.next.prev = cur.prev 

但是假如我们遇到了删除结点为尾巴结点该怎么办呢?

 

很简单,只需让last = last.prev即可!

 //删除第一次出现关键字为key的节点
    public void remove(int key){
      ListNode cur = this.head;
      while(cur != null){
          if(cur.val == key){
              if(cur == head){//假如为头结点
                  head = head.next;//Next指针移动完毕接下来就是prev指针
                  if(head != null){
                      //判断head是否为空,为空说明这个链表就只有一个元素,不为空说明不止一个
                      //这是说明这个属于中间位置hah
                     head.prev = null;
                  }else{
                     last = null;
                  }
              }else{
                  cur.prev.next = cur.next;
                  if(cur.next != null){
                      //中间位置
                      cur.next.prev = cur.prev;
                  }else{
                      last = last.prev;
                      //假如是最后一个结点,last结点始终指向双向链表最后一个结点,最后一个删除last变为其前驱即可
                  }

              }
              return;
          }
          cur = cur.next;
      }
    }

三、总结

双向链表算是对于链表进行了更加全面的优化,需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:

  • 将新节点的 prior 指针指向直接前驱节点;
  • 将直接前驱节点的 next 指针指向新节点;

这样也更好的利用了前后指针,更加具体的防止了,当我们进行对于链表的增加与删除时,操作不当整个链表丢失的情况。 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 20
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

跑不死的程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值