【数据结构】双向链表及LRU缓存的实现

目录

前言

1. 在原有的自定义链表类 Linked 的基础上,添加新的 “节点添加”方法 addNode(Node node)

测试用例

测试结果

2. 在自定义链表类的基础上,使用双重循环“强力” 判断两个节点是否发生相交

测试用例

测试结果

3. 在自定义链表类的基础上,使用“双指针”判断两条链表是否相交

测试用例

测试结果

4. 在自定义链表类的基础上,将一条链表进行分割

测试用例

测试结果 

5. 使用链表实现 LRU缓存

重写toString()方法

测试用例

测试结果


前言

书接上回,我们继续撕链表,不同上次的是,这次双向链表(使用“双向链表 + 哈希表”实现LRUCache缓存)也得被我们撕;

相关传送门:===》【算法】单向链表手撕代码《===


1. 在原有的自定义链表类 Linked 的基础上,添加新的 “节点添加”方法 addNode(Node node)

    //不使用 new Node 创造节点
    public void addNode(Node node){
        //获取当前链表的尾节点
        final Node l =last;

        if(l !=null){
            //链表不为空
            l.next = node;
        }else{
            //链表为空
            first =node;
        }

        last = node;

        size++;
    }

不同于之前添加节点的方法 add(int val) 的是:这个方法不使用 new Node 创造节点;

原 add(int val) 方法:

    //添加元素(尾插法)
    public void add(int val){
        //获取当前链表的尾节点
        final Node l = last ;

        //创建新节点
        final Node newNode = new Node(val);

        if(l !=null){
            //链表不为空
            l.next = newNode;
        }else{
            //链表为空
            first = newNode;
        }

        last = newNode;
        size++;
    }
  • 测试用例
        Linked.Node node1 =new Linked.Node(1);
        Linked.Node node2 =new Linked.Node(2);
        Linked.Node node3 =new Linked.Node(3);
        Linked.Node node4 =new Linked.Node(4);
        Linked.Node node5 =new Linked.Node(5);


        Linked.Node nodeA =new Linked.Node(1);
        Linked.Node nodeB =new Linked.Node(2);
        Linked.Node nodeC =new Linked.Node(3);

        Linked link1 = new Linked();
        link1.addNode(node1);
        link1.addNode(node2);
        link1.addNode(node3);
        link1.addNode(node4);
        link1.addNode(node5);


        Linked link2 =new Linked();
        link2.addNode(nodeA);
        link2.addNode(nodeB);
        link2.addNode(nodeC);
        link2.addNode(node3);   //链表相交

        System.out.println(link1);
        System.out.println(link2);
  • 测试结果

如图所示,link1链表与link2链表发生了链表相交

链表相交:顾名思义,两个链表在某个节点处有相同的节点,即它们共享同一个节点作为交点;


2. 在自定义链表类的基础上,使用双重循环“强力” 判断两个节点是否发生相交

思路:

  • 设置两个变量,分别遍历两个链表,双重 for 循环,在循环遍历过程中,如果两个变量相等,那么两个链表相交;
    //使用双重循环的方式判断两条链表是否相交
    public  static  boolean isIntersect1(Linked link1,Linked link2){
        for(Node p=link1.first; p !=null;p = p.next){
            for(Node q =link2.first; q != null; q = q.next){
                if( p == q ){
                    return true; //相交
                }
            }
        }
        return false; // 不相交
    }

解读:

  • 外部循环从第一个链表的头节点开始,依次遍历每个节点;
  • 内部循环检查第一个链表的当前节点是否与第二个链表的任何节点相同;
  • 在内部循环中,通过比较节点的引用地址来判断两个节点是否相同;
  • 如果找到相同的节点,说明两个链表在此处相交,返回 true
  • 如果外部循环结束后仍未找到相交的节点,那么说明两个链表不相交,返回 false

效率较低,尤其是在处理大型链表时

  • 测试用例

 在 1 的测试用例中,添加:

 System.out.println("link1 与 link2 是否发生相交:"+Linked.isIntersect1(link1,link2));
  • 测试结果


3. 在自定义链表类的基础上,使用“双指针”判断两条链表是否相交

