无中生有之突击NOIP(7)--Tree

总算来到了最为期待的一张,旁边总是有人会告诉你,这个用树很简单,这个用树都不用做,然而今天我也开始了树,不是每个人都没有权利去追赶,而是你有没有不服输的心没人可以有资格看不起你,没有一个人有资格站在神坛告诉你不行,更何况,你身边的人?站的真的是神坛吗??

言归正传:
对于树我们先给他一个定义:
1.一棵树有任意两个节点有且仅有唯一的一条路径连通。
2.一棵树有n个结点,那么它一定恰好有n-1条边。
3.在一棵树中加一条边可以构成一个回路。
首先树是指任意两个结点间有且只有一条路径无向图。或者说只要是没有没有任何回路的连通图就是树。
为了确定没一个数的形态,在树中我们可以指定一个特殊的结点———根。我们在对任意一棵数进行讨论的时候,将树中的每个点称为结点或者叫节点,且一个点又有很多分结点,我们把这个点叫做父结点其他与其直接相连的点叫做子节点,所以同常一个树会由许多的父节点和子节点,而且大部分的点不仅仅是一种,而是父与子的集合,这就构成了一个类似与家族族谱一样的东西,而最上面的那个我们称其为祖先。就这么点破东西,还打着么多,浪费脑子。

现在开始的才算是正常的学习了:

  1. 二叉树

二叉树是一种特殊的树,我们给其一个严格的定义:二叉树要么为空,要么由根左子树和右子树组成,而左子树和右子树分别是一棵二叉树。

二叉树分为满二叉树和完全二叉树,满二叉树是一个深度为h,且有2的h次方-1个结点的二叉树。

如果一棵二叉树除了最右边的位置有一个或者几个叶结缺少外,其他是丰满的,那么这样的二叉树就是完全二叉树,更为严格的定义是:若设二叉树的高度为h除了第h层外,其他各层结点数都能达到最大个数,第h层从右向左联系缺若干结点。则这个二叉树就是完全二叉树,也就是说如果结点有右子节点,那么它一定有左子节点,其实可以将满二叉树理解成一种特殊的或者极其完美的完全二叉树

这里写图片描述

通过上图我么发现,如果完全二叉树的一个父节点编号为k,那么它左儿子的编号就是2*k,右儿子的编号就是2*k+1。如果一直儿子的编号是x,那么他父节点一定是x/2,注意,这里只求整数部分,即自动向下取整(C++就是这样),且如果一个完全二叉树有n个结点,那么这个完全二叉树的高度为log2 N(log以二为底N的对数,打不出来),简写为logN,即最多有logN层节点。

2.堆

堆是神马?堆就是一种特殊的二叉树,他们保持着某种特性,例如每个父节点比子节点小,我们就叫其为小顶堆,同时还有大顶堆,定义你肯定懂了哈。

这个有什么用呢?假设我们现在有一堆数,我们要找到其中的最小数,这很简单,一个循环可以解决,我们要是需要删除这个最小数并且再加入一个新数,再次求里面的最小数,那只能在重新扫描,假如每个都要来一遍呢?此时这个特殊的结构”堆”就可以解决这个问题。

首先我们按照最小对的要求将这些数放入一棵完全二叉树中。
然后我们开始替换,把替换后的值与下面的两个儿子比较,选择较小的一个与之交换。
然后继续向下交换,直到符合最小堆的特性为止。

向下调整的代码如下:

void sifedown(int i)//传入向下调整的结点编号i
{                   //向下调整
    int,t,flag=0;//flag标记是否需要向下调整
    //当i结点有儿子(至少是有做儿子的情况下)并且有需要继续调整的时候,循环就执行
    while(i*2<=n && flag=0)
    {
        if(h[i] > h[i*2]
            t=i*2;
        else
            t=i;
        //如果有右儿子我们在对右儿子进行讨论
        if(i*2+1 <= n)
        {
            //如果右儿子的值更小,更新较小的结点编号
            if( h[t] > h[i*2+1])
                t=i*2+1;
        }
        //如果发现最小的结点编号不是自己,说明子节点有比父节点更小的
        if(t!=i)
        {
            swap(t,i);交换他们,注意swap函数需要自己来写
            i=t;//更新i为刚才与它交换的儿子结点编号,便于接下来继续向下调整。
        }
        else
            flag=1;
    }
    return ;
}

现在时间复杂度恰好变为了logN,我们可以设一个数据,现在有一亿个数,,进行一亿次删除最小数并增加一个数的操作,原来大约要允许一亿的平方次,而现在只需要11一亿log一亿次,即27亿次在哟哟哟,假设计算机每秒运行10亿次,原来的方法要运行115天!而现在的我们只需要2,7秒,是不是很神奇?再次感受到了算法的伟大吧。

说到这里,如果我们想新增一个数,而不是删除最小值怎么办呢?其实只需要直接将新元素插入到末尾,再根据情况判断新元素是否需要上移,直到其满足了堆的特性为止。

我们每次让其与其父节点比较,为了维护最小堆的特性,我们让其父与其交换,重复循环,直至满足了最小堆的特性为止。
向上调整代码如下:
void siftup(int i)//传入向上调整的编号
{
    int,flag=0;//用来标记是否需要向上调整,
    if(i==1) return;//如果已经到了堆顶就不需要调整了
    while(i!=1 && flag==0)//只有不在堆顶,并且当前结点i的值比父节点小的时候就继续调整。
    {
    //判断是否比父节点的小
        if(h[i]<h[i/2])
            swap(i,i/2);//交换它和它爸爸的位置。
        else
            flag=1;//表示不需要调整了,当前节点的值比父节点的值要大
        i=i/2;//这句话很重要,更新编号i为它父节点的编号,从而便于下一次继续向上调整
    }
    return;
}

到了现在,我们却忽略了一个很重要的问题,该如何定义堆呢?
每次插入一个对,然后升就好,代码如下:

n=0;
for(i=1;i<=m;i++)
{
    n++;
    h[n]=a[i];//或者是scanf("%d",&h[n]);
    siftup(n);
}

但其实还有更为简单的方法,我们先把所有数据延1-n自行编号,在这棵完全二叉树中,我们从最后一个结点开始,依次判断以这个结点为根的子树是否符合最小堆的特性,如果所有的子树都符合最小堆的特性,那么整棵就是最小堆了。
我们从最后一个非叶节点开始遍历即n/2每次使其向下调整。然后直至顶点,再回过头来从上面将本来在顶部的向下移动到底部,也许你会认为这个叙述在代码里难以实现,但实际上,这个代码却只有两行for(i=n/2;i>=1;i--) siftdown(i);用这种方法我们设立的堆的时间复杂度为O(N),

堆还有一个作用是堆排序,与快排一样,堆排序的时间复杂度也是NlogN比如我们现在要从小到大排序可以先建立最小堆,然后每次删除顶部元素将顶部元素输出或者放入到一个新的数组里,直到堆为空位置,
完整代码如下:

#include<stdio.h>
int h[101];//用来存放堆的个数
int n;//用来存储堆中的元素的个数,也就是堆的大小
//交换函数,交换堆中的两个元素的值。
void swap(int x, int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;
    return;
}
//向下调整函数
void siftdown(int i)
{//传入一个需要向下调整的结点编号i,这里传入1,即从堆顶开始向下调整
    int t,flag=0;//flag标记是否向下调整
    //当i结点有儿子(其实是至少有左儿子)并且有需要继续调整的时候就循环执行
    while( i*2<=n && flag=0)
    {//判断他和左儿子的关系,用t记录值较小的结点编号
        if( h[i] > h[i*2})
            t=i*2;
        else 
            t=i;
        //如果它有右儿子,再对右儿子进行讨论
        if(i*2+1<=n)
        {
        //如果右儿子的值更小则更新较小的结点编号
            if(h[t]>h[i*2+1])
                T=I*2+1;
        }
        如果发现最小的节点编号不是自己,说明子结点中有比父结点更小的
        if(t!=i)
        {
            swap(t,i);//交换他们
            i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整。
        }
        else
            flag=1;//否则说明当前的的父节点已经比两个子结都小了,不需要再调整了
    }
    return;
}

void cteat()
{
    int i;//从最后一个非叶结点到第一个结点一次进行向下调整
    for(i=n/2;i>=1;i--)
    {
        siftdown(i);
    }
    return;
}
删除最大的元素
int deletemax()
{
    int t;用一个临时变量来记录堆顶点的点
    t=h[1];
    //将堆的最后一个点赋值到顶堆。
    h[1]=h[n];
    n--;//对减少一个
    siftdown(1);//向下调整
    return t;//返回之前记录的堆的顶点的最小值
}

int main()
{
    int i,num;
    //读入要排序的数字的格式
    scanf("%d",&num);
    for(i=1;i<=num;i++)
        scanf("%d",&h[i]);
    n=num;
    //建堆
    creat();

    //删除顶部元素,连续删除n次,其实也就是从大到小输个来回
    for(i=1;i<=num;i++)
        printf("%d",deletemax());
    getchar();
    getchar();
    return0;
}   

以上只是其中一种方法,当然我们还有另一种更好的办法,从小到大排序的时候不建立最小堆而建立最大堆,最大堆建好后,最大的元素在h[1],因为我们的需求是从小到大排序,希望最大的值放后面,因此我们将h[1]和h[n]交换,此时h[n]就是数组中的最大元素。请注意,交换后还需将h[1]向下调整以保持堆的特性。OK最大的元素已经归为后,需要将堆的大小减一1即n–,并将交换后的信h[1]向下调整以保持堆的特性,如此反复直到堆的大小变成1为止,此时数组中的数就是已经排好的数了

完整代码如下:

#include<stdio.h>;
int h[101];
int n;
//交换函数
void swap(int x,int y)
{
    int t;
    t=h[x];
    h[x]=h[y];
    h[y]=t;
    return;
}

//向下调整函数
void siftdown(int i)//传入一个需要想想啊调整的结点编号i,这里传入1,即从堆的顶点开始,向下调整
{
    int t,flag=0;//用来标记是否需要继续向下调整
    //当i结点有儿子(至少有左儿子的时候)并且有需要继续调整的时候就循环执行
    while(i*2<=n &&flag == 0)
    {
        if( h[i]<h[i*2] )
            t=i*2;
        else
            t=i;
        //如果他有右儿子,再对右儿子进行讨论,
        if(i*2+1 <= n)
        {
            //如果它有右儿子,在对右儿子进行讨论
            if(h[t] < h[i*2+1])
                t=i*2+1;
        }
        //如果发现最大的结点编号不是自己,说明子结点中有比父结点更大的
        if(t!=i)
        {
            swap(t,i)
            i=t;
        }
        else
            flag=1;
    }
    return;
}

//建立堆的函数
void creat()
{
    int i;
    //从最后一个非叶结点到第一个结点依次向上调整
    for(i=n/2;i>=1;i--)
    {
        siftdown(1);
    }
    return;
}

//堆排序
void heapsort()
{
    while(n>1)
    {
        swap(1,n);
        n--;
        siftdown(1);
    }
    return;
}

int main()
{
    int i,num;
    //读入n个数
    scanf("%d",&num);
    for(i=1;i<=num;i++)
        scanf("%d",&h[i]);
    n=num;
    //建堆
    creat();
    //堆排序
    heapsort();
    //输出
    for(i=1;i<=num;i++)
        printf("%d",h[i]);
    getchar();
    getchar();
    return 0;
}

最后总结一下,像这样支持插入元素和寻找最大小值元素的数据结构称为优先队列。如果使用普通队列来实现这两个功能,那么寻找最大元素需要枚举整个队列,这样的时间复杂度比较高。如果是已排序好的数组,那么插入一个元素则需要移动很多元素,时间复杂度仍然很高,而堆就是一种优先队列的实现,可以很好的解决这两种操作。

三、擒贼先擒王——并查集

我们先简述一下并查集的算法,并查集是一个通过一维数组来实现,其本质是维护一个森林。刚开始的时候,森林的每个点都是孤立的,也可以认为每个点都是一个孤立的,也可以理解为每一个点就是一课只有一个结点的树,之后通过一些条件,逐渐将这些树合并成一棵大树,其实合并的过程就是认爹的过程。在认爹过程中,要遵守靠左原则和擒贼先擒王原则。在每次判断两个结点是否已经在同一棵树中的时候(一棵树其实就是一个集合),也要注意必须要求其根源,中间父亲结点(小BOSS)是不能说明问题的,必须找到其祖宗(树的根结点),判断两个结点的组宗是否已经在同一个根节点才行,下面给出一个完整代码:
#include<stdio.h>
int f[1000]={0},n,m,k,sum=0;
//这里初始化,非常重要,数组里面存的是自己数组下标的编号就好了
void init()
{
    int i;
    for(i=1;i<=n;i++)
        f[i]=i;
    return;
}
//这是找爹的递归函数,不停地去找爹,知道找到祖宗为止,其实就是去找犯罪团伙的最高领导人。
//找到每个组织里的大Boss,
//擒贼先擒王原则
int gerf(int v)
{
    if(f[v]==v)
        return v;
    else
    {
        f[v]=getf(f[v]);
        return f[v];
    }
}
//合并两个子集合的函数
void merge(int v,int u)
{
    int t1,t2;//分别为v和u的大BOSS,每次双方的会谈都必须是各自最高领导人才行。
    t1=getf(v1);
    t2=getf(v2);
    if(t1!=t2)//判断两个结点是否在同一个集合,即是否是同一个祖先
        f[t2]=t1;
        //靠左原则,左边变成右边的BOSS,即把右边的集合作为左边的子集合。
//经过路径压缩以后,将f[u]的根的值也赋值为v的祖先f[t1]。
    return;
}
//请从主函数开始阅读程序
int main()
{
    int i,x,y;
    scanf("%d %d",&n,&m);
    init();//初始化是必须的
    for(i=1;i<=m;i++)
    {
    //开始合并团伙
        scanf("%d %d",&x,&y);
        marge(x,y);
    }//最后扫描有多少个独立的团伙
    for(i=1;i<=n;i++)
    {
        if(f[i]==i)
            sum++;
    }
    printf("%d\n,sum);
    getchar();getchar();
    return 0;
}

总结:并查集我们用到的还是会很多,并且有很多人说用模板,我这种STL小白级的人物,就静静的看着你装B。

自述:我们得乐呵滴哈,真真得乐呵滴,你最亲的人,是你自己!为你活着,马上要写完了,感觉好不是滋味,时间好快,COME ON!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
,发送类别,概率,以及物体在相机坐标系下的xyz.zip目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值