线性表 - 链表(单链表)

线性表 - 链表(单链表)

1.1 链表的介绍

  • 链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成

  • 链表中数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域

  • 单链表

    • 单向链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next
      在这里插入图片描述
      存储原理
  • 数组在内存中的存储方式是顺序存储(连续存储)链表在内存中的存储方式则是随机存储(链式存储)

  • 链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间
    在这里插入图片描述

  • 链表的第1个节点被称为头节点(3),没有任何节点的next指针指向它,或者说它的前置节点为空头结点用来记录链表的基地址。有了它,我们就可以遍历得到整条链表链表的最后1个节点被称为尾节点(2),它指向的next为空

1.2 链表常用操作

操作

  • 查找节点

    • 在查找元素时,链表只能从头节点开始向后一个一个节点逐一查找
      在这里插入图片描述
  • 更新节点

    • 找到要更新的节点,然后把旧数据替换成新数据
      在这里插入图片描述
  • 插入节点

    • 尾部插入

      • 把最后一个节点的next指针指向新插入的节点即可
        在这里插入图片描述
    • 头部插入

      • 第1步,把新节点的next指针指向原先的头节点
      • 第2步,把新节点变为链表的头节点
        在这里插入图片描述
    • 中间插入

      • 第1步,新节点的next指针,指向插入位置的节点

      • 第2步,插入位置前置节点的next指针,指向新节点
        在这里插入图片描述

      • 只要内存空间允许,能够插入链表的元素是无限的,不需要像数组那样考虑扩容的问题

  • 删除节点

    • 尾部删除

      • 把倒数第2个节点的next指针指向空即可
        在这里插入图片描述
    • 头部删除

      • 把链表的头节点设为原先头节点的next指针即可
        在这里插入图片描述
    • 中间删除

      • 把要删除节点的前置节点的next指针,指向要删除元素的下一个节点即可
        在这里插入图片描述

1.3 编写代码实现链表常用操作

package com.lagou.entity;

/**
 * @author 云梦归遥
 * @date 2022/5/12 11:34
 * @description 单链表
 */
// 对单链表进行相关实现
public class SingleLink {
    // 单链表
    public class MyLink{
        private int value; // 数据域
        private MyLink link; // 指针域

        public MyLink(int num){
            this.value = num;
            this.link = null;
        }
        public MyLink(int num, MyLink node){
            this.value = num;
            this.link = node;
        }
    }

    // 记录链表的长度(因为头结点的数据域保存着链表的信息)
    private MyLink head = new MyLink(0, null); // 头结点

    // 增加节点
    // 尾插增加节点(遍历获取最后一个节点)
    public void addNodeByOrder(MyLink node){
        MyLink temp = head; // 创建临时节点,进行链表的遍历
        // 更新链表长度
        if (head.value == 0){
            // 空链表
            head.link = node; // 更新指针域
        } else {
            // 链表不为空,则需要找到链表的尾节点,进行更新
            while (true){
                if (temp.link == null){
                    break;
                } else {
                    temp = temp.link; // 继续遍历下一个节点
                }
            }
            // 此时获取到了尾节点
            temp.link = node; // 更新指针域
        }
        head.value++; // 更新链表长度
    }

    // 头插增加节点(插入到头节点的后面,首元节点的前面)
    public void addNodeByHead(MyLink node){
        if (head.value == 0){
            // 空链表
            head.link = node; // 更新指针域
        } else {
            // 进行指针域,链表长度的更新
            node.link = head.link;
            head.link = node;
        }
        head.value++;
    }

    // 中间插入
    public void addNodeByMiddle(int index, MyLink node){
        MyLink temp = head; // 创建临时节点,进行链表的遍历
        if (head.value <= index){
            while (true){
                if (temp.link == null){
                    break;
                } else {
                    temp = temp.link; // 继续遍历下一个节点
                }
            }
            temp.link = node;
        } else {
            // 遍历到指定的节点
            for (int i = 1; i <= index; i++){
                temp = temp.link;
            }
            node.link = temp.link;
            temp.link = node;
        }
        head.value++;
    }

    // 删除节点
    // 尾删
    public MyLink deleteByTail(){
        MyLink temp = head; // 创建临时节点,进行链表的遍历
        if (head.value == 0){
            return null;
        } else {
            // 循环遍历尾节点
            while (true){
                if (temp.link != null && temp.link.link == null){
                    break;
                } else {
                    temp = temp.link;
                }
            }
            MyLink deleteNode = temp.link;
            temp.link = null;
            head.value--;
            return deleteNode;
        }
    }