思路:

  • 判断两个链表是否相交,该两个链表中长度必定有长有短,或者相等。如果这两个链表长度不相等,我们可以得到两个链表的长度的插值diff。同样也是设置两个变量 p,q,分别遍历长链表和短链表,与方法使用双重循环不同的是,p遍历长链表的时候不是从第一个节点开始遍历,而是先让p往后移动diff个节点,然后p和q同时循环往后一个节点,如果p == q,那么两个链表就相交。
    //使用“双指针”判断两条链表是否相交
    public static boolean isIntersect2(Linked link1 ,Linked link2){
        /**
         * 不支持这个算法,需要添加代码,对链表元素进行遍历,计算链表长度
         * public int size(){
         *  return size;
         * }
         */
        // p指向长链表
        //
        Node p = link1.size() > link2.size() ? link1.first : link2.first;
        Node q = link1.size() < link2.size() ? link1.first : link2.first;

        //两条链表的长度差
        int diff = Math.abs(link1.size() - link2.size());

        //长链表移动diff个结点
        while (diff-- >0){
            p = p.next;
        }

        //遍历链表中的剩余结点
        while (p != q){
            p = p.next;
            q = q.next;
        }

        if( p != null){
            return  true;  //相交
        }
        return false;  //不相交
    }

更改原自定义链表类的链表长度计算方法 size()

    //返回链表长度
    public int size(){
        int size =0;
        for(Node x =first;x !=null; x =x.next){
            size++;
        }
        return size;
    }

解读:

  • 使用链表的 size() 方法获取链表的长度;
  • 通过比较两个链表的长度,选择其中较长的链表,将较长的链表赋值给 p,较短的链表赋值给 q
  • 然后计算两个链表的差值,并将p移动该差值的节点数目,使得 p q 所在位置到链表末尾的距离相同;
  • 使用两个指针同时遍历两个链表,直到 p q 相等,或者遍历到链表结尾;
  • 如果 p q 相等,说明两个链表在某个位置相交,返回 true 表示相交;

时间复杂度为O(m+n),其中m和n分别为两个链表的长度。相较于双重循环的方法,这种方法通常具有更好的性能。

  • 测试用例
        Linked.Node node1 =new Linked.Node(1);
        Linked.Node node2 =new Linked.Node(2);
        Linked.Node node3 =new Linked.Node(3);
        Linked.Node node4 =new Linked.Node(4);
        Linked.Node node5 =new Linked.Node(5);


        Linked.Node nodeA =new Linked.Node(1);
        Linked.Node nodeB =new Linked.Node(2);
        Linked.Node nodeC =new Linked.Node(3);

        Linked link1 = new Linked();
        link1.addNode(node1);
        link1.addNode(node2);
        link1.addNode(node3);
        link1.addNode(node4);
        link1.addNode(node5);


        Linked link2 =new Linked();
        link2.addNode(nodeA);
        link2.addNode(nodeB);
        link2.addNode(nodeC);
        link2.addNode(node3);   //链表相交

        System.out.println(link1);
        System.out.println(link2);

        System.out.println("link1 与 link2 是否发生相交:"+Linked.isIntersect2(link1,link2));
  • 测试结果


4. 在自定义链表类的基础上,将一条链表进行分割

思路:

  • 遍历原链表,判断每个节点,并存入两条不同的新链表中,分别用于保存小于给定值x和大于给定值x的节点,最后合并两条链表。
    //链表的分割
    public static Node partition(Node head,int x){

        //准备两条链表,用于分别保存小于x的节点和大于x的节点
        Node linked1 = new Node(0);
        Node linked2 = new Node(0);

        Node cur1 =linked1;
        Node cur2 =linked2;

        //从头节点开始遍历,分别判断每个节点与x之间的大小关系
        while (head !=null){
            if(head.val <=x){
                //小于x,存入链表1
                cur1.next = head;
                cur1 = cur1.next;
            }else{
                //大于x,存入链表2
                cur2.next = head;
                cur2 = cur2.next;
            }

            head = head.next;

        }
        //合并链表
        cur1.next = linked2.next;
        cur2.next = null;
        return linked1.next;
    }

