• 链表(3):双链表


    引入

    我们之前学的单向链表有什么缺点呢? 

    缺点:后一个节点无法看到前一个节点的内容

    那我们就多设置一个格子prev用来存放前面一个节点的地址,第一个节点的prev存最后一个节点的地址(一般是null)

    这样一个无头双向链表就造好啦😊

    老规矩,手搓双链表

    初始

    和单链表一样,我们也是实现IList接口的方法

    初始化差不多,可以去看我前面的博客

    数据结构:链表(1)_cx努力编程中的博客-CSDN博客

    跟前驱没关系的函数我先放进来

    1. @Override
    2. public boolean contains(int key) {
    3. ListNode cur = this.head;
    4. while(cur != null){
    5. if(cur.val == key){
    6. return true;
    7. }
    8. cur = cur.next;
    9. }
    10. return false;
    11. }
    12. @Override
    13. public int size() {
    14. int count = 0;
    15. ListNode cur = this.head;
    16. while (cur != null) {
    17. count++;
    18. cur = cur.next;
    19. }
    20. return count;
    21. }
    22. @Override
    23. public void display() {
    24. ListNode cur = head;
    25. while(cur != null){
    26. System.out.print(cur.val + " ");
    27. cur = cur.next;
    28. }
    29. System.out.println();
    30. }

    插入

    头插法

    实例化一个node,如果只有一个节点的话就把head和last都放在这个节点的下面

    node.next = head;

    head.prev = node;

    head = node;//把head的位置放到node这里

    1. public void addFirst(int data) {
    2. ListNode node = new ListNode(data);
    3. if(head == null){
    4. head = node;
    5. last = node;
    6. }else{
    7. node.next = this.head;
    8. head.prev = node;
    9. head = node;
    10. }
    11. }

    尾插法

    跟单链表不一样的是,双链表有last,所以时间复杂度是O(1)

    1. @Override
    2. public void addLast(int data) {
    3. ListNode node = new ListNode(data);
    4. if(head == null){
    5. head = node;
    6. last = node;
    7. }else{
    8. last.next = node;
    9. node.prev = head;
    10. last = node;
    11. }
    12. }

    指定位置插入

    比如我们实例化一个节点,想要将其插入到1和2之间

    那么我们就要将cur移动到2的位置(走index步)

    这个cur的前驱(节点1)的next就是这个新节点的地址

    node.next = cur 

    cur.prev.next = node(0x123)

    node.prev = cur.prev

    cur.prev = node

    代码:

    1. @Override
    2. public void addIndex(int index, int data) {
    3. //检查index是否异常
    4. int len = size();
    5. if(index < 0 || index > len){
    6. System.out.println("异常索引"+index);
    7. return;
    8. }
    9. if(index == 0){
    10. //头插法
    11. addFirst(data);
    12. return;
    13. }
    14. if(index == len){
    15. //尾插法
    16. addLast(data);
    17. return;
    18. }
    19. ListNode cur = findIndex(index);
    20. ListNode node = new ListNode(data);
    21. node.next = cur;
    22. cur.prev.next = node;
    23. node.prev = cur.prev;
    24. cur.prev = node;
    25. }

     删除

    假如删除中间45这个节点,先让cur走到这个节点,再让45前一个节点的地址等于45后一个节点地址,让45后一个节点的前驱等于45前一个节点的地址

    但是假如要删除的是13这个节点(头节点),那么cur.prev.next = cur.next这行代码有问题

    所以我们这么干,直接把头节点的后一个节点的前驱prev置为null就行了

    1. if(cur == head){
    2. head = head.next;
    3. head.prev = null;

    假如要删除的是56这个节点(尾节点),那么cur.next.prev = cur.prev这行代码有问题

    那我们这么干,直接把尾节点的前一个节点的next置为null就行了

    cur.prev.next = cur.next;

    last = last.prev;

    我们发现尾节点的删除和中间节点删除有一行代码是一样的

    cur.prev.next = cur.next;

    我们可以把它作为这二者的公用代码

    1. cur.prev.next = cur.next;
    2. if(cur.next == null){
    3. last = last.prev;
    4. }else {
    5. cur.next.prev = cur.prev;
    6. }

    其实这段代码还存在问题

    当链表只有一个元素的时候,

    head = head.next //这行代码一走完表明head此时已经是空的了,下一行代码还说head.prev就根本不存在(注意:head.prev = null是赋值操作,首先你得有head.prev才能进行赋值)

    更改代码:

    整个remove代码

    1. @Override
    2. public void remove(int key) {
    3. ListNode cur = head;
    4. while(cur != null){
    5. if(cur.val == key){
    6. if(cur == head){
    7. head = head.next;
    8. if(head == null){
    9. last = null;
    10. }else{
    11. head.prev = null;
    12. }
    13. }else {
    14. cur.prev.next = cur.next;
    15. if(cur.next == null){
    16. last = last.prev;
    17. }else {
    18. cur.next.prev = cur.prev;
    19. }
    20. }
    21. return;
    22. }else{
    23. cur = cur.next;
    24. }
    25. }
    26. }

    删除指定值的所有相同值的节点

    跟上面的代码很像,区别就是删除之后不要return,让cur继续往下走

    1. @Override
    2. public void removeAllKey(int key) {
    3. ListNode cur = head;
    4. while(cur != null){
    5. if(cur.val == key) {
    6. if (cur == head) {
    7. head = head.next;
    8. if (head == null) {
    9. last = null;
    10. } else {
    11. head.prev = null;
    12. }
    13. } else {
    14. cur.prev.next = cur.next;
    15. if (cur.next == null) {
    16. last = last.prev;
    17. } else {
    18. cur.next.prev = cur.prev;
    19. }
    20. }
    21. }
    22. cur = cur.next;
    23. }
    24. }

    清除链表

    暴力方法:

    head = null;  last = null;

    细致方法:

    定义一个cur,遍历整个链表,每到一个节点就把prev和next都置为空

    1. @Override
    2. public void clear() {
    3. ListNode cur = head;
    4. while(cur!=null){
    5. ListNode curNext = cur.next;//防止将这个节点的next置为空之后,cur没办法继续遍历
    6. cur.prev = null;
    7. cur.next = null;
    8. cur = curNext;
    9. }
    10. }

    总结

    ArrayList和LinkedList的区别是?(易考点)

    1.如果是经常根据下标查找的(随机访问),使用顺序表(ArrayList)或者数组

    ArrayList尾插/删除速度比较快,头插/中间插入/删除比较慢(元素要进行搬运)

    2.LinkedList底层是一个链表,不能进行随机访问

    进行头插/头删/尾插/尾删操作是O(1)复杂度,进行查找/中间位置插入删除都是O(N)复杂度

    相比于ArrayList唯一的优势,就是能快速进行头删(在实现队列的时候比较有用)

    3.用LinkedList是否遍历速度更快呢?

    不是的,链表访问下一个元素要通过next引用,相比于顺序表的下标++多了依次内存访问的过程

    而下标++通常会优化到寄存器中完成

  • 相关阅读:
    基于香橙派和SU-03T 使用Linux实现语音控制刷抖音
    逆向-破零ARM 64位版本
    微信公众号-遇到的问题
    基于RT-Thread的智能家居助手
    IDEA 在远程 Tomcat 上运行项目(亲身避坑版)
    状态管理库-vuex
    Laravel 创建自己的 Facade 扩展 geoip 根据 IP 获取国家、地域、城市信息
    【附源码】计算机毕业设计JAVA成绩分析系统
    漏洞复现--企望制造ERP系统 RCE
    怎么防止U盘复制电脑文件
  • 原文地址:https://blog.csdn.net/hellg/article/details/133813213