数据结构与算法4-链表

目录

链表的定义

链表的特点

单链表

循环链表

双向链表

双向循环链表

代码实现链表

面试题-LRU缓存淘汰算法


在上面数组的数据结果过了之后,接下来轮到链表

无论对于哪种语言,链表都是一个很重要的数据结构

链表的定义

链表通过指针将一组零散的内存块组合在一起,其中,我们把内存块成为链表的“节点”,为了将所有节点串起来,每个链表的节点除了存储数据之外,还需要记录链表的下一个节点地址

链表的结构如图所示

 data+next就是一个节点

链表的特点

1、不需要连续的内存空间

2、有指针引用

常见的链表结构:单向链表、双向链表、循环链表

也正是因为链表的特点,所以链表没有所谓的内存地址计算公式,并且,因为链表每个节点除了数据之外还包含下一个节点的特性,存储相同的数据,链表占用的内存会比数组多

单链表

上面的链表结构示意图其实就是一个单链表

链表不像数组那样有内存地址计算公式,所以如果想要在链表里进行查询,那就是从头开始,一个一个看next,直到找到对应的数据为止,也就是说,时间复杂度是O(n)

链表的插入如何插入呢?插入的地方可以有三个,一个是从头插入,一个是从尾插入,一个是从中间插入,插入头或者尾的时候,我们只需要找到对应的位置,将next指向进行改变就可以了,而插入中间位置不大一样,需要改变插入节点前后的next指针

它和数组的插入明显的不一样就是链表不用移动,链表的插入就是先断开指针,断开前指向当前,当前的next指向断开后,而数组如果插入尾部,其实和链表的效果是一样的,不过链表不需要考虑越界问题,但,需要考虑内存问题

链表的删除,就更简单,直接把断开前的next指向断开后就好了

链表还有两个特殊的节点,头节点和尾节点,头节点用来记录链表的基地址,拿到头节点,我们就可以遍历整个链表,而尾节点特殊的地方是因为,尾节点的next是NULL,表示这是链表的最后一个节点

循环链表

循环链表是一种特殊的单链表,它和单链表唯一的区别就在于尾节点,单链表的尾节点是指向一个空地址NULL,而循环链表的尾节点,指向的是头节点,就像一个环一样, 首尾相连,所以才叫做循环链表

双向链表

所谓双向链表,和单链表不同的是,单链表只有一个指向后置节点的next指针,而双向链表除了存在指向后置节点的指针之外,还存在一个指向前置节点的prev节点,也就是说,通过一个节点,获取完成这一整个链表

当然,多了一个前置指针,占用的内存空间更大了

双向循环链表

双向链表存在一个延伸,那就是双向循环链表,和循环链表相似,也是尾节点的next指向头节点,当然,你可能会说,既然是双向链表,我尾节点一直prev也能获取到头,但是,有没有一种可能,直接尾节点.next更快呢?

代码实现链表

用代码实现链表,其实无非就是节点和每个节点之间的对应关系

我们来构建一个这样的数据结构


public class MyListNodes {

    private ListNode head;
    private int size = 0;


    public class ListNode {
        int value;
        ListNode next;

        public ListNode(int value) {
            this.value = value;
            this.next = null;
        }
    }

}

简单来写其实就是这样的,MyListNodes是我们的链表,ListNode是我们一个个的节点,根据我们上面说的特性,链表拿到一个head,头结点,就可以拿到整个链表,并且可以存储一个链表的长度方便后续的使用

当然,链表是有它自己的一些方法的,我们实现一些经典的常用的方法,比如插入头、插入第n个、删除头、删除第n个、打印,当然,细节之处我们暂时忽略,比如越界问题,某个节点为空的异常等


public class MyListNodes {

    private ListNode head;
    private int size = 0;

    /**
     * @Description: 插入头节点
     * @Author: create by YanXi on 2022/8/26  14:35
     * #Enail: best.you@icloud.com
     *
     * @param value 要插入的值
     * @exception
     * @Return: void
     */
    public void insertHead(int value) {
        ListNode node = new ListNode(value);
        // 不能直接替换,head可能有数据
        node.next = head;
        head = node;
    }

