数据结构与算法-双向链表知识详解

概述

鉴于党项链表添加、修改等操作的复杂度,我们可以使用双向链表提升链表的综合性能

在这里插入图片描述

双向链表的实现

添加元素

图解过程

常规插入

插入过程如下图所示,整个过程大致为:

  1. 新结点的prev指针指向原索引为1的结点的前驱节点
  2. 新节点的next指针指向原索引为1的结点
  3. 此时,新结点的前驱结点next指针指向新结点
  4. 此时,新结点的后继节点的prev指针指向新结点

:这种操作衍生出一种特殊情况,即在表头插入,第三步的操作改为first指向新结点即可

在这里插入图片描述

插入到链表末尾

操作过程如下图所示,插入到链表尾巴只要操作四个指针即可,具体过程为:

  1. 新结点prev指向原先的last结点,next不指向任何结点,置为null
  2. 原last的next指针指向新的尾结点
  3. last指针指向新尾结点

:这种操作衍生出一种特殊情况,即链表为空,操作只需将上述的第二步改为first指向新结点即可

在这里插入图片描述

代码

  @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        Node<E> newNode;
        if (index == size) {
            Node<E> oldLast = this.last;
            last = new Node<E>(last, element, null);
            if (oldLast == null) {
                first = last;
            } else {
                oldLast.next = last;

            }

        } else {
            Node<E> newNextNode = findNode(index);
            Node<E> newPrevNode = newNextNode.prev;
            newNode = new Node<E>(newPrevNode, element, newNextNode);
            newNextNode.prev = newNode;

            if (newPrevNode == null) {
                first = newNode;
            } else {
                newPrevNode.next = newNode;
            }
        }

        size++;
    }

修改元素

代码

 @Override
    public E set(int index, E element) {
        Node<E> node = findNode(index);
        E oldElement = node.element;
        node.element = element;


        return oldElement;
    }

删除元素

图解过程

如下所示,操作步骤大致为:

  1. 找到要删除的元素
  2. 待删元素的前驱结点next指针指向待删元素的后继结点
  3. 待删元素的后继结点指向待删元素的前驱结点

注: 这里的删除的结点若是first结点和last结点需特殊处理:

  1. 若删除的是first,则将第2步改为first指向first结点的前一个
  2. 若删除的是last,则将第3步改为last指向last结点的前一个

ps:看到下图操作,可能会有读者问为什么待删元素的prev和next不需要手动处理嘛?其实是不必的,java的gc root会回收那些没有被栈区(局部变量)指向的对象,这就是用java写数据结构比C语言方便的地方。

在这里插入图片描述

代码

 @Override
    public E remove(int index) {
        Node<E> delNode = findNode(index);
        Node<E> delPrevNode =delNode.prev;
        Node<E> delNextNode = delNode.next;

        if (delPrevNode == null) {
            first = delNextNode;
        } else {
            delPrevNode.next = delNextNode;
        }


        if (delNextNode == null) {
            last = delPrevNode;
        } else {
            delNextNode.prev = delPrevNode;
        }
        size--;
        return delNode.element;
    }

查找元素

代码

这里查找相比单向链表快很多,因为有了前后双指针,所以若要找的元素在前半部分,就用first去遍历。若要找后半部分,则用last指针往前遍历

public Node<E> findNode(int index) {
        rangeCheck(index);
        Node<E> node = null;
        if (index < size >> 1) {
            node = this.first;
            for (int i = 0; i < index; i++) {

                node = node.next;
            }

        } else {
            node = this.last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
        }

        return node;
    }

清空元素

代码


    @Override
    public void clear() {
        first = null;
        last = null;
        size = 0;
    }

完整代码

package com.study.doubleLinkListDemo;

import com.study.singlelinkDemo.AbstractList;
import com.study.singlelinkDemo.List;


/**
 * Created by Zsy on 2020/8/4.
 */
public class DoubleLinkList<E> extends AbstractList<E> {
    private Node<E> first;
    private Node<E> last;

    private static class Node<E> {
        E element;
        Node<E> prev;
        Node<E> next;

        public Node(Node<E> prev, E element, Node<E> next) {
            this.prev = prev;
            this.element = element;
            this.next = next;
        }
    }


    @Override
    public void clear() {
        first = null;
        last = null;
        size = 0;
    }

    @Override
    public int size() {
        return size;
    }

    public Node<E> findNode(int index) {
        rangeCheck(index);
        Node<E> node = null;
        if (index < size >> 1) {
            node = this.first;
            for (int i = 0; i < index; i++) {

                node = node.next;
            }

        } else {
            node = this.last;
            for (int i = size - 1; i > index; i--) {
                node = node.prev;
            }
        }

        return node;
    }