解读:

  • 定义了一个静态方法 partition,该方法接收两个参数:一个是头节点 head,另一个是值 x;
  • 创建了两个新的链表 linked1 linked2,并分别用 cur1 cur2 来指向这两个链表的当前节点;
  • 从头节点开始遍历原始链表 head,对每个节点的值与给定值 x 进行比较;
  • 如果节点的值小于等于 x,则将该节点添加到 linked1 链表中,并更新 cur1 指针;
  • 如果节点的值大于 x,则将该节点添加到 linked2 链表中,并更新 cur2 指针;
  • 遍历完整个原始链表后,将 linked1 链表的尾部与 linked2 链表的头部连接起来,同时将 linked2 链表的尾部指向 null,以避免形成循环;
  • 最后返回 linked1 链表的头部作为结果,即经过分区后的新链表;
  • 测试用例
        // 创建链表节点
        Linked.Node node1 = new Linked.Node(3);
        Linked.Node node2 = new Linked.Node(5);
        Linked.Node node3 = new Linked.Node(8);
        Linked.Node node4 = new Linked.Node(5);
        Linked.Node node5 = new Linked.Node(10);
        Linked.Node node6 = new Linked.Node(2);
        Linked.Node node7 = new Linked.Node(1);

        // 构建链表:3 -> 5 -> 8 -> 5 -> 10 -> 2 -> 1
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;

        // 执行分割方法
        Linked.Node result = Linked.partition(node1, 5);

        // 输出分割后的链表
        while (result != null) {
            System.out.print(result.val + " -> ");
            result = result.next;
        }
        System.out.println("null");
  • 测试结果 


5. 使用链表实现 LRU缓存

要求:

实现 LRUCache 类:满足 LRU ( Least Recently User 最近最少使用)缓存实现类;

  • LRUCache(int capacity) 以正整数作为容量 capacity 初始化LRU缓存;
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1;
  • void put(int key,int value) 如果关键字 key 已经存在,则变更其数据值 value;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该逐出最久未使用的关键字;
  • 函数 get() put() 必须以 O(1) 的平均时间复杂度运行;
/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

public class LRUCache{
    
    public LRUCache(int capacity){
    
    }
    
    public int get(int key){

    }
    
    public void put(int key,int value){

    }

}

思路:

通过双向链表 + 哈希表实现:

  • 双向链表按照被使用的顺序存储键值对,靠近头部的键值对是最近使用的,而靠近尾部的键值对是最久未使用的,用于实现 Least Recently User 最近最少使用的缓存约束;
  • 哈希表通过缓存数据的键映射在双向链表中的位置;
  • get () 根据 key 获取 value ,并同时将节点移动至链表的头部;
  • put () 添加新缓存键值对,并同时添加至链表的头部。如果超出容量,则删除链表尾部节点;
  • 通过上述操作,可保证查找函数 get() put() 的时间复杂度为 O(1);

基础代码:

在 LRUCache 类中创建 Node 内部类

    //①节点类:双向链表的节点
    class Node{
        public int key;
        public int value;
        Node prev;  //节点前趋
        Node next;  //节点后继

        public Node(){}

        public Node(int key,int value){
            this.key = key;
            this.value = value;
        }
    }

 解读:

  • key:表示节点的键,缓存中存储的数据的标识;
  • value:表示节点的值,缓存中存储的数据;
  • prev:表示节点的前趋节点,该节点在链表中的前一个节点;
  • next:表示节点的后继节点,该节点在链表中的后一个节点;
  • 默认构造函数 public Node():创建一个空节点;
  • 构造函数 public Node(int key, int value):创建一个具有指定键值的节点;

定义缓存容量和“伪”头节点和“伪”尾节点

    //②定义缓存容量
    private int capacity;

    //②定义“伪”头节点和"伪"尾节点
    private Node first,last;

 解读:

  • capacity:表示缓存的容量,缓存可以存储的键值对的最大数量。在LRU缓存中,当缓存达到容量上限时,需要进行淘汰操作以腾出空间存储新的数据;

  • first last:分别表示双向链表中的虚拟头节点和虚拟尾节点。在LRU缓存中,使用虚拟头节点和虚拟尾节点的目的是简化链表操作,使得在链表头部和尾部插入、删除节点更加方便高效。这两个节点并不存储实际的数据,只是作为辅助节点来连接实际的数据节点;

定义 LRUCache 的有参构造方法

    public LRUCache(int capacity) {
        this.capacity = capacity;

        //③创建
        first = new Node();
        last = new Node();

        //③形成链表
        first.next = last;
        last.prev = first;
    }

解读:

  • 将输入的缓存容量赋给成员变量 capacity,保存缓存的最大容量;

  • 创建虚拟头节点 first 和虚拟尾节点 last,并将它们连接起来形成一个双向链表。这两个节点不存储实际的数据,只作为辅助节点来连接实际的数据节点。在这里,通过将 first next 指向 last,以及 last prev 指向 first,形成了一个空的双向链表结构;

