数据结构入门教程-双向链表

前面我们对单向链表做了详细的讲解,那么对于单链表的学习就告一段落,本篇来了解下链表的另外一种,双向链表,接下来我们来看看双向链表的基本用法等。

双向链表介绍

双向链表首先是链表的一种,其次我们在单链表中学习的过程中会发现,我们的操作都是基于一个辅助变量来完成的,这是因为我们的单链表查找是只能是一个方向的,这就衍生出了双链表,我们不仅可以从前往后操作,也可以从后往前操作.

  • 那么还是通过之前梁山好汉排行榜的案例来学习我们的双向链表

我们先来通过如下图来分析下它的添加过程(默认末尾添加)

双向链表末尾添加分析.png

上图是我的链表,可以发现的是与单链表不同的是多了一个指针pre(它表示当前节点的前一个节点),接下来我们分析末尾添加的思路:

  • 首先我们需要当前节点的最后一个节点(这是前提)
  • 接着是最后一个节点.next=新节点
  • 最后是新节点.pre = 最后一个节点

简单的来说是就这三步,接下里我们通过代码实现,首先我们需要创建一个节点类,这里直接用单链表的稍作修改即可

代码实现
  • 节点的创建
//1.定义一个HeroNode节点,每一个HeroNode对象就是一个节点
class HeroNode2{
public int no; //英雄节点的编号
public String name;
public String nickName; //英雄的别名
public HeroNode2 next;//指向下一个节点,默认为null
public HeroNode2 pro; //指向前一个节点,默认为null
public HeroNode2(int hNo,String hName,String hNickName){
    this.no = hNo;
    this.name = hName;
    this.nickName = hNickName;
}

@Override
public String toString() {
    return "HeroNode{" +
            "no=" + no +
            ", name='" + name + '\'' +
            ", nickName='" + nickName + '\'' +
            '}';
    }
}
  • 创建一个双向链表类,编写添加方法,代码如下:
//双向链表
class DoubleLinkedList{

//1.先初始化一个头结点,不存储具体的数据,只是作为我们链表的头部
private HeroNode2  head = new HeroNode2(0,"","");

public HeroNode2 getHead(){
    return head;
}

//1.双向链表添加节点到末尾
public void add(HeroNode2 heroNode){
    //1.定义一个临时的变量(主要的目的是因为我们的头节点是不能动的,通过这个辅助指针来进行相关的操作)
    HeroNode2 temp = head; //temp指向了head节点
    //2.遍历链表,找到最后一个
    while (true){
        if (temp.next == null){ //表明是最后一个了
            break;
        }
        //如果没有找到,将temp后移
        temp = temp.next;
    }
    //3.当退出while时表名是最后一个节点了,指向新的节点即可
    temp.next = heroNode;
    heroNode.pro = temp; //新节点的前一个节点指向temp
}

上述就是我们添加的代码实现,我们在来编一个打印双链表的代码,具体的实现思路跟单链表一样,代码中有详细的注释,代码如下:

//显示链表(通过一个辅助变量,因为head不能动)
public void show(){
    //1.首先判断链表是否为null
    if (head.next ==null){
        System.out.println("链表为null");
        return;
    }
    //因为头结点不能动,需要一个辅助指针来遍历
    HeroNode2 temp = head.next; //说明至少有一个节点
    while (true){
        //判断是否到了链表的最后
        if (temp ==null){
            break;
        }
        //打印节点信息
        System.out.println(temp);
        //将指针后移,遍历下一个节点信息
        temp = temp.next;
    }
}

测试我们的添加操作,代码如下:

/**
 * 数据结构-双向链表
 */
public class DoubleLinkedListCase {

public static void main(String[] args) {

    //测试
    HeroNode2 node1 = new HeroNode2(1,"宋江","及时雨");
    HeroNode2 node2 = new HeroNode2(2,"卢俊义","玉麒麟");
    HeroNode2 node3 = new HeroNode2(3,"吴用","智多星");
    HeroNode2 node4 = new HeroNode2(4,"林冲","豹子头");
    DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    //测试链表的添加
    System.out.println("双向链表的添加测试================");
    doubleLinkedList.add(node1);
    doubleLinkedList.add(node2);
    doubleLinkedList.add(node3);
    doubleLinkedList.add(node4);
    doubleLinkedList.show();

测试结果如下图:

双向链表添加测试.png

从结果图来看是没问题的,亲测有效,接着我们来看修改操作,由于修改操作的思路分析跟单链表的一样,这里稍作了修改,代码中见:

双链表的修改操作
//3.修改指定的链表节点
public void update(HeroNode2 newHeroNode){
    //判断是否为null
    if (head.next == null){
        System.out.println("链表为null");
        return;
    }
    //找到需要修改的节点位置,根据编号去找
    //定义一个辅助变量
    HeroNode2 temp = head;
    boolean flag = false; //flag用来表示是否找到该节点
    while (true){
        if (temp == null){
            break; //已经遍历完列表
        }
        if (temp.no == newHeroNode.no){
            flag = true; //表示已经找到该节点
            break;
        }
        //往后移动接着遍历找
        temp = temp.next;
    }
    //根据flag来判断是否找到要修改的节点
    if (flag){
        temp.name = newHeroNode.name;
        temp.nickName = newHeroNode.nickName;
    } else {

        System.out.printf("编号为 %d的节点不存在,无法修改 \n",newHeroNode.no);
    }
}

其实跟单链表的操作是一样的,来看测试代码:

  • 测试代码:
    System.out.println("双向链表的修改测试================");
    HeroNode2  newHeroNode = new HeroNode2(2,"卢俊义1","玉麒麟1");
    doubleLinkedList.update(newHeroNode);
    doubleLinkedList.show();
  • 测试结果如图:


    双向链表的修改测试结果图.png

图中修改了节点的信息,从代码来看我们实现了,接着我们来看双链表的删除操作

双链表的删除操作

我们先来回顾下单链表删除的思路,我们通过辅助指针帮我们找到要删除节点的前一个(注意:这里必须是删除节点的前一个,如果不是的话,整个链表就断了),然后是要删除节点的前一个的next指向删除节点的下一个,这样的话删除节点的引用为null,后被垃圾回收机制回收,而对于我们的双链表缺恰恰相反,它不在指向要删除节点的前一个,而是直接指向删除的节点实现自我删除,这也是 双向链表和单链表区别大的一点。

通过上面的比较我们会有一个最大的发现,那就是双向链表可以实现自我删除某一个节点,那么具体的思路是怎样的呢,请看如图的分析:

双向链表图解分析思路.png

就像图中的一样,我这里假设要删除节点5,只需要满足如下条件即可

  • 让节点5.pre.next = 节点5.next
  • 让节点5.next.pre = 节点5.pre

其实只需要满足这两个条件即可,既然我们知道了如何实现,接下来代码实现:

代码实现
//从双向链表中删除一个节点
//1.对于双向链表的删除,我们可以能直接找到删除的节点,找到后自己删除即可
public void delete(int no){
    if (head.next ==null){
        System.out.println("链表为null");
        return;
    }
    //1.定义辅助变量
    HeroNode2 temp = head.next; //辅助指针
    boolean flag = false; //是否找打要删除节点的前一个
    while (true){
        if (temp == null){ //说明已经遍历到最后一个节点了
            break;
        }
        if (temp.no == no){ //说明找到待删除节点的前一个temp
            flag = true;
            break;
        }

        temp = temp.next; //后移遍历链表
    }
    //通过flag来判断是否找到
    if (flag){
        temp.pro.next = temp.next; //当前删除节点的上一个指向当前删除节点的下一个
        //如果删除的节点是最后一个,需要判断不然后出现空指针异常
        //因为 temp.next=null
        if (temp.next !=null) {
            temp.next.pro = temp.pro; //当前删除节点的下一个的上一个箭头指向当前节点的上一个节点即可
        }
    }else {
        System.out.printf("要删除的 %d 节点不存在\n",no);
    }
}

代码中有详细的注释,我们来测试一把,代码如下:

 System.out.println("双向链表的删除测试================");
 doubleLinkedList.delete(2);
 doubleLinkedList.show();

测试结果如下图所示:

双向链表删除测试结果图.png

从图中我们可以得知,删除的代码是没有问题的,关于双向链表的基本操作就到这里呢....

你可能感兴趣的:(数据结构入门教程-双向链表)