数据结构与算法-双向链表

一、基本介绍

定义 双向链表(Double-Linked List)是链表家族的一员,相比于单链表,它的每个节点不仅包含数据域,还具备两个指针域,分别指向前一个节点和后一个节点。这样的结构赋予了双向链表更高的操作灵活性和更多的应用场景。在双向链表中,每个节点(Node)一般由以下三个部分构成:

  1. 数据域(Data Field):存储节点承载的具体数据信息。
  2. 前驱指针(Previous Pointer):指向该节点的前一个节点。
  3. 后继指针(Next Pointer):指向该节点的后一个节点。

双向链表通常有一个特殊的节点——头节点(Header Node),其前驱指针为空,用作链表的起点。链表的末尾节点的后继指针为空,标识链表的终点。

 二、基本操作

  1. 节点的插入

    • 在头部插入:创建新节点,令其后继指针指向原头节点,并更新原头节点的前驱指针指向新节点,新节点成为新的头节点。
    • 在中间插入:找到插入位置的前一个节点,新节点的前后指针分别指向原节点及其后继节点,同时更新原节点及其后继节点的指针指向新节点。
    • 在尾部插入:找到尾节点,将其后继指针指向新节点,并更新新节点的前驱指针指向尾节点。
  2. 节点的删除

    • 删除头部节点:将原头节点的后继节点提升为新的头节点,并更新其前驱指针为null。
    • 删除中间节点:找到要删除的节点,将它的前驱节点的后继指针和它的后继节点的前驱指针互相连接起来。
    • 删除尾部节点:找到尾节点的前一个节点,将其后继指针设为null。
  3. 遍历

    • 双向链表的遍历可以从前向后(通过后继指针)或从后向前(通过前驱指针)进行,这也使得双向链表在需要双向遍历时具有更高的效率。

  三、应用场景 

  1. LRU缓存淘汰策略:双向链表常用于实现Least Recently Used(LRU)缓存机制,通过双向链表记录数据的访问顺序,便于快速找到并移除最近最少使用的数据。

  2. 文件系统:在文件系统的目录结构中,每个目录项可以视为一个双向链表节点,前驱指针和后继指针分别指向父目录和子目录,便于快速导航和管理目录结构。

  3. 数据结构的实现:双向链表常作为其他数据结构如双向队列(Deque)和循环队列的基础结构。

  4. 数据库索引:在数据库系统中,双向链表可用于实现双向索引,使得查询既可以向前也可以向后进行。

  四、实现原理

   1.带头部链表在直接尾部插入        

  • 先创建一个head头节点,作用就是表示单链表的头
  • 找到双向链表的最后一个节点
  • temp.next = 新节点
  • 新节点.pre = temp

  2.带头部链表按照编号顺序插入

  • 先创建一个head头节点,作用就是表示单链表的头
  • 找到添加新节点的位置,通过辅助变量遍历搞定
  • 新节点.next = temp.next   注意:此操作temp.next != null
  • temp.next.pre = 新节点
  • temp.next = 新节点
  • 新节点.pre = temp

 3.修改节点

  • 先创建一个head头节点,作用就是表示单链表的头
  • 通过遍历找到该节点
  • temp.name = 新节点.name  temp.nickName = 新节点.nickName

 4.删除节点

  • 先创建一个head头节点,作用就是表示单链表的头
  • 通过遍历找到该节点的前一个节点
  • temp.pre.next = temp.next
  • temp.next.pre = temp.pre
  • 被删除的节点,将不会有其他引用指向,会被垃圾回收机制回收

  五、代码展示

  说明:代码展示依然以水浒传英雄添加按照编号顺序添加和不按照编号顺序添加两种方式进行添加操作,其中先定义HeroNode,每一个HeroNode对象就是一个节点并且是带头部链表。这里注意展示重要功能。

 1.带头部链表在直接尾部插入 

    //    链表添加,默认添加到最后
    public void add(HeroNode2 heroNode2) {
        HeroNode2 temp = head;
        while (true) {
            if (temp.next == null) {
                break;
            }
            temp = temp.next;
        }
//        双向链表关联新节点
        temp.next = heroNode2;
        heroNode2.pre = temp;
    }

  2.带头部链表按照编号顺序插入 

    public void addByOrder(HeroNode2 heroNode2) {
        HeroNode2 temp = head;
        boolean flag = false;   //标志添加的编号是否存在,默认FALSE
        while (true) {
            if (temp.next == null) {  //说明temp已经在链表的最后
                break;
            }
            if (temp.next.no > heroNode2.no) {  //位置找到
                break;
            } else if (temp.next.no == heroNode2.no) {  //编号存在
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            System.out.printf("准备插入的英雄编号 %d 已存在,不能加入\n", heroNode2.no);
        } else {
//            添加
            if (temp.next != null) {
                heroNode2.next = temp.next;
                temp.next.pre = heroNode2;
            }
            temp.next = heroNode2;
            heroNode2.pre = temp;
        }
    }

 3.修改节点 

    //    修改双向链表
    public void update(HeroNode2 heroNode2) {
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                System.out.println("链表是空的!无法修改!");
                break;
            }
            if (temp.no == heroNode2.no) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.name = heroNode2.name;
            temp.nickName = heroNode2.nickName;
        } else {
            System.out.println("没有这个节点!");
        }
    }

 4.删除节点 

    //    删除节点
    public void delete(int nodeNo) {
        HeroNode2 temp = head.next;
        boolean flag = false;
        while (true) {
            if (temp == null) {
                System.out.println("链表是空的!");
                break;
            }
            if (temp.no == nodeNo) {
                flag = true;
                break;
            }
            temp = temp.next;
        }
        if (flag) {
            temp.pre.next = temp.next;
            if (temp.next != null) {
                temp.next.pre = temp.pre;
            }
        } else {
            System.out.println("删除节点不存在!!!");
        }
    }

 六、总结 

        总之,双向链表作为一个既有单链表的动态特性又增加了双向访问能力的数据结构,其在实际应用中扮演着举足轻重的角色。通过对双向链表的深入理解和熟练运用,开发者能够更好地优化算法性能,提高程序设计的灵活性和效率

                              

你可能感兴趣的:(数据结构与算法,java,数据结构,算法)