[数据结构](双链表的实现,以及双链表和单链表之间的比较,链表和顺序表的优劣)

🥇双链表简单介绍:

博主在前面介绍了单链表,并实现了它的基本功能,详细请看博客单链表,相信有一点链表基础的同学肯定会知道,单链表的每个节点都有两部分组成那就是数据域和指针域,指针域指向下一个节点中数据域的地址。而双链表一个节点中包含三个部分,数值域,指向后继节点的指针,还有指向前驱节点的指针。它在单链表的基础上优化了很多,例如尾插法等就不用了逐个遍历链表节点,直接就可以找到链表的为节点,实现与添加的新节点连接。
那让我们看看他的庐山真面目吧
在这里插入图片描述

🥇双链表基本实现和各种功能实现:

新建MydoubleLinkedList.java文件在该文件中实现双向链表的所有基础操作
新建TestDemo。java文件在文件中实现双向链表的测试,测试双向链表的功能

 class Node{
    public int val; //数值域
    public Node next; //后继
    public Node prev; //前驱
    public Node(int val){
        this.val = val;
    }
}
public class MydoubleLinkedList {
    //创建头节点
    public Node head;
    //创建尾节点
    public Node tail;
    }

双链表之打印链表:

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

双链表之求链表长度

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

双链表之寻找链表中是否含有某个数字:

    public boolean isContains(int val){
        if(this.head == null){
            return false;
        }
        Node cur = this.head;
        while(cur!=null){
            if(cur.val == val){
                return true; 
            }
            cur = cur.next;
        }
        return false;
    }

双链表之头插法:
终于到我要给大家介绍的重点了,前面的三种功能和单链表基本没有区别,没有使用双向链表中的前驱节点。
💡算法思想:

  1. 第一步还是判断链表是否为空,为空就返回新加节点node
  2. 将新节点插入链表头部,新的头节点就要发生变化,它的后继node.next = this.head;也就是新链表的头节点的后继指针域指向原链表头节点的地址当然原链表头节点的前驱也就变成了新加入的node,即 this.head.prev = node,然后新链表真正的头节点 this.head = node;

看图说话:
在这里插入图片描述
💯代码:

   public Node addFirst(int val){
        Node node = new Node(val);
        if(this.head == null){
            return node;
        }
        node.next = this.head;
        this.head.prev = node;
        this.head = node;
        return head;
    }

双链表之尾插法:
💡算法思想:

  1. 还是判断链表是否为空,为空就返回新加节点node
  2. 因为在建立链表时,已经规定了尾节点tail,所以在这里不用在链表中从头节点遍历,找到尾节点。我们直接就可以让链表的尾节点tail的后继指针指向新加节点,即this.tail.next = node,还要把新节点的前驱指针指向原链表的尾节点node.prev = this.tail,然后新节点就变成链表中新的尾节点 即 this.tail = node

看图说话:
在这里插入图片描述
💯 代码:

     public Node addLast(int val){
        Node node = new Node(val);
        if(this.head == null){
            return node;
        }
        this.tail.next = node;
        node.prev = this.tail;
        this.tail = node;
        return this.head;
    }

双链表之在链表任意节点添加
💡算法思想:

  1. 判断传来的数组下标是否合法,如果不合法就返回下标不合法
  2. 如果下标为0,也就是头插法,调用头插法方法就是了
  3. 如果下标为链表的长度,那么就直接调用尾插法就是了
  4. 如果所插节点的下标既不是0也不是和链表等长的下标,那么先遍历到原链表的这个下标让所添加节点的后继指针指向下标这个节点,即node.next = cur,然后让原下标节点的前驱节点的后继指针指向所添加节点,即cur.prev.next = node;然后让所添加节点的前驱指针指向原下标节点的前驱节点,即node.prev = cur.prev;最后让原下标节点的前驱指针指向所添加节点,即cur.prev = node;

