DAY39:贪心算法(七)根据身高重建队列(注意思路)+最少箭引爆气球(重叠区间)

文章目录

    • 406.根据身高重建队列(注意思路)
      • 思路
        • 两个维度
        • 降序排序注意点
      • 完整版
        • vector容器插入相关复习
        • 为什么能直接根据ki数值插入ki位置的下标
      • 时间复杂度
      • vector-insert操作存在的问题
      • 链表优化版
      • 时间复杂度
      • list和vector的插入与访问操作区别
    • 452.最少弓箭引爆气球(重叠区间)
      • 思路
        • 情况分析
      • 完整版
        • 时间复杂度
        • 弓箭初值设置的原因
      • 总结

406.根据身高重建队列(注意思路)

  • 如果某元素前面有k个满足条件的元素那么这个元素的下标就是k,而不是k-1。本题排序结束之后,如果想要>=hi的元素个数=ki,那么需要插入的位置下标就是ki

  • 本题的两个维度,和 135.分发糖果 类似,当遇到两个维度的问题的时候,一定不要两个维度同时考虑,需要先考虑一个再考虑另一个!

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 01 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0123 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length

题目数据确保队列可以被重建。

思路

本题首先要理解题意,是针对每个人属性第二栏的有多少个人比此人高,来对队列重新排列,使得队列前面h>=此人身高hi的人数=ki,如下图所示:

在这里插入图片描述

两个维度

本题有h和k两个维度,看到这种题目一定要想如何确定一个维度,然后再按照另一个维度重新排列

本题的两个维度体现在同时有两个需要考虑的限制条件。朴素的想法应该是直接降序排序,然后降序排序的同时满足hi≥当前hi的数字个数=ki,但是不能同时满足

所以,我们需要先把hi降序排序,ki放在降序之后考虑。因为如果按照k来从小到大排序,排完之后,会发现k的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。而当身高h定死的时候,k也就定死了(也就是降序排列之后,对于每个人,前面有多少个比他高的人就定死了),此时再去调整K,就会方便很多。

移动策略如下图所示:

在这里插入图片描述
hi先降序排完,再根据ki的情况去insert。降序排序结束之后,再从头开始遍历,对于每个组合{h,k},判断该数字前面>=h的数字个数,并且把该向量放到个数=k的下标位置

例如{6,1}这个例子,从头遍历需要找到1个大于6的数字,找到的数字是第一个数字nums[0]=7>6,所以放在nums[1]的位置。遍历第一遍的策略情况如图粉色线条所示。

降序排序注意点

注意,在降序排序的过程中,同样hi的组合,应该是ki较小的放在前面。也就是说{5,0}{5,2}这个组合,应该是{5,0}放在前面。我们假设{5,2}放在前面,那么先遍历{5,2},再遍历{5,0},遍历到{5,0}的时候把{5,0}又放在了{5,2}的前面,这个时候{5,2}前面就又多了一个5!就会导致结果错误(因为大于/等于都算)。

完整版

  • 降序排序需要满足两条,一条是降序,一条是相同的时候k从小到大排!注意cmp的写法,是比较了一维数组的两个元素
  • 降序排列结束之后,如果想要>=hi的元素个数=ki,那么需要插入的位置下标就是ki
class Solution {
public:
    //注意cmp接收的是两个一维数组,而不是二维数组
    static bool cmp(vector<int>& P1,vector<int>& P2){
        if(P1[0]>P2[0])  return true;//整体降序
        if(P1[0]==P2[0]){
            if(P1[1]<P2[1]) 
                return true;//p1[0]相同的时候按照p1[1]升序
        }
        return false;
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        //先对所有的hi降序排序,因为本题的people中的变量是{a,b},所以需要自定义sort cmp
        sort(people.begin(),people.end(),cmp);
        //定义新的二维数组作为输出
        vector<vector<int>>result;
        //开始遍历排序后的people
        for(int i=0;i<people.size();i++){
            //因为此时已经排序完毕,所以[6,1]直接插入到下标为1的地方,[5,0]直接插入下标为0的地方
            int position=people[i][1];//people[i][1]就代表着第i个集合people的第二个元素!
            //元素放到对应的二维结果数组里
            result.insert(result.begin()+position,people[i]);
        }
        return result;

    }
};

vector容器插入相关复习

(1条消息) vector容器语法相关_大磕学家ZYX的博客-CSDN博客

为什么能直接根据ki数值插入ki位置的下标