    /**
     * @Description: 插入某一个位置
     * @Author: create by YanXi on 2022/8/26  15:10
     * #Enail: best.you@icloud.com
     *
     * @param value 要插入的值
     * @param position 要插入的位置
     * @exception
     * @Return: void
    */
    public void insertNP(int value, int position) {
        // 如果等于 0 ,说明是头结点
        if (position == 0) {
            insertHead(value);
        } else {
            // 找到要插入的节点的位置
            ListNode nowPostition = head;
            for (int i = 0; i < position; i++) {
                nowPostition = nowPostition.next;
            }
            // 打断链表,插入新节点,并链接断开的点位
            ListNode node = new ListNode(value);
            // 断开后,让新的节点的指针指向原来的下一个节点
            node.next = nowPostition.next;
            // 将上一个节点的下一个节点指向新节点
            nowPostition.next = node;
        }

    }

    /**
     * @Description: 删除头节点
     * @Author: create by YanXi on 2022/8/26  15:12
     * @Email: best.you@icloud.com
     *
     * @param
     * @exception
     * @Return: void
    */
    public void deleteHead() {
        head = head.next;
    }

    /**
     * @Description: 删除某个位置的节点
     * @Author: create by YanXi on 2022/8/26  15:12
     * @Email: best.you@icloud.com
     *
     * @param position 要删除的位置
     * @exception
     * @Return: void
    */
    public void deleteNP(int position) {
        if (position == 0) {
            deleteHead();
        }else {
            ListNode nowPostition = head;
            for (int i = 0; i < position; i++) {
                nowPostition = nowPostition.next;
            }
            nowPostition.next = nowPostition.next.next;
        }
    }

    /**
     * @Description: 打印整个链表
     * @Author: create by YanXi on 2022/8/26  15:12
     * @Email: best.you@icloud.com
     *
     * @param
     * @exception
     * @Return: void
    */
    public void printList() {
        ListNode nowPostition = head;
        System.out.println(nowPostition.value);
        while (nowPostition.next != null) {
            System.out.println(nowPostition.value);
            nowPostition = nowPostition.next;
        }
    }

    /**
     * @Description: 查找指定值的node
     * @Author: create by YanXi on 2022/8/26  15:24
     * @Email: best.you@icloud.com
     *
     * @param queryValue 要查找的值
     * @exception
     * @Return: ListNode
    */
    public ListNode query(int queryValue) {
        ListNode nowPostition = head;
        while (nowPostition.next != null) {
            if (nowPostition.value == queryValue) {
                break;
            }
            nowPostition = nowPostition.next;
        }
        return nowPostition;
    }

    public class ListNode {
        int value;
        ListNode next;

        public ListNode(int value) {
            this.value = value;
            this.next = null;
        }
    }

}

当然, 我们也可以实现一个双向链表,双向链表比单链表多了一个前驱结点

我们实现一下插入头方法就行了,其他的方法其实也就是类似的


public class DoubleListNodes {

    private DoubleNode head; // 头结点
    private DoubleNode tail; // 尾结点

    public DoubleListNodes() {
        head = null;
        tail = null;
    }

    /**
     * @Description: 插入头结点
     * @Author: create by YanXi on 2022/8/28  16:10
     * @Email: best.you@icloud.com
     *
     * @param data 要插入的数据
     * @exception
     * @Return: void
    */
    public void insertHead(int data) {
        DoubleNode node = new DoubleNode(data);
        if (head == null) {
            tail = node;
        } else {
            head.pre = node;
            node.next = head;
        }
        head = node;
    }


    class DoubleNode{
        int value; // 值
        DoubleNode next; // 下一节点
        DoubleNode pre; // 前一节点

        public DoubleNode(int value) {
            this.value = value;
            this.next = null;
            this.pre = null;
        }
    }

}

面试题-LRU缓存淘汰算法

如何设计LRU缓存淘汰算法?

所谓LRU,就是长时间不使用的就淘汰掉

我们只需要维护一个有序的单链表就行了,这个顺序,就是加入的时间排序


如果新插入一个值,先遍历链表,如果存在,那就删除,并且插入到头结点,如果不存在,那就直接插入头部,当然,需要判断链表能否继续插入,毕竟LRU也是有存储限制的,如果有,直接插入,没有的话,那就删除最后的节点,最后的节点一定是相对于最少使用的

当然,这只是一个简单的使用,实际在查询时,可以结合数组等方法,进行优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值