02优先队列和索引优先队列-优先队列-数据结构和算法(Java)

news2024/11/15 20:28:37

文章目录

    • 1 概述
      • 1.1 需求
      • 1.2 优先队列特点
      • 1.3 优先队列分类
      • 1.4 应用场景
      • 1.5 相关延伸
    • 2 说明
    • 3 索引优先队列
      • 3.1 实现思路
      • 3.2 API设计
      • 3.2 代码实现及简单测试
    • 5 主要方法讲解
      • 5.1 exch()
      • 5.2 insert()
      • 5.2 poll()
    • 6 分析
    • 7 后记

1 概述

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。

1.1 需求

许多应用程序都需要处理有序的元素,但不一定要求它们全部有序,或是不一定要一次就将它们排序。很多情况下我们会收集一些元素,处理当前键最大(或最小)的元素,然后在收集更多的元素,在处理当前键值最大(或最小)的元素,如此这般。例如,在一台同时运行多个应用程序的电脑(或手机),它通常为每个应用程序分配一个优先级,并总是处理下一个优先级最高的事件。比如大多数手机分配给来电的优先级都会比游戏程序高。

1.2 优先队列特点

  • 支持两种基本操作:
    • 删除最大(或最小)元素
    • 插入元素
  • 使用和队列以及栈类似,但是处理插入和删除更高效

1.3 优先队列分类

  • 按队列删除元素是最大还是最小

    • 最大优先队列:删除的元素为最大值
    • 最小优先队列:删除的元素为最小值
  • 按内部实现:

    • 基于数组的优先队列

      • 无序
      • 有序
    • 基于链表优先队列

      • 无序
      • 有序
    • 堆(二叉堆)

    • 索引优先队列

1.4 应用场景

一些常见常见如下:

  • 模拟系统:其中事件的键即为发生的时间,而系统需要按照时间顺序处理所有事件。
  • 任务调度:其中键值对应的优先级决定了应该首先执行那些任务
  • 数值计算:键值代表计算错误,而我们需要按照键值指定的顺序来修正它们。

1.5 相关延伸

我们可以使用优先队列实现排序算法,一种名为堆排序的重要排序算法来自于基于堆的优先队列实现。

2 说明

基于数组和链表的普通优先队列实现,因为它们的插入和删除操作其中之一在最坏情况下需要线性时间来完成,应用中不会使用,我们不实现,如果有兴趣可以自己实现或查阅相关文档。关于堆和堆排序,我们在堆(二叉堆)-优先队列-数据结构和算法(Java)和堆排序-排序-数据结构和算法中已经解决,下面我们学习下索引优先队列。

优先队列的各种实现在最坏情况下运行时间的增长量级如下表2-1所示:

数据结构插入元素删除最大(或最小)元素
有序数组(或链表)N1
无序数组(或链表)1N
log ⁡ N \log N logN log ⁡ N \log N logN
理想情况11

使用无序序列解决问题是惰性方法,我们仅在必要的时候才会采取行动(找出最大或最小元素);使用有序序列是解决问题的积极方法,因为我们会尽可能的未雨绸缪(在插入元素时就保持列表有序),是后续操作更高效。

3 索引优先队列

在很多应用中,允许用例引用已经进入优先队列中的元素是有必要的。做到这一点的一种简单方法就是给每个元素一个索引,也就是我们下面要学习的索引优先队列。我们以最小索引优先队列为例,进行讲解。

3.1 实现思路

  • 首先有一个数组table存放E类型的数据,插入元素e的时候同时指定一个索引i,即放入数组table的索i处。

  • 然后准备一个堆排序数组pq,来存放元素对应的索引i,此时通过table[pq[i]]很容易找到元素,如下图所示:在这里插入图片描述

  • 新元素插入或者删除元素或者改变元素,破坏了堆有序,此时我们需要进行调整,我们对pq进行堆排序,而不是对table数组排序,此时堆有序如下图所示:在这里插入图片描述

    • 此堆有序从元素插入开始排序,不是之后的堆排序调整,可以使用之前实现的二叉堆校验。
  • 当进行堆排序之后,我们怎么通过原先的索引i快速定位到元素呢?一种方式是遍历pq数组,但是效率太慢,这时我们在引入第三个数组qp为数组pq的逆数组,这样呢qp索引就与table索引一致。通过table[qp[pq[i]]]很容易定位到我们改动的元素。

    • 逆数组即数组pq的值为qp的索引,而pq的索引为qp的值。
  • 三个数组最终内存示意图如下:在这里插入图片描述

  • 详细的插入、删除等方法实现,我们在代码实现部分给出分析。