这也是本题的一个思维问题,当我们降序排序结束之后,降序排序就是为了把大于这个元素的因素,全都放在这个元素的前面。因此,以[7,1]为例,当遍历到[7,1]的时候,[7,1]前面的元素一定是>=[7,1]的!

因此此时如果想要前面只有ki个>=[7,1]的元素直接把[7,1]移动到ki的位置就行了

前面有k个满足条件的元素,下标又是从0开始,因此当前下标就是k而不是k-1

在这里插入图片描述

时间复杂度

在C++中,std::vector::insert的时间复杂度是O(n),其中n是从插入点到vector末尾的元素数量。这是因为插入新元素时,所有在插入点后的元素都需要移动以创建空间。因此,对于在vector的开头插入元素,需要移动所有的元素,这是最糟糕的情况,对应于O(n)的时间复杂度。对于在vector的末尾插入元素,不需要移动任何元素,这是最好的情况,对应于O(1)的时间复杂度。

代码中的循环体内使用了std::vector::insert,因此,循环的每一次迭代都可能需要移动元素。在最糟糕的情况下,这个代码的时间复杂度是O(n^2),其中n是people中的元素数量。

此外,还需要考虑到代码中的排序操作。std::sort函数的时间复杂度通常为O(n log n),其中n是要排序的元素数量。

  • 时间复杂度:O(nlog n + n^2)
  • 空间复杂度:O(n)

vector-insert操作存在的问题

使用vector是非常费时的,C++中vector(可以理解是一个动态数组,底层是普通数组实现的)如果插入元素大于预先普通数组大小,vector底部会有一个扩容的操作,即申请两倍于原先普通数组的大小,然后把数据拷贝到另一个更大的数组上

所以使用vector(动态数组)来insert,是费时的,插入再拷贝的话,单纯一个插入的操作就是O(n2)了**,甚至**可能拷贝好几次,就不止O(n2)了

因此我们这道题,在结果数组的数据结构选择上,可以选择把vector换成List。list底层是链表实现,链表不存在双倍扩容的问题

链表优化版

  • list不支持随机访问迭代器,因此result.begin()+position这种操作是不被允许的。
class Solution {
public:
    static bool cmp(vector<int>& P1,vector<int>& P2){
        if(P1[0]>P2[0])  return true;
        if(P1[0]==P2[0]){
            if(P1[1]<P2[1]) 
                return true;
        }
        return false;
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort(people.begin(),people.end(),cmp);
        //结果数组类型修改为list<vector<int>>
        list<vector<int>>result;
        
        //遍历排序后的people
        for(int i=0;i<people.size();i++){
            int position=people[i][1];
            //找到position位置之后,定义迭代器再插入
            list<vector<int>>::iterator it = result.begin();
            //注意这里insert的写法,先寻找插入位置
            while(position--){
                it++;
            }
            //while结束之后找到插入位置
            result.insert(it,people[i]);
        }
        //把结果转换为vector<vector<int>>,相当于构造新的二维vector
        return vector<vector<int>>(result.begin(),result.end());

    }
};

时间复杂度

区别讲解:代码随想录 (programmercarl.com)

链表的做法,时间复杂度也是O(nlog n + n^2)

首先,std::list插入操作的时间复杂度是O(1),但这只是指插入操作本身,即在已知要插入的位置的情况下的插入。然而,你需要找到要插入的位置,而在std::list中找到一个位置的时间复杂度是O(n)

在代码中有一个while循环,用于找到每个元素应插入的位置。这个查找操作的时间复杂度是O(n)。因此,每次插入的总时间复杂度(查找+插入)是O(n)。由于你在循环中对每个元素都进行了这样的操作,因此,总的时间复杂度仍然是O(n^2)

因此,链表做法的时间复杂度还是O(n log n + n^2)。其中,O(n log n)对应于排序操作,O(n^2)对应于插入操作

std::vector的情况类似。std::vector插入操作的时间复杂度是O(n),但这已经包含了查找位置和插入两个步骤(对于std::vector,查找位置的时间复杂度是O(1),但插入的时间复杂度是O(n))。所以,std::vector的做法的时间复杂度也是O(n log n + n^2)

vector的主要问题在Insert上,我们使用vector来做insert的操作,insert每一次插入都会动态扩容虽然表面上复杂度是O(n2),但是其底层都不知道额外做了多少次全量拷贝了,所以算上vector的底层拷贝,整体时间复杂度可以认为是O(n2 + t × n)级别的,t是底层拷贝的次数