    // 头删
    public MyLink deleteByHead(){
        MyLink deleteNode = null;
        if (head.value == 0){
            return null;
        } else if (head.value == 1){
            deleteNode = head.link;
            head.link = null;
            head.value--;
            return deleteNode;
        } else {
            deleteNode = head.link;
            head.link = head.link.link;
            head.value--;
            return deleteNode;
        }
    }

    // 中间删除
    public MyLink deleteByMiddle(int index){
        MyLink temp = head;
        MyLink deleteNode = null;
        if (head.value < index){
            return null;
        } else {
            for (int i = 1; i < index; i++){
                temp = temp.link;
            }
            if (head.value == index){
                deleteNode = temp.link;
                temp.link = null;
                return deleteNode;
            } else {
                deleteNode = temp.link;
                temp.link = temp.link.link;
                return deleteNode;
            }
        }
    }

    // 更新
    public MyLink update(int index, int value){
        MyLink temp = head;
        MyLink updateNode = null;
        if (head.value <= index){
            return null;
        } else{
            for (int i = 1; i <= index; i++){
                temp = temp.link;
            }
            updateNode = temp;
            temp.value = value;
            return updateNode;
        }
    }

    // 查询
    public MyLink select(int index){
        MyLink temp = head;
        MyLink selectNode = null;
        if (head.value <= index){
            return null;
        } else{
            for (int i = 1; i <= index; i++){
                temp = temp.link;
            }
            selectNode = temp;
            return selectNode;
        }
    }

    // 查询整个链表
    public String select(){
        MyLink temp = head;
        StringBuilder stringBuilder = new StringBuilder();
        if (head.value == 0){
            return null;
        } else{
            stringBuilder.append("【链表长度:" + head.value + "】");
            temp = temp.link;
            while (true){
                if (temp.link == null){
                    stringBuilder.append(temp.value);
                    break;
                } else {
                    stringBuilder.append(temp.value + " => ");
                    temp = temp.link;
                }
            }
            return stringBuilder.toString();
        }
    }
}

进行测试上述功能

package com.lagou.test;

import com.lagou.entity.SingleLink;

/**
 * @author 云梦归遥
 * @date 2022/5/12 13:25
 * @description
 */
public class SingleLinkTest {
    public static void main(String[] args) {
        SingleLink singleLink = new SingleLink();
        SingleLink.MyLink myLink1 = singleLink.new MyLink(1);
        SingleLink.MyLink myLink2 = singleLink.new MyLink(2);
        SingleLink.MyLink myLink3 = singleLink.new MyLink(3);
        // 尾插 3 个节点
        singleLink.addNodeByOrder(myLink1);
        singleLink.addNodeByOrder(myLink2);
        singleLink.addNodeByOrder(myLink3);
        System.out.println(singleLink.select());
        SingleLink.MyLink myLink4 = singleLink.new MyLink(4);
        // 头插 节点
        singleLink.addNodeByHead(myLink4);
        SingleLink.MyLink myLink5 = singleLink.new MyLink(5);
        // 中间插 1 个节点
        singleLink.addNodeByMiddle(2, myLink5);
        System.out.println(singleLink.select());
        // 更新节点
        singleLink.update(4, 6);
        System.out.println(singleLink.select());
        singleLink.deleteByTail(); // 尾删 节点
        singleLink.deleteByHead(); // 头删 节点
        singleLink.deleteByMiddle(2); // 中间删 节点
        System.out.println(singleLink.select());
    }
}

测试结果
在这里插入图片描述

1.4 链表总结

  • 时间复杂度

    • 查找节点 : O(n)
    • 插入节点:头插O(1),尾插O(1),中间插O(n)
    • 更新节点:O(n)
    • 删除节点:头删O(1),尾删O(1),中间删O(n)
  • 优缺点

    • 优势
      • 插入、删除、更新效率高,省空间
    • 劣势
      • 查询效率较低,不能随机访问
  • 应用

    • 链表的应用也非常广泛,比如树、图、Redis的列表、LRU算法实现、消息队列等
  • 数组与链表的对比

    • 数据结构没有绝对的好与坏,数组和链表各有千秋
    • 数组的优势在于能够快速定位元素,对于读操作多、写操作少的场景来说,用数组更合适一些
    • 链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适一些
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值