3.2 API设计

我们继续沿用队列接口Queue

类声明:

public class IndexPriorityQueue<E extends Comparable<E>> implements Queue<E>, Serializable

内部迭代器类:

private class Itr implements Iterator<E>

成员变量:

访问控制和类型名称描述
private E[]table存放元素数组
private int[]pq存放table索引
private int[]qppq的逆数组
private intsize元素个数
private intmaxSize最大元素个数
private Comparator<E>comparator比较器,默认从小到大比较
private booleanmax是否是最大索引队列,默认false即最小索引队列

方法:主要方法,Queue中声明的方法不在列举

访问控制和返回值类型名称描述
publicIndexPriorityQueue(int)构造器
publicIndexPriorityQueue(int, Comparator<E>, boolean )构造器
public EchangeVal(int i, E e)更换索引i处元素为e
public booleancontains(int i)判断索引是否已经存在
public voiddecreaseVal(int i, E e)索引i处优先级降级为e
public voidincreaseVal(int i, E e)索引i处元素优先级升级为e
public voidinsert(int i, E e)插入元素
public voidsink(int i)下沉
public voidswim(int i)上浮

3.2 代码实现及简单测试

完整实现代码如下:

import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * @author Administrator
 * @date 2022-12-09 20:24
 */
public class IndexPriorityQueue<E extends Comparable<E>> implements Queue<E>, Serializable {

    /**
     * 存放元素数组
     */
    private final E[] table;

    /**
     * 堆排序数组
     */
    private final int[] pq;

    /**
     * 数组pq的逆数组
     */
    private final int[] qp;

    /**
     * 元素个数
     */
    private int size;

    /**
     * 最大元素个数
     */
    private final int maxSize;

    /**
     * 比较器,默认从小到大排序
     */
    private Comparator<E> comparator;

    /**
     * 是否是最大索引队列,默认不是即最小优先队列
     */
    private boolean max = false;


    /**
     * 传从小到大排序,max指定false;从大到小排序,指定max为true
     */
    public IndexPriorityQueue(int maxSize, Comparator<E> comparator, boolean max) {
        this(maxSize);
        if (comparator != null) {
            this.comparator = comparator;
        }
        this.max = max;
    }

    public IndexPriorityQueue(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException();
        }
        this.maxSize = maxSize;
        size = 0;

