Algorithms_基础数据结构(03)_线性表之链表_双向链表

在这里插入图片描述


大纲图

在这里插入图片描述


双向链表

Algorithms_基础数据结构(02)_链表&链表的应用案例之单向链表中梳理了 单向链表的基本操作,接下来我们继续来看下双向链表吧。


双向链表的基本结构

在这里插入图片描述

单向链表只有一个方向,结点只有一个后继指针next指向后面的结点。

双向链表,顾名思义,它支持两个方向,每个结点不止有一个后继指针next指向后面的结点,还有一个前驱指针prev指向前面的结点。

双向链表需要额外的两个空间来存储后继结点前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。

虽然两个指针比较浪费存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性。那相比单链表,双向链表适合解决哪种问题呢?

-----> B+Tree:Mysql索引 叶子节点 双向链表


双向链表的基本操作

头插

在这里插入图片描述


尾插

在这里插入图片描述


中间部位插入

在这里插入图片描述


删除头部

在这里插入图片描述


删除尾部

在这里插入图片描述


删除中间位置的数据

在这里插入图片描述


查找

通常,双向链表同单链表一样,都仅有一个头指针。所以双链表查找指定元素的实现同单链表类似,都是从表头依次遍历表中元素,直到找到对应的元素为止。


更新

更改双链表中指定结点数据域的操作那必须要先查找到该节点,因此是在查询的基础上完成的。------>即:通过遍历找到存储有该数据元素的结点,直接更改其数据域即可。


Code

/**
 * @author 小工匠
 * @version v1.0
 * @create 2020-01-03 06:08
 * @motto show me the code ,change the word
 * @blog https://artisan.blog.csdn.net/
 * @description
 **/

public class ArtisanDoubleLinkedList {

    private ArtisanNode head; // head节点
    private ArtisanNode tail; // tail节点  为了方便直接获取tail节点,省去每一次都要遍历的操作

    private int size; // 链表元素数量
    /**
     * 双向链表初始化
     */
    public ArtisanDoubleLinkedList() {
        this.head = null;
        this.tail = null;
    }

    /**
     * 头插
     * @param data
     */
    public  void add2Head(Object data) {
        ArtisanNode node = new ArtisanNode(data); // 新的Node
        if (this.head == null) { // 如果head节点为null,  head和tail节点均为这个新的node节点
            this.tail = node;
        } else {// 将原来的头节点的前驱节点指向node, 将新节点的后驱节点指向head
            this.head.pre = node;
            node.next = head;

        }
        this.head = node; // 将新的节点置为head节点
        size++;
    }


    /**
     * 尾插 (低效)
     *
     * @param data
     */
    public void add2Tail(Object data) {// 从头部遍历,找到最后的节点,然后加到尾部

        ArtisanNode node = new ArtisanNode(data); // 要加入的节点
        ArtisanNode currentNode = head;

        if (currentNode == null){
            add2Head(data);
        }

        while(currentNode !=null){
            if (currentNode.next == null){ // 说明找到了当前的tail节点
                currentNode.next = node ;// 将当前tail节点的next指针指向新的tail节点
                node.pre = currentNode; //新的tail节点的pre指向当前tail节点节点
                this.tail = node;
                break;
            }else{
                currentNode = currentNode.next;
            }
        }

        size++;
    }


   /**
     * 尾插 (利用tail 无需遍历 效率更高)
     *
     * @param data
     */
    public void add2Tail2(Object data) {// 已经设置tail了,直接用即可,效率更高

        ArtisanNode node = new ArtisanNode(data); // 要加入的节点

        if (this.head == null){
            add2Head(data);
        }else {
            tail.next = node;
            node.pre = tail;
            tail = node;
        }
    }



    /**
     *
     * @param postition
     * @param data
     */
    public void add2Nth(int postition ,Object data) {
        ArtisanNode newNode = new ArtisanNode(data); // 新的Node
        ArtisanNode currentNode = head;
        if (postition == 0 ){ // 如果是0 ,添加到头节点
            add2Head(data);
        }else {
            for (int i = 1; i < postition; i++) { // 找到要插入的位置的前面的节点
                currentNode = currentNode.next;
            }

            // 与后继节点建立双层逻辑关系
            newNode.next = currentNode.next;
            currentNode.next.pre = newNode;

            // 与前置节点建立双层逻辑关系
            currentNode.next = newNode;
            newNode.pre = currentNode;
        }

        size++;
    }

    /**
     * 根据value 查找元素
     * @param data
     * @return
     */
    public ArtisanNode find(Object data){ // 从头遍历
        ArtisanNode currentNode = head;
        while(currentNode != null){
            if (data.equals(currentNode.data)){
                printPreAndNextInfo(currentNode);
                break;
            }else{
                currentNode = currentNode.next;
            }
        }
        return currentNode;
    }


    /**
     * 删除头部节点
     */
    public  void deleteHead(){
        this.head = this.head.next; // 将当前头节点的下一个节点置为头节点
        this.head.pre = null; // 将前置节点置为null

        size--;
    }

