无头双向非循环链表模拟

双向链表概述

不同于单链表,双链表可以通过某一结点找到它的前驱,是双向的。并且双链表不仅有头指针head,还有尾指针last
而单链表只能找后继,是单向的

  • 双链表图示
    ![[Pasted image 20231010154127.png]]

1) 构建结点

private static class LindNode {  
    int val;  
    LindNode prev;  
    LindNode next;  
  
    LindNode(int val) {  
        this.val = val;  
    }  
}  
  
private LindNode head; // 链表头部  
private LindNode last; // 链表尾部

无头链表但有头结点head 解释:
无头单链表的模拟#头结点注释


2) 头插法

  • 思路:
    1. 将新结点node与头结点双向连接:即 node 后继更改为原头结点head,head前驱更改为新结点node,然后头结点更换为新结点
    2. 注意事项: 如果链表一开始为空,在head引用其前驱时,就会发生空指针异常! 所以需要添加一个判定条件:当链表为空时,新结点即为头结点和尾结点
//头插法  
public void addFirst(int data) {  
    LindNode node = new LindNode(data);  
    if (head == null) {  
        head = node;  
        last = node;  
    }else {  
        node.next = head;  
        head.prev = node;  
        head = node;  
    }  
}
  • 图解:
    在这里插入图片描述

3) 尾插法

  • 思路:与头插法相差无几
    1. 将新结点与尾结点 双向连接,尾结点更改为新结点
    2. 同理,当链表为空,会发生空指针异常。所以依旧是第一个结点手动创建
//尾插法  
public void addLast(int data) {  
    LindNode node = new LindNode(data);  
    if (head == null) {  
        head = node;  
        last = node;  
    } else {  
        last.next = node;  
        node.prev = last;  
        last = node;  
    }  
}
  • 图解:
    ![[Pasted image 20231010161902.png]]

4) 插入指定位置(第一个数据节点为0号下标)

  • 思路:与单向链表大同小异,只不过是多了前驱相连接
    1. 判断输入位置 index是否合法
    2. 找到 index 下标结点,新结点与 index的前驱和 index本身 双向连接 即可

  • 图解:假设插入的位置是1下标
    • 单向链表进行插入,是需要定位到 1 下标的前驱结点即 0 结点。然后基于 0结点来进行插入操作

    • 在双向链表,1 结点本身就已经知道它的前驱结点,所以我们直接找到 index 下标结点即可,不用定位到其前驱结点。

![[Pasted image 20231010164441.png]]

private LindNode findIndexPos(int index) {  
    LindNode cur = head;  
    while (index != 0) {  
        cur = cur.next;  
        index--;  
    }  
    return cur;  
}
  • 找到之后,将新结点与插入前后结点 双向连接
//任意位置插入,第一个数据节点为0号下标  
public boolean addIndex(int index, int data) {  
    if (index < 0 || index > size()) {  
        return false;  
    }  
    // 插入位置为头位置,头插法即可  
    if (index == 0) {  
        addFirst(data);  
        return true;    
    }  
    // 插入位置为最后,尾插法即可  
    if (index == size()) {  
        addLast(data);  
        return true;    
    }  
    // 插入位置为中间  
    LindNode node = new LindNode(data);  
    // 找到index下标位置  
    LindNode cur = findIndexPos(index);  
  
    //插入操作  
    node.next = cur;  
    node.prev = cur.prev;  
    cur.prev.next = node;  
    cur.prev = node;  
    return true;
}
  • 图解:
    ![[Pasted image 20231010170353.png]]

5) 查找关键字key是否在链表当中


6) 删除第一次出现关键字为key的节点

  • 思路:单指针
    1. 定义一个单指针 cur 遍历链表,寻找key
    2. 找到后,将 cur 的 前驱和后继双向连接,即删除成功,直接return
// 前驱与后继双向连接
cur.prev.next = cur.next;  
cur.next.prev = cur.prev;
  • 但如果是删除头结点和尾结点或者链表只有一个结点,此时就会发生空指针异常。
  • 基于第2点,延伸拓展,考虑当 单指针cur 指向 头head与尾last时,增加判断条件,防止出现空指针异常。一步一步完善代码
//删除第一次出现关键字为key的节点  
public void remove(int key) {  
    if (head == null) {  
        return;  
    }  
    LindNode cur = head;  
    while (cur != null) {  
        if (cur.val == key) {  
            if (cur == head) {  
	            // 删除头结点
                head = head.next;  
                if (head == null) {
	                // 当链表只有头结点  
                    last = null;  
                }else {  
                    head.prev = null;  
                }  
            }else {  
                if (cur == last) {  
	                // 删除尾结点
                    last = cur.prev;  
                    last.next = null;  
                }else {  
                    cur.prev.next = cur.next;  
                    cur.next.prev = cur.prev;  
                }  
            } 
            // 删除成功,后面的就不删了,直接返回
            return; 
        }  
        cur = cur.next;  
    }  
}

7) 删除所有值为key的节点

  • 将第六点删一次就return语句去掉即可
// 删除所有值为key的节点 
public void remove(int key) {  
    if (head == null) {  
        return;  
    }  
    LindNode cur = head;  
    while (cur != null) {  
        if (cur.val == key) {  
            if (cur == head) {  
	            // 删除头结点
                head = head.next;  
                if (head == null) {
	                // 当链表只有头结点  
                    last = null;  
                }else {  
                    head.prev = null;  
                }  
            }else {  
                if (cur == last) {  
	                // 删除尾结点
                    last = cur.prev;  
                    last.next = null;  
                }else {  
                    cur.prev.next = cur.next;  
                    cur.next.prev = cur.prev;  
                }  
            } 
            // 删掉return语句,cur一直寻找key,找到一个删一个
        }  
        cur = cur.next;  
    }  
}

8) 得到单链表的长度


9) 清空链表

  • 思路:单指针
    1. 单指针cur 遍历链表,将每一个结点的 prev域和 next 域置空,最后再将头结点与尾结点置空即可
    2. 光有一个 cur 遍历,cur 前驱后继均置空后,就拿不到后续结点的地址。此时需要再添加一个定位指针 curNext 用于拿到后续结点地址
// 清空链表
public void clear() {  
    LindNode cur = head;  
    while (cur != null) {  
        LindNode curNext = cur.next;  
        cur.next = null;  
        cur.prev = null;  
        cur = curNext;  
    }  
    head = last = null;  
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Prectie.RTE

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

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

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

打赏作者

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

抵扣说明:

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

余额充值