        table = (E[]) new Comparable[maxSize];
        pq = new int[maxSize + 1];
        qp = new int[maxSize];
        for (int i = 0; i < maxSize; i++) {
            qp[i] = -1;
        }
        this.comparator = Comparable::compareTo;
    }

    /**
     * 插入元素
     *
     * @param e 元素
     */
    @Override
    public void offer(E e) {
        throw new UnsupportedOperationException();
    }

    /**
     * 插入元素
     *
     * @param i 索引i
     * @param e 元素e
     */
    public void insert(int i, E e) {
        validateIndex(i);
        if (contains(i)) {
            throw new IllegalArgumentException("索引已经存在");
        }
        if (size >= maxSize) {
            throw new IllegalArgumentException("队列已满");
        }
        size++;
        table[i] = e;
        pq[size] = i;
        qp[i] = size;
        swim(size);
    }

    /**
     * 获取队首元素
     *
     * @return 队首元素
     */
    @Override
    public E peek() {
        validateEmpty();
        return table[pq[1]];
    }

    private void validateEmpty() {
        if (size == 0) {
            throw new NoSuchElementException();
        }
    }

    /**
     * 获取并删除堆有序队首元素
     *
     * @return 队首元素
     */
    @Override
    public E poll() {
        validateEmpty();
        int m = pq[1];
        E oldVal = table[m];
        exch(1, size--);
        sink(1);
        qp[m] = -1;
        table[m] = null;
        pq[size + 1] = -1;
        return oldVal;
    }

    /**
     * 判断队列是否为空
     *
     * @return {@code true}队列为空;反之{@code false}
     */
    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 返回元素个数
     *
     * @return 元素个数
     */
    @Override
    public int size() {
        return size;
    }

    /**
     * 更换索引i处元素为e
     *
     * @param i 索引
     * @param e 更换后的值
     */
    public E changeVal(int i, E e) {
        validateIndex(i);
        if (!contains(i)) {
            throw new NoSuchElementException("队列不存在该索引:" + i);
        }
        E o = table[i];
        table[i] = e;
        int j = qp[i];
        swim(j);
        sink(j);
        return o;
    }


    /**
     * 索引i处优先级降级为e
     *
     * @param i 索引
     * @param e 目标值
     */
    public void decreaseVal(int i, E e) {
        validateIndex(i);
        if (!contains(i)) {
            throw new NoSuchElementException("队列不存在该索引:" + i);
        }
        if (table[i].compareTo(e) == 0) {
            throw new IllegalArgumentException(e + " 优先级不能等于 " + table[i]);
        }
        if (table[i].compareTo(e) < 0) {
            throw new IllegalArgumentException(e + " 优先级不能高于原优先级 " + table[i]);
        }
        table[i] = e;
        if (max) {
            sink(qp[i]);
        } else {
            swim(qp[i]);
        }
    }

    /**
     * 索引i处元素优先级升级为e
     *
     * @param i 索引i
     * @param e 目标值
     */
    public void increaseVal(int i, E e) {
        validateIndex(i);
        if (!contains(i)) {
            throw new NoSuchElementException("队列不存在该索引:" + i);
        }
        if (table[i].compareTo(e) == 0) {
            throw new IllegalArgumentException(e + " 优先级不能等于 " + table[i]);
        }
        if (table[i].compareTo(e) > 0) {
            throw new IllegalArgumentException(e + " 优先级不能低于原优先级 " + table[i]);
        }
        table[i] = e;
        table[i] = e;
        if (max) {
            swim(qp[i]);
        } else {
            sink(qp[i]);
        }
    }

    /**
     * 判断索引是否已经存在
     *
     * @param i 元素索引
     * @return {@code true}逆数组包含该索引;反之{@code false}
     */
    public boolean contains(int i) {
        validateIndex(i);
        return qp[i] != -1;
    }

    /**
     * 删除索引i处的元素
     *
     * @param i 索引i
     * @return 删除的元素
     */
    public E delete(int i) {
        validateIndex(i);
        if (!contains(i)) {
            throw new NoSuchElementException("队列不存在该索引: " + i);
        }
        int k = qp[i];
        exch(k, size--);
        swim(k);
        sink(k);
        E o = table[k];
        table[k] = null;
        qp[k] = -1;
        pq[size + 1] = -1;
        return o;
    }

    /**
     * 交换元素
     *
     * @param i 索引i
     * @param j 索引j
     */
    private void exch(int i, int j) {
        int p = pq[i];
        pq[i] = pq[j];
        pq[j] = p;
        qp[pq[i]] = i;
        qp[pq[j]] = j;
    }

    /**
     * 比较索引i和索引j处的值
     *
     * @param i
     * @param j
     * @return
     */
    private boolean compare(int i, int j) {
        return comparator.compare(table[pq[i]], table[pq[j]]) < 0;
    }

    /**
     * 返回索引i处的值
     *
     * @param i 索引
     * @return i处的值
     */
    public E valueOf(int i) {
        validateIndex(i);
        if (!contains(i)) {
            throw new NoSuchElementException("索引不在优先队列中");
        }
        return table[i];
    }

    /**
     * 返回最低(高)优先级元素对应的索引
     *
     * @return 最低(高)优先级元素对应的索引
     */
    public int firstIndex() {
        validateEmpty();
        return pq[1];
    }

    /**
     * 下沉
     *
     * @param i 下沉起始索引
     */
    public void sink(int i) {
        while (2 * i <= size) {
            int j = 2 * i;
            if (j < size && compare(j + 1, j)) {
                j++;
            }
            if (!compare(j, i)) {
                break;
            }
            exch(i, j);
            i = j;
        }
    }

    /**
     * 上浮
     *
     * @param i 起始索引
     */
    public void swim(int i) {
        while (i > 1 && compare(i, i / 2)) {
            exch(i, i / 2);
            i /= 2;
        }
    }

    /**
     * 校验索引i
     *
     * @param i 索引i
     */
    private void validateIndex(int i) {
        if (i < 0) {
            throw new IllegalArgumentException("index is negative: " + i);
        }
        if (i >= maxSize) {
            throw new IllegalArgumentException("index >= capacity: " + i);
        }
    }

    @Override
    public String toString() {
        Iterator<E> iterator = iterator();
        if (!iterator.hasNext())
            return "[]";
        StringBuilder b = new StringBuilder("[");
        while (true) {
            E next = iterator.next();
            b.append(next);
            if (!iterator.hasNext()) {
                return b.append("]").toString();
            }
            b.append(",");
        }
    }

    @Override
    public Iterator<E> iterator() {
        return new Itr();
    }

    private class Itr implements Iterator<E> {
        // create a new pq
        private IndexPriorityQueue<E> copy;

        public Itr() {
            copy = new IndexPriorityQueue<E>(maxSize, comparator, max);
            for (int i = 1; i <= size; i++) {
                copy.insert(pq[i], table[pq[i]]);
            }
        }

        @Override
        public boolean hasNext() {
            return !copy.isEmpty();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        @Override
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return copy.poll();
        }
    }
}