    /**
     * 删除尾部节点
     */
    public void deleteTail(){
        ArtisanNode currentNode = this.head;
        ArtisanNode previousNode = null;
        while (currentNode != null){
            if (currentNode.next == null){
                currentNode.pre = null;// 最后一个节点的pre置为置为null
                previousNode.next = null;// 前置节点的next指针置为null
                this.tail = previousNode; // 将当前节点的前一个节点置为tail节点
            }else { // 如果当前节点的next指针指向不为空,则把下个节点置为当前节点,继续遍历
                previousNode = currentNode;// 保存上一个节点的信息
                currentNode = currentNode.next;
            }

        }
    }

    /**
     * 删除指定位置的节点
     * @param position
     */
    public ArtisanNode deleteNth(int position){
        ArtisanNode currentNode = this.head;
        if (position == 0 ){
            deleteHead();
        }else {
            for (int i = 1 ; i < position ; i++){// 找到要删除节点的前一个节点
                currentNode = currentNode.next;
            }
            currentNode.next.next.pre = currentNode; // 将  要删除节点的后一个节点的前驱节点 指向 当前节点(要删除的节点的前一个节点)
            currentNode.next = currentNode.next.next; // 将 要删除节点的前一个节点的next指针指向 要删除节点的后一个节点
        }
        size--;
        return currentNode.next ; // 返回删除的节点
    }

    /**
     * 获取tail节点
     * @return tail节点
     */
    public ArtisanNode getTail(){
        System.out.println("tail节点的值为:" + this.tail.data );
        return this.tail;
    }

    /**
     * 获取head节点
     * @return head节点
     */
    public ArtisanNode getHead(){
        System.out.println("head节点的值为:" + this.head.data );
        return this.head;
    }


    /**
     * 打印链表中的数据
     */
    public void print() {
        ArtisanNode currentNode = this.head;// 从head节点开始遍历
        while (currentNode != null) { // 循环,节点不为空 输出当前节点的数据
            System.out.print(currentNode.data + " -> ");
            currentNode = currentNode.next; // 将当前节点移动到下一个节点,循环直到为null
        }
        System.out.print("null");
        System.out.println();
    }

    /**
     * 打印前后节点信息
     * @param currentNode
     */
    private void printPreAndNextInfo(ArtisanNode currentNode) {
        System.out.println("当前节点:" + currentNode.data);
        if (currentNode.pre != null){
            System.out.println("当前节点【" + currentNode.data + "】的前驱节点:" + currentNode.pre.data);
        }else{
            System.out.println("当前节点【"+ currentNode.data + "】为head节点");
        }
        if (currentNode.next != null){
            System.out.println("当前节点【"+ currentNode.data + "】的后继节点:" + currentNode.next.data);
        }else{
            System.out.println("当前节点【"+ currentNode.data + "】为tail节点");
        }
    }


    public static void main(String[] args) {
        ArtisanDoubleLinkedList doubleLinkedList = new ArtisanDoubleLinkedList();
        doubleLinkedList.add2Head("artisanData96");
        doubleLinkedList.add2Head("artisanData97");
        doubleLinkedList.add2Head("artisanData99");
        doubleLinkedList.add2Head("artisanData98");
        doubleLinkedList.getTail();

        doubleLinkedList.add2Tail("artisanData100");

        doubleLinkedList.getTail();
        doubleLinkedList.print();

        doubleLinkedList.getHead();

//        doubleLinkedList.add2Nth(2,"addedDataByPos");

//        doubleLinkedList.add2Tail2(1);
//        doubleLinkedList.add2Tail2(2);
//        doubleLinkedList.add2Tail2(3);
//        doubleLinkedList.add2Tail2(4);

//        doubleLinkedList.print();
//
//        System.out.println("tail:" + doubleLinkedList.tail.data);
//
//        doubleLinkedList.find("artisanData98");
//        doubleLinkedList.deleteHead();
//        doubleLinkedList.print();
//        doubleLinkedList.find("artisanData99");

//        System.out.println("被删除节点:" + doubleLinkedList.deleteNth(1).data);
//        doubleLinkedList.print();
//        doubleLinkedList.find("artisanData96");
    }


    /**
     * 双向链表中的节点
     */
    class ArtisanNode {
        ArtisanNode pre; // 前驱结点
        Object data; // 数据
        ArtisanNode next;// 后继节点

        public ArtisanNode(Object data) {
            this.data = data;
        }


    }
}



总结

在这里插入图片描述

重要区别:

  • 1.数组简单易用,在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。

  • 2.链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。

  • 3.数组的缺点是大小固定,一经声明就要占用整块连续内存空间。如果声明的数组过大,系统可能没有足够的连续内存空间分配给它, 导致“内存不足(out ofmemory)”。如果声明的数组过小,则可能出现不够用的情况。注意下标越界的问题。

  • 4.动态扩容:数组需再申请一个更大的内存空间,把原数组拷贝进去,非常费时。链表本身没有大小的限制,天然地支持动态扩容,使用的时候也需要考虑占用内存的问题。

发布了782 篇原创文章 · 获赞 1978 · 访问量 411万+

猜你喜欢

转载自blog.csdn.net/yangshangwei/article/details/103812847