创建操作节点 Node 的三个方法:addFirst、removeNode、removeLast、moveToFirst;

    //④添加新节点至链表头部
    private void addFirst(Node newNode){
        newNode.prev = first;
        newNode.next = first.next;

        first.next.prev = newNode;
        first.next = newNode;
    }

    //④删除链表中的指定节点
    private void removeNode(Node node){
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    //④删除链表的尾节点
    private Node removeLast(){
        Node res =last.prev;
        removeNode(res);
        return res;
    }

    //④移动指定节点至链表头部
    private void moveToFirst(Node node){
        removeNode(node);
        addFirst(node);
    }

解读:

  • addFirst(Node newNode) 方法用于将新节点添加至链表头部。该方法首先将新节点的 prev 指向虚拟头节点 first,将新节点的 next 指向原头节点的下一个节点,然后将原头节点的 prev 指向新节点,最后将虚拟头节点 first next 指向新节点,完成新节点的插入;

  • removeNode(Node node) 方法用于删除链表中的指定节点。该方法通过修改指定节点的前趋节点和后继节点的指针来实现节点的删除操作;

  • removeLast() 方法用于删除链表的尾节点,并返回被删除的节点。该方法首先找到尾节点 last 的前一个节点,然后调用 removeNode(node) 方法删除该节点,并返回被删除的尾节点;

  • moveToFirst(Node node) 方法用于将指定节点移动至链表头部。该方法先调用 removeNode(node) 方法将节点从原位置删除,然后调用 addFirst(node) 方法将节点添加至链表头部,实现节点的移动操作;

初始化哈希表和定义哈希表的 put() 方法

    //⑤创建哈希表,用来提高查询链表的性能
    private Map<Integer,Node> cache = new HashMap<>();
    //⑤添加新缓存 key-value 键值对
    public void put(int key, int value) {
        Node node =cache.get(key);

        if(node == null){
            //key 如果不存在
            //创建新节点
            Node newNode =new Node(key,value);

            cache.put(key,newNode); //添加至哈希表
            addFirst(newNode);  //添加链表头部

            if(++size > capacity){
                //⑦超出缓存容量时,删除尾部节点(哈希表、链表)
                Node last = removeLast();
                cache.remove(last.key);
            }
        }else{
            //key 如果存在
            node.value = value;
            moveToFirst(node);  //移动至链表头部
        }
    }

解读:

1. 通过调用 cache.get(key) 方法从哈希表中获取指定键 key 对应的节点 node

2. 如果 nodenull,表示键 key 不存在于缓存中,需要执行以下操作:

  • 创建新的节点 newNode,使用输入的 key 和 value 初始化该节点。
  • 将新节点 newNode 添加至哈希表 cache 中,使用 key 作为键,newNode 作为值。
  • 调用 addFirst(newNode) 方法将新节点 newNode 添加至链表的头部
  • 如果缓存的大小 size 超过了最大容量 capacity,则需要执行以下操作:
    • 调用 removeLast() 方法删除链表的尾节点,并返回被删除的节点 last;
    • 从哈希表 cache 中删除键为 last.key 的键值对;

3. 如果 node 不为 null,表示键 key 已存在于缓存中,需要执行以下操作:

  • 更新节点 node 的值为输入的 value;
  • 调用 moveToFirst(node) 方法将节点 node 移动至链表的头部;

定义链表长度和 哈希表的 put() 方法

    //⑥定义链表长度(缓存个数)
    private int size;
    //⑥ 根据key,获取value
    public int get(int key) {
        Node node =cache.get(key);
        if(node == null){
            return -1;  //不存在
        }

        moveToFirst(node);  //存在则将这个元素移动至链表的头部
        return node.value;  //返回这个节点的值
    }

 解读:

  • 通过调用 cache.get(key) 方法从哈希表中获取指定键 key 对应的节点 node

  • 如果 nodenull,表示缓存中不存在键 key,则返回 -1 表示不存在该键;

  • 如果 node 不为 null,表示缓存中存在键 key,则需要执行以下操作:

    • 调用 moveToFirst(node) 方法将节点 node 移动至链表的头部,以更新节点的访问顺序;
    • 返回节点 node 的值 node.value;

在 put()方法中定义超出容量时的判断逻辑

         if(++size > capacity){
                //⑦超出缓存容量时,删除尾部节点(哈希表、链表)
                Node last = removeLast();
                cache.remove(last.key);
            }

 解读:

  • 调用 removeLast() 方法删除链表的尾节点,并将被删除的节点赋值给变量 last;

  • 通过 cache.remove(last.key) 从哈希表 cache 中删除键为 last.key 的键值对,即删除了对应的缓存项;

重写toString()方法
  @Override
    public String toString() {
        //使用线程不安全,但性能较好的StringBuilder
        StringBuilder ret = new StringBuilder();
        String ret1 ="";
        for(Node x =first;x != null;x =x.next){
//            ret.append(x.key+":"+x.value+"\t");
            ret1 += x.key + ":"+x.value+"\t\t";
        }
        return ret1.toString();
    }
测试用例
 public static void main(String[] args) {
        LRUCache lRUCache = new LRUCache(2);

        lRUCache.put(1, 1);
        System.out.println(lRUCache);

        lRUCache.put(2, 2);
        System.out.println(lRUCache);

        lRUCache.put(3, 3);
        System.out.println(lRUCache);

        lRUCache.put(2, 20);
        System.out.println(lRUCache);

        lRUCache.put(4, 4);
        System.out.println(lRUCache);

        System.out.println(lRUCache.get(2));    // 返回 20
        System.out.println(lRUCache);


    }
测试结果


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/455554.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Security 结合 JWT使用

Spring Security原理 Spring Security的原理主要基于过滤器链的概念。在Web应用程序中&#xff0c;每个请求都会通过一系列的过滤器&#xff0c;Spring Security就是在这个过程中介入并进行安全相关的操作。 Spring Security的核心原理可以概括为以下几点&#xff1a; 1. 认…

JavaWeb06-MVC和三层架构

目录 一、MVC模式 1.概述 2.好处 二、三层架构 1.概述 三、MVC与三层架构 四、练习 一、MVC模式 1.概述 MVC是一种分层开发的模式&#xff0c;其中 M&#xff1a;Model&#xff0c;业务模型&#xff0c;处理业务 V&#xff1a; View&#xff0c;视图&#xff0c;界面展…

雅特力AT32A403开发板评测 01 开箱及环境搭建流程

01-雅特力AT32A403A开发板开箱及环境搭建流程 雅特力AT32 2023年&#xff0c;玩了不少的国产MCU开发板&#xff0c;例如武汉芯源CW32&#xff0c;兆易创新GD32、上海航芯ACM32、沁恒CH32V307等开发板&#xff0c;虽然电子工程世界论坛也有雅特力AT32开发板的评测活动&#xf…

NO5 蓝桥杯实践之矩阵键盘的使用(或许是一篇求助帖...)

1 任务 2 思路 视频中老师的思路写的代码过长&#xff0c;所以我想了个自己的思路&#xff0c;但是没完全跑出来&#xff0c;求大神指教&#xff01;&#xff01;&#xff01;&#xff01; 我的思路是首先将矩阵键盘的行和列对应的端口引脚分别存储在类似数组&#xff0c;然后…

人工智能|机器学习——BIRCH聚类算法(层次聚类)

这里再来看看另外一种常见的聚类算法BIRCH。BIRCH算法比较适合于数据量大&#xff0c;类别数K也比较多的情况。它运行速度很快&#xff0c;只需要单遍扫描数据集就能进行聚类。 1.什么是流形学习 BIRCH的全称是利用层次方法的平衡迭代规约和聚类&#xff08;Balanced Iterative…

环形缓冲区在stm32上的使用

目录 环形缓冲区在stm32上的使用前言实验目的环形缓冲区的定义和初始化写入数据到环形缓冲区从环形缓冲区读取数据实验结果本文中的实践工程 环形缓冲区在stm32上的使用 本文目标&#xff1a;环形缓冲区在stm32上的使用 按照本文的描述&#xff0c;应该可以跑通实验并举一反三…

利用Anaconda创建环境

利用Anaconda创建环境 1. 创建环境的步骤 1. 创建环境的步骤 1.在终端中&#xff0c;使用以下命令创建一个新的 Anaconda 环境。假设您想要创建一个名为 myenv 的环境&#xff1a; conda create --name myenv2.如果您想指定 Python 版本&#xff0c;可以在创建环境时添加版本号…

AI论文速读 | 计时器(Timer):用于大规模时间序列分析的Transformer

题目&#xff1a; Timer: Transformers for Time Series Analysis at Scale 作者&#xff1a;Yong Liu,&#xff08;刘雍&#xff09;, Haoran Zhang&#xff08;张淏然&#xff09;, Chenyu Li&#xff08;李晨宇&#xff09;, Jianmin Wang&#xff08;王建民&#xff09;, …

群晖 Synology Photos DSM7 自定义文件夹管理照片

背景 众所周知&#xff0c;目前群晖DSM7中使用Synology Photos做照片管理时&#xff0c;个人照片只能默认索引 /home/Photos 文件夹&#xff0c;但是如果个人照片很多或者用户很多时&#xff0c;共享文件夹/homes 所在的存储空间就会不够用 当然&#xff0c;如果你的存…

【软考高项】四、信息化发展之数字中国

1、数字经济 定义&#xff1a;从本质上看&#xff0c;数字经济是一种新的技术经济范式&#xff0c;它建立在信息与通信技术的重大突破的基础上&#xff0c;以数字技术与实体经济融合驱动的产业梯次转型和经济创新发展的主引擎&#xff0c;在基础设施、生产要素、产业结构和治理…

certificate has expired or is not yet valid:npm和node证书过期问题

在 1 月 22 日&#xff0c;淘宝原镜像域名&#xff08;registry.npm.taobao.org&#xff09;的 HTTPS 证书正式到期。如果想要继续使用&#xff0c;需要将 npm 源切换到新的源&#xff08;registry.npmmirror.com&#xff09;&#xff0c;否则会报错。 解决方案切换到新的源&a…

nacos做注册注册中心go语言实战教程(服务的注册与获取)

背景 随着访问量的逐渐增大&#xff0c;单体应用结构渐渐不满足需求&#xff0c;在微服务出现之后引用被拆分为一个个的服务&#xff0c;服务之间可以互相访问。初期服务之间的调用只要知道服务地址和端口即可&#xff0c;而服务会出现增减、故障、升级等变化导致端口和ip也变…

欧科云链做客Google Cloud与WhalerDAO专题论坛,畅谈Web3数据机遇

3月10日&#xff0c;由Google Cloud、WhalerDAO和baidao data主办&#xff0c;以Web3AI 2024 DATA POWER为主题的分享会在北京中关村举行。欧科云链高级研究员Jason Jiang受邀参加活动&#xff0c;带来“从链上数据发掘Web3时代的无限机遇”的主题分享。 Web3.0核心要素始终是链…

如何从 Mac 电脑外部硬盘恢复删除的数据文件

本文向您介绍一些恢复 Mac 外置硬盘数据的快速简便的方法。 Mac 的内部存储空间通常不足以存储所有数据。因此&#xff0c;许多用户通过外部驱动器扩展存储或创建数据备份。然而&#xff0c;与几乎所有其他设备一样&#xff0c;从外部硬盘驱动器丢失有价值的数据并不罕见。由于…

第二证券|炒股最好用的6个指标?

炒股存在以下好用的6个目标&#xff1a; 1、kdj目标 当k线从下方往上穿过d线时&#xff0c;构成金叉&#xff0c;是一种买入信号&#xff0c;投资者能够考虑在此刻买入一些个股&#xff0c;其间kdj金叉方位越低&#xff0c;买入信号越强&#xff1b;当k线从上往下穿过d线时&a…

HTML静态网页成品作业(HTML+CSS)——电影肖申克的救赎介绍设计制作(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用教程

原文链接&#xff1a;“SRP模型”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI 指数的生态质量评价及拓展应用教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247597452&idx5&snf723d9e5858a269d00e15dbe2c7d3dc0&chksmfa823c6…

Python算法(列表排序)

一。冒泡排序&#xff1a; 列表每两个相邻的数&#xff0c;如果前面比后面大&#xff0c;则交换这两个数 一趟排序完成后&#xff0c;则无序区减少一个数&#xff0c;有序区增加一个数 时间复杂度&#xff1a;O(n*n) 优化后&#xff1a;已经排序好后立马停止&#xff0c;加快…

Ubuntu 14.04:PaddleOCR基于PaddleHub Serving的服务部署(失败)

目录 一、为什么使用一键服务部署 二、安装 paddlehub 1.8 2.1 安装前的环境准备 2.2 安装paddlehub 1.8 2.2.1 安装paddlehub 2.2.2 检测安装是否成功 2.2.3 检查本地与远端PaddleHub-Server的连接状态 2.2.4 测试使用 2.3 其他 2.3.1 如何卸载、pip常用命令、常见…

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+HLS图像缩放+多路视频拼接,提供4套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收OSD动态字符叠加输出应用本方案的SDI接收HLS多路视频融合叠加应用本方案的SDI接收G…