测试代码如下:

public class TestIndexPq {
    public static void main(String[] args) {
        IndexPriorityQueue<Integer> ipq = new IndexPriorityQueue<Integer>(20, (o1, o2) -> o2.compareTo(o1), true);
//        Random random = new Random();
//        int[] a = new int[10];
//        for (int i = 0; i < 10; i++) {
//            a[i] = random.nextInt(1000);
//        }
        int[] a = {317, 430, 734, 880, 105, 536, 536, 813, 48, 806};
        for (int i = 0; i < a.length; i++) {
            ipq.insert(i, a[i]);
        }
//        System.out.println(Arrays.toString(a));

//        ipq.increaseVal(9, 881);
        System.out.println(ipq);
        ipq.changeVal(1, 1);
        System.out.println(ipq);
//        for (Integer integer : ipq) {
//            System.out.println(integer);
//        }
    }
}

5 主要方法讲解

5.1 exch()

该方法用于在堆排序时交换元素,因为我们是通过pq数组来进行的堆排序,那么在交换pq元素的的同时,需要调整qp数组相应值。

方法执行流程如下:

  • 交换pq索引i,j的值
  • 那么qp对应的索引就是pq[i],pq[j],值替换为i,j

5.2 insert()

这个索引指的就是存储元素数组table的索引,也是qp的索引

执行流程如下:

  • 校验索引范围
  • 校验索引是否已经存在
  • 判断队列是否已满
  • 元素大小size+1
  • 数组table[i]设置为e
  • pq添加至数组末尾,qp设置相应的值
  • pq数组堆排序

5.2 poll()

方法目标就是获取堆有序的堆顶元素,并删除

执行流程如下:

  • 校验队列是否为空
  • 获取堆顶元素在table中的索引
  • 记录元素值
  • 交换pq中堆顶和堆底的元素,并进行下沉排序
    • 删除堆顶元素,破坏了堆有序,把最后一个放置在堆顶,进行下沉排序
  • 做清理工作,同时返回记录的原数值

6 分析

命题Q:在一个大小为N的所以优先队列中,插入元素、改变优先级、删除和删除最小元素操作所需的比较次数和 log ⁡ N \log N logN成正比。