list和vector的插入与访问操作区别

博客整理:list和vector对比

std::liststd::vector是C++中的两种常见数据结构,它们在不同的使用场景下各有优势。

  • std::vector的内部实现是动态数组,它在连续的内存块中存储数据。这使得std::vector访问元素时具有非常高的效率,因为可以直接通过索引来访问元素,时间复杂度为O(1)。然而,std::vector在插入和删除元素时可能需要移动大量的元素,特别是在非尾部进行插入或删除操作时,时间复杂度为O(n)
  • std::list的内部实现是双向链表,它在非连续的内存块中存储数据。这使得std::list插入和删除元素时具有非常高的效率,因为你只需要修改相关节点的指针,无需移动其他元素时间复杂度为O(1)。然而,std::list在访问元素时可能需要遍历整个链表,时间复杂度为O(n)

如果主要的操作是插入元素insert操作,那么使用std::list会比使用std::vector更高效。

虽然插入操作的理论时间复杂度没有改变,但在实践中,由于std::list不需要移动元素,所以实际运行时间会更短。这就是为什么使用std::list后代码运行时间减少的原因。

452.最少弓箭引爆气球(重叠区间)

  • 重叠区间题目需要注意元素初值的问题,包括计数变量的初值,以及有时候需要考虑数组i=0时候的初值(因为重叠判断大都是i=1开始的)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstartxend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8][1,6]-在x = 11处发射箭,击破气球[10,16][7,12]

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]]
输出:2
解释:气球可以用2支箭来爆破:

-在x = 2处发射箭,击破气球[1,2][2,3]-在x = 4处射出箭,击破气球[3,4][4,5]

提示:

  • 1 <= points.length <= 10^5
  • points[i].length == 2
  • -2^31 <= xstart < xend <= 2^31 - 1

思路

本题是一道经典的重叠区间类型题目,重点就是用一只弓箭尽量射重叠最多的气球。题意示意图如下。

在这里插入图片描述
本题关键在于代码的模拟。首先是记录重叠的气球,再引爆气球。

首先,想要得到重叠情况的统计,需要先对气球左边界进行排序。按照左边界对气球排序之后,才能得到大概类似上图,气球相邻的情况,方便处理气球。

情况分析

  • 气球不重叠:第i个气球左边界>第i-1个气球的右边界,说明这两个气球一定不重合,此时一定要添加一个弓箭
//注意数组points第一个量是第几个气球,第二个量是左边界or右边界
//示例:points = [[10,16],[2,8],[1,6],[7,12]]
if(i>0&&points[i][0]>points[i-1][1]){
    //不重叠一定要添加弓箭
    arrow++;
}
  • 气球重叠:判断右边界就可以判断相邻气球是不是重叠了,i左边界<=i-1右边界
  • 但是气球重叠的情况存在一个问题,如果气球一直重叠,我们需要判断共有几个重叠的气球可以用一根箭。此时我们使用的方法是更新最小右边界,示意图如下所示。
  • 更新的右边界是最新气球的右边界,是取最新气球右边界上一个重叠气球右边界的最小值,注意是取最大值,因为points[i-1][1]可能大于points[i][1]
if(points[i][0]<=points[i-1][1]){
    //此时,第i个气球和第i-1重合了,还需要判断是不是和下一个也重合
    //方法:更新最小右边界,也就是把第i个气球的右边界,取为第i-1个气球的右边界和第i个的右边界的最大值
    points[i][1]=min(points[i-1][1],points[i][1]);  
}

在这里插入图片描述

完整版

  • arrow初值设置为1,是因为这样遍历下去,到了最后一个就会缺失弓箭增加的逻辑,此时直接初值设置为1即可
  • 也可以考虑,当气球不为0的时候,至少需要一只箭,所以初值是1
class Solution {
public:
    static bool cmp(vector<int>&a,vector<int>&b){
        if(a[0]<b[0]) 
            return true;
        return false;
    }
    int findMinArrowShots(vector<vector<int>>& points) {
        int arrow=1;
        if(points.size()==0) return 0;
        //先对气球左边界进行升序排序
        sort(points.begin(),points.end(),cmp);
        //排序完了进行遍历,先是不重叠
        for(int i=1;i<points.size();i++){
            if(points[i][0]>points[i-1][1]){
                arrow++;
            }
            //其他情况就都是重叠
            else{
                //重叠,更新最小右边界
                points[i][1]=min(points[i-1][1],points[i][1]);
            }
        }
	 return arrow;
    }
};