    @Override
    public E get(int index) {
        return findNode(index).element;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node = findNode(index);
        E oldElement = node.element;
        node.element = element;


        return oldElement;
    }

    @Override
    public void add(int index, E element) {
        rangeCheckForAdd(index);
        Node<E> newNode;
        if (index == size) {
            Node<E> oldLast = this.last;
            last = new Node<E>(last, element, null);
            if (oldLast == null) {
                first = last;
            } else {
                oldLast.next = last;

            }

        } else {
            Node<E> newNextNode = findNode(index);
            Node<E> newPrevNode = newNextNode.prev;
            newNode = new Node<E>(newPrevNode, element, newNextNode);
            newNextNode.prev = newNode;

            if (newPrevNode == null) {
                first = newNode;
            } else {
                newPrevNode.next = newNode;
            }
        }

        size++;
    }

    @Override
    public E remove(int index) {
        Node<E> delNode = findNode(index);
        Node<E> delPrevNode =delNode.prev;
        Node<E> delNextNode = delNode.next;

        if (delPrevNode == null) {
            first = delNextNode;
        } else {
            delPrevNode.next = delNextNode;
        }


        if (delNextNode == null) {
            last = delPrevNode;
        } else {
            delNextNode.prev = delPrevNode;
        }
        size--;
        return delNode.element;
    }

    @Override
    public int indexOf(E element) {
        if (element == null) {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (node.element == null) return i;

                node = node.next;
            }
        } else {
            Node<E> node = first;
            for (int i = 0; i < size; i++) {
                if (element.equals(node.element)) return i;

                node = node.next;
            }
        }
        return ELEMENT_NOT_FOUND;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("size=").append(size).append(", [");
        Node<E> node = first;
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(node.element);

            node = node.next;
        }
        string.append("]");
        return string.toString();
    }
}

小结

与单向链表的比较

删除

单向链表的复杂度为:
总规模:1+2+3+4+…n=(n+1)*n/2=n/2+n^2/2
除n求平均复杂度=(n+1)/2=>1/2+n/2

双向链表的复杂度为:
总规模:(1+2+3+4+…n/2)*2=n/2+n^2/4
除n求平均复杂度=1/2+n/4

综上所述:
双向链表操作过程将近缩减一半

与动态数组的比较

动态数组:开辟、销毁内存空间相对较小,但可能出现内存空间浪费的情况

双向链表:开辟、销毁内存空间相对较多,但不会出现内存空间浪费的情况

使用场景分析

如果频繁在尾结点add、remove操作,动态数组和双向链表均可
如果频繁在头结点add、remove操作,双向链表合适
如果需要频繁查询指定位置的元素,动态数组合适

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
package 单双向链表; /** * 单向链表增删改查操作 * */ public class LinkTest { public static void main(String[] args) { Link l=new Link(); l.addNode("A"); l.addNode("B"); l.addNode("C"); l.addNode("D"); l.addNode("E"); l.printNode(); System.out.println("\n是否包含D:"+l.contains("D")); System.out.println("==========删除之前的内容=========="); l.printNode(); System.out.println("\n==========删除之后的内容=========="); l.deleteNode("A"); l.printNode(); } } class Link{//链表的完成类 class Node{//保存每个节点 private String data;//节点内容 private Node next;//下一个节点 public Node(String data){ this.data=data; } public void add(Node newNode) {//将节点加入到合适的位置 if(this.next==null){ this.next=newNode; }else{ this.next.add(newNode); } } public void print() {//输出节点的内容 System.out.print(this.data+"\t"); if(this.next!=null){ this.next.print();//递归调用输出 } } public boolean search(String data){//内部搜索的方法 if(data.equals(this.data)){ return true; }else{ if(this.next!=null){//向下继续判断 return this.next.search(data); }else{ return false; } } } public void delete(Node previous, String data) { if(data.equals(this.data)){//找到了匹配的节点 previous.next=this.next;//空出当前的节点 }else{ if(this.next!=null){ this.next.delete(this, data);//继续查找 } } } } private Node root;//链表中的根节点 public void addNode(String data){//增加节点 Node newNode=new Node(data); if(root==null){ root=newNode; }else{ root.add(newNode); } } public void printNode(){//链表的输出 if(root!=null){ root.print(); } } public boolean contains(String name){//判断元素是否存在 return this.root.search(name); } public void deleteNode(String data){//链表删除节点 if(this.contains(data)){ if(this.root.data.equals(data)){//如果是根节点 this.root=this.root.next;//修改根节点 }else{ this.root.next.delete(root,data);//把下一个节点的前节点和要删除的节点内容一起传入 } } } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

shark-chili

您的鼓励将是我创作的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值