证明:已知堆中所有路径最长为 ∼ lg ⁡ N \sim \lg N lgN,从代码中很容易证明这个结论。

主要操作在最坏情况下的成本:

操作比较次数的增长数量级
insert log ⁡ N \log N logN
changeVal log ⁡ N \log N logN
contains1
delete log ⁡ N \log N logN
firstIndex1
poll log ⁡ N \log N logN

注:

  • 我们这里没有提供对队列扩容的方法,要实现扩容或者缩容的话记得三个数组同时进行

7 后记

​ 如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/algorithm

[1][美]Robert Sedgewich,[美]Kevin Wayne著;谢路云译.算法:第4版[M].北京:人民邮电出版社,2012.10

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JMeter连接Oracle过程及常见问题总结

如果被测试系统使用的数据库不是MySQL而是Oracle&#xff0c;如何用JMeter进行连接呢&#xff1f; 一、下载数据库驱动 需要确认数据库的版本&#xff0c;可以上网站下载驱动&#xff1a;https://www.oracle.com/database/technologies/jdbc-ucp-122-downloads.html。 或者直…

【Linux】进程间通信-共享内存

前言 我们知道&#xff0c;在Linux中&#xff0c;进程是相互独立存在的&#xff0c;不存在直接让进程之间互相通信的方式。但是如果我们能让不同进程之间见到同一块内存&#xff0c;也就是都能读写这片区域是不是就能够达到进程间通信呢&#xff1f; 事实证明确实如此。在之前我…

【springboot进阶】基于starter项目构建(二)构建starter项目-fastjson

目录 一、创建 fastjson-spring-boot-starter 项目 二、添加 pom 文件依赖 三、构建配置 四、加载自动化配置 五、打包 六、使用 这个系列讲解项目的构建方式&#xff0c;主要使用 父项目 parent 和 自定义 starter 结合。项目使用最新的 springboot3 和 jdk19。本系列的…

kinect v2安装iai_kinect2

目前已完成 前期已经安装了libfreenect2 使用的系统为ubuntu 18.04 使用的相机为kinect v2 已经安装好了orb_slam3&#xff0c;已经完成使用stereo在euroc数据集和使用RGB-D在tum数据集上的测试 目的 想要完成使用深度相机进行在线测试 步骤 step1 经过查阅资料发现目前仅…

大学生HTML期末作业, JavaScript期末大作业

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Java项目:SSM设备台账管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能&#xff1a; 管理员登录,物理设备管理,IP地址资源管理,虚拟机管理,通知公告管理,学历管理,部门管理,员工管理等功能。 …

DFS 数据结构 C++语言实现 图的深度优先遍历

1.DFS图解 图的深度优先遍历 1.1 基本定义&#xff1a; 设初始时&#xff0c;图中所有顶点未曾被访问过&#xff1a; ● 从图中某个顶点 v 出发&#xff0c;访问此顶点&#xff1b; ● 依次从 v 的未被访问的邻接点出发深度优先遍历图&#xff0c;直至图中所有和顶点 v 有路…

【非下载vs解决】error: Microsoft Visual C++ 14.0 or greater is required

首先说解决办法 搜索下载对应库的whl文件即可 下面是解决过程 部分报错为&#xff1a;error: Microsoft Visual C 14.0 or greater is required. Get it with “Microsoft C Build Tools”: https://visualstudio.microsoft.com/visual-cpp-build-tools/ 我是安装wordcloud库…

Activemq安装和控制台

目录 一、安装 二、后台服务启动 三、查看前台 一、安装 http://blog.csdn.net/gebitan505/article/details/55096222 二、后台服务启动 普通启动/关闭 到activemq的目录下执行【./activemq start/stop】进行开启和关闭activemq 带日志的启动 控制台不输出东西&#x…

Stm32旧版库函数2——mpu6050 移植成旧版兼容型库函数DMP