时间复杂度

  • 时间复杂度:O(nlog n),因为有一个快排(加法+n省略)
  • 空间复杂度:O(1),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间

弓箭初值设置的原因

依然以上面的图为例,我们按照更新最小右边界的逻辑,遇到重叠的就继续遍历,遇到不重叠才++,这种逻辑在遇到最后一个元素的时候,就缺失了弓箭++的操作。

在这里插入图片描述
但是,在只有最后一个元素是这样,其他元素不受影响的情况下,我们可以直接通过调整初值来实现逻辑,也就是直接把初值设置为1即可!这样就不需要在代码里单独加上处理最后一个数字的逻辑了。

总结

这道题目贪心的思路很简单也很直接,就是重复的一起射了,但是真正模拟引爆气球是有难度的。

我们需要注意的一点是,气球并不需要真的引爆,只需要累积弓箭数目+1就行了

另外,气球的左右数值也不是不能改变的,我们可以通过更新最小右边界的形式,相当于"修改"这个气球,使得当前气球继续判断和下一个气球是否重合的逻辑。

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

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

相关文章

CentOS7安装详细安装

CentOS 7镜像下载 官网下载链接&#xff1a;http://isoredirect.centos.org/centos/7/isos/x86_64/ step1: 进入下载页&#xff0c;选择阿里云站点进行下载 Actual Country 国内资源 Nearby Countries 周边国家资源 阿里云站点&#xff1a;http://mirrors.aliyun.com/cento…

​浅谈大型语言模型

大型语言模型&#xff08;Large Language Models&#xff0c;LLMs&#xff09;是一类强大的人工智能模型&#xff0c;具有出色的自然语言处理能力。它们在许多任务中表现出色&#xff0c;如机器翻译、文本摘要、对话生成和情感分析等。下面我们将介绍大型语言模型的训练和生成过…

计算机体系结构基础知识介绍之缓存性能的十大进阶优化之非阻塞缓存(四)

优化四&#xff1a;非阻塞缓存&#xff0c;提高缓存带宽 对于允许乱序执行的流水线计算机&#xff0c;处理器不需要因数据高速缓存未命中而停止。 例如&#xff0c;处理器可以继续从指令高速缓存获取指令&#xff0c;同时等待数据高速缓存返回丢失的数据。 非阻塞高速缓存或无…

MYSQL的基本数据类型和建库

1.创建数据库&#xff0c;删除数据库&#xff0c;查询创建数据的语句&#xff0c;使用数据库&#xff0c;查询当前默认的数据库以及使用的编码方式校验规则 2.数字&#xff0c;文本&#xff0c;日期 在一章表中定义多个字段&#xff0c;要使用今天提到的所有的数据类型 1创建数…

为了实现上网自由,我做了一个多功能串口服务器

项目作者&#xff1a;小华的物联网嵌入式之旅 介绍&#xff1a;从事电气自动化行业&#xff0c;多次获得物联网设计竞赛&#xff0c;爱好嵌入式设计开发&#xff0c;物联网开发。 设计方案思路的由来&#xff0c;是因为我们现在的开发板基本需要通过串口与WIFI模组或以太网模…

图片框架Glide学习总结及插件实现

一.前言 图片加载框架个人选择的是Glide&#xff0c;该框架非常优秀&#xff0c;其知识体系很庞大&#xff0c;个人就对Glide部分知识的学习做一下总结&#xff0c;同时对框架的使用做一下封装&#xff0c;做成插件。 二.知识主干 知识主干如下&#xff0c;每一部分的知识会…

Network Neuroscience:整个生命周期的功能连接体指纹

导读 随着年龄的增长&#xff0c;人脑功能结构发生了系统性的变化。然而&#xff0c;功能连接(FC)作为一种检测独特“连接体指纹”的强大特征&#xff0c;使个体能够在同龄人中被识别出来。虽然已在年轻人样本中观察到这种指纹&#xff0c;但该方法在整个生命周期内的可靠性尚…

实现【Linux--NTP 时间同步服务搭建】

实现【Linux--NTP 时间同步服务搭建】 &#x1f53b; 前言&#x1f53b; 一、NTP 校时&#x1f530; 1.1 NTP 服务校时与 ntpdate 校时的区别&#x1f530; 1.2 NTP 校时服务搭建&#x1f530; 1.2.1 确认 ntp 的安装&#x1f530; 1.2.2 配置 ntp 服务&#x1f530; 1.2.3 启动…