看图说话:
在这里插入图片描述
💯 代码:

 public void addNode(int index,int val){

        if(index < 0 || index > size()){
            System.out.println("下标不合法!!!");
            return;
        }
        if(index == 0){
            addFirst(val);
            return;
        }
        if(index == size()){
            addLast(val);
            return;
        }
        //如果添加的位置不是下标为0,或者是链表最后一位添加
        //先找到具体下标
        Node node = new Node(val);
        Node cur = findIndex(index);
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        node.prev = node;
    }
    public Node findIndex(int index){
        int count = 0;
        Node cur = this.head;
        while(count != index && cur != null){
            count++;
            cur = cur.next;
        }
        return cur;
    }

双向链表之删除第一次出现的的val值
💡 算法思想:

  1. 在删除节点时,我们不需要像单链表一样从后向前找到链表可删节点的前驱节点。我们只需要找到这个可删节点,让可删节点的前驱节点的后继指针指向可删节点的后继节点,即 node.prev.next = node.next;让可删节点的后继节点的前驱指针指向可删接单的前驱node.next.prev = node.prev;
  2. 当头节点为可删节点,让链表的新头节点向原链表头节点的后继节点,即this.head = this.head.next,还有将新头节点的前驱置为null,即this.head.prev = null
  3. 当链表的尾节点为可删节点,那么就让尾节点的前驱节点的后继指针为null,即this.tail.prev.next = null,也可以写成this.tail.prev.next = this.tail.next,然后指向新的尾节点 this.tail = this.tail.prev

看图说话:
在这里插入图片描述
💯 代码:

     public void remove(int key) {
        Node cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(cur == this.head) {
                    this.head = this.head.next;
                    if(this.head == null) {//防止只有1个节点的
                        this.tail = null;
                    }else {
                        this.head.prev = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    //尾巴节点
                    if(cur.next == null) {
                        this.tail = cur.prev;
                    }else {
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }else {
                cur = cur.next;
            }
        }
    }

双链表之删除链表中所有的val值

💡算法思想:

  • 和删除节点一样只是要去掉代码中的return,让代码循环,直到把val值全部删除完。

💯 代码:

 public void remove(int key) {
        Node cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                //判断是不是头节点
                if(cur == this.head) {
                    this.head = this.head.next;
                    if(this.head == null) {//防止只有1个节点的
                        this.tail = null;
                    }else {
                        this.head.prev = null;
                    }
                }else {
                    cur.prev.next = cur.next;
                    //尾巴节点
                    if(cur.next == null) {
                        this.tail = cur.prev;
                    }else {
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }else {
                cur = cur.next;
            }
        }
    }

🥇 双链表和单链表的比较

  • 双链表和单链表比较,虽然双链表添加了一个前驱指针域,但是他在实现某些功能时,是真的比单链表方便,比如我们要删除节点就不需要遍历可删节点的前驱节点,就少了一步遍历链表的步骤,还有双向链表可以双向遍历,单链表只可以从头节点依次遍历到链表的尾节点,为实现功能带来诸多不便。

🥇链表和顺序表的优劣

和数组相比,链表更适合储存一个大小动态变化的数据集,如果需要在一个数据集中频繁的添加新的数据并且不需要考虑数据集的顺序,那么可以用链表来实现这个数据集,链表中的插入操作可以用O(1)的时间来实现,其次链表的删除操作也可以用O(1)的时间来实现,所以是当要实现某些数据集的插入或删除时,可以考虑链表使用,当时在查找时链表又有了弊端,他只能从链表的头节点遍历到链表的尾节点找到要查找的数值,时间复杂度为O(n).

和链表相比,数组在读取数值时,用着很大的优势,可以凭借数组下标来读取数组中的每个数字,但是要实现添加数字,和删除数字时,要移动大量的数值,并且时间复杂度为O(n),还有在创建数组时,要预先指定数组的容量大小,然后根据容量的大小分配内存,即使只在数组中存储一个数字,也需要为所有的数据预先分配内存,依次数组的空间利用率不高,可能会有空闲的空间没有得到充分使用,并且当数组容量不够时,需要重新分配一个较大的空间,通常增加后的数组容量时原来的两倍。每次扩充数组容量时,都会有大量操作,这是时间性能有负面影响。

  • 24
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小周学编程~~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值