main.c: /******************************************************************************* // 陀螺仪 MPU6050 IIC测试程序 // 使用单片机STM32F103C8T6 // 晶振&#xff1a;8.00M // 编译环境 Keil uVision4 // 在3.3V的供电环境下&#xff0c;就能运行 // 波特率 9600 /…

STM32通过DAC产生正弦波

前言 这一讲主要来讲解DAC功能 文章目录 前言一、DAC简介二、DAC通道框图三、DAC输出电压四、输出正弦波五、代码一、DAC简介 数字/模拟转换模块(DAC)是12位数字输入,电压输出的数字/模拟转换器。DAC可以配置为8位或12位模式,也可以与DMA控制器配合使用。DAC工作在12位(1…

[机缘参悟-93]:时间、空间、多维度、动态、全局、系统思考模型汇总

目录 前言&#xff1a; 一、空间 - 广度 - 静态 - 多维度模型 1.1 一元太极本源模型 1.2 二元阴阳组合模型 1.3 三元铁三角稳定模型 1.4 四象限优先级模型 1.5 九宫格二维矩阵模型 二、空间 - 高度 - 静态 - 多层次模型 2.1 倒树形层次模型 2.2 金字塔层次结构模型 …

新年新气象:Stimulsoft Designer 2023.1.0 Crack

使用 Stimulsoft Designer&#xff0c;您可以轻松设计从简单列表到多页、具有复杂计算、条件、函数和变量的报告。只需单击几下&#xff0c;您就可以创建易于理解且信息丰富的仪表板、设置过滤器以及对任何元素进行排序。Ω578867473 报表设计器很简单 这是一个不需要编程技能的…

6 转移指令

转移指令 1 数据存储位置的表示 我们定义的描述性符号&#xff1a; reg 和sreg 。使用描述性的符号reg 来表示一个寄存器&#xff0c;用sreg 表示一个段寄存器。 reg 的集合包括&#xff1a; ax 、bx 、ex 、dx 、ah 、al 、bh 、bl 、ch 、cl 、dh 、di 、sp 、bp 、si 、d…

NAT (Network Address Translations) 网络地址转换

数据来源 1、ipv4地址严重不够用了 X.X.X.X X 0-255 A、B、C类可以使用 D组播 E科研 2、IP地址分为公网IP和私网IP 公网IP只能在公网上使用私网IP只能在内网中使用公网上不允许出现私有IP地址私网IP可以重复在内网使用1&#xff09;私有地址范围 10.0.0.0/8&…

Vite+Vue3构建前端框架及模板代码及重要知识点

Vue3Vite构建步骤 用vite初始化vue项目(回车) npm create vitelatest vueVitePro -- --template vue安装配置路由vue-router npm install vue-router4 import router from ./router/index.js createApp(App).use(router).mount(#app) 安装 element-plus 及图标 npm ins…

一个PCA加速技巧

EVD-PCA PCA推导&#xff1a;PCA主成分分析法浅理解 具体数值如1030410304是我机器学习课程实验的数据集参数&#xff0c;这里关注数字量级即可。 code % EVD-PCA数据降维 % input: DN output:KN function [Z, K] EVD_PCA(X, K, weight)fprintf(Running EVD-PCA dimension…

Matplotlib学习笔记(第二章 2.13 Matplotlib中的图形(二))

路径(Paths) 你可以使用matplotlib.path模块在Matplotlib中添加任意路径&#xff1a; Fig. 6: Path Patch 三维绘图(Three-dimensional plotting) mplot3d工具包(参见see Getting started and mplot3d-examples-index))支持简单的3D图形&#xff0c;包括曲面、线框、散点图和…

【华为上机真题 2022】玩牌高手

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

【OpenCV-Python】教程:4-5 SURF (Speeded-Up Robust Features) 介绍

OpenCV Python SURF &#xff08;Speeded-Up Robust Features&#xff09; 介绍 【目标】 SURF的基础 【理论】 SURF 是 SIFT 的提速版本&#xff1b; 在SIFT中&#xff0c;Lowe用 DoG 近似 LoG&#xff1b;SURF 走的更远一点&#xff0c;用 box filter 近似 LoG 。下图显…