QNAP威联通NAS搭建SFTP服务,并内网穿透实现公网远程访问

文章目录 前言1. 威联通NAS启用SFTP2. 测试局域网访问3. 内网穿透3.1 威联通安装cpolar内网穿透3.2 创建隧道3.3 测试公网远程访问 4. 配置固定公网TCP端口地址4.1 保留一个固定TCP端口地址4.2 配置固定TCP端口地址4.3 测试使用固定TCP端口地址远程连接威联通SFTP 转载自远程内…

优化|一阶方法:求解不具有凸性和lipschitz连续性的复合问题

论文解读者&#xff1a;陈康明&#xff0c;赵田田&#xff0c;李朋 编者按&#xff1a;​ 对于大多数一阶算法&#xff0c;我们会在收敛性分析时假设函数是凸的&#xff0c;且梯度满足全局 Lipschitz 条件。而本文中&#xff0c;对于某一类特殊函数。我们不仅不要求函数是凸的…

一次源码编译安装PostgreSql失败

需要perl&#xff1b;之前博文已提到&#xff1b;之前有一种编程语言叫perl&#xff0c;此perl应该不是那个&#xff1b;可到其官网下载&#xff0c;Perl Download - www.perl.org 安装时添加到环境变量&#xff1b; 可能是一个东西&#xff1b;有编程语言和工具&#xff1b;大…

html实现多种风格的时间轴(附源码)

文章目录 1.设计来源1.1 对称风格时间轴1.2 横向风格时间轴1.3 回忆风格时间轴1.4 记事风格时间轴1.5 简易风格时间轴1.6 科技风格时间轴1.7 列表风格时间轴1.8 跑道风格时间轴1.9 人物风格时间轴1.10 容器风格时间轴1.11 沙滩风格时间轴1.12 双边风格时间轴1.13 图文风格时间轴…

CRM系统中AI如何进行销售线索评分?有什么好处(上)

每个公司的TOP销售都是精明的猎手。他们善于从大量潜在客户中挑出最可能购买的&#xff0c;把最好的时间、精力和资源给到高意向客户。意向度差一些的排在后面&#xff0c;在资源分配上也会降低。现在&#xff0c;您可以通过AI来进行线索评分&#xff0c;可以说CRM销售线索评分…

【Linux】 Linus世界,WIndows VS Linux

文章目录 前言WindowsLinux操作系统Windows VS Linux收费情况技术支持安全性开源 区别 前言 在电脑世界有两种十分常见的电脑操作系统——Linux与和Windows&#xff0c;相信对电脑有一定了解的人对它们一定并不陌生&#xff01;但是在我们的使用过程中&#xff0c;是否有什么事…

MySQL用户管理

目录 用户管理 用户 用户信息 创建用户 删除用户 修改用户密码 数据库的权限 给用户授权 回收权限 用户管理 如果我们只能使用root用户&#xff0c;这样存在安全隐患。这时&#xff0c;就需要使用MySQL的用户管理。 用户 用户信息 MySQL中的用户&#xff0c;都存储…

3 springboot更改tomcat的端口和启动时的banner

3.1 更改tomcat端口 点击resources下的application.properties。 然后&#xff0c;添加以下信息&#xff0c;即可把端口号更改为8081。 # 更改项目的端口号 server.port80813.2 更改启动时的banner 首先&#xff0c;进入网站&#xff1a;https://www.bootschool.net/ascii-art…

STL源码刨析 string实现

目录 一. string 类介绍 二. string 的简单实现 1. 类内成员变量 2. Member functions string ~string operator string(const string& str) 3. Capacity size capacity empty clear reserve resize 4.Modifiers push_back append operator insert era…

微服务:Springboot集成Hystrix实现熔断、降级、隔离

文章目录 前言知识积累Springboot集成Hystrix1、maven依赖引入2、application开启feign的hystrix支持&#xff08;客户端配置限流降级熔断&#xff09;3、入口类增加EnableFeignClients EnableHystrix 开启feign与hystrix4、feign调用增加降级方法服务端配置限流降级熔断(选择使…

MySQL基础篇第1章(数据库概述)

文章目录 1、为什么要使用数据库2、数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库管理系统排名2.4 常见的数据库介绍 3、MySQL介绍3.1 概述3.2 MySQL发展史重大事件3.3 关于MySQL 8.03.4 Oracle VS MySQL 4、RDBMS 与 非RDBMS4…

基于Python热门旅游景点数据分析系统设计与实现

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专…