0x72 随机数据生成与对拍

0x72 随机数据生成与对拍

本节介绍随机数据的生成方式与对拍测试方式。将学习使用C++随机数产生器,根据题目要求构造各种规模的输入数据,用于对自己编写的程序进行检测。同时,也将学习编写简单的脚本,自动化、批量化运行“数据生成程序”和两份不同的“问题求解程序”,并对两份程序的输出结果进行对比——我们把这种过程称为“对拍”。

随机数据生成与对拍可用于一下场景:

1.在无法获得实时测评反馈的比赛中,思考并实现了一个“高分解法”,但实在不会证明自己的结论,或者不能确保自己编写的程序是否完全正确。

这种情况下,建议斟酌分配一些时间,额外编写一份随机数据生成程序、一份用朴素算法求解的程序(通常朴素解法时间复杂度高,但容易实现,不易出错)。然后把“高分解法”与“朴素解法”进行对拍,看二者的输出结果是否始终保持一致。

2.在平时解题时,自己编写的程序无法在Online Judge取得Accepted结果,调试很久之后仍未发现错误,并且不能下载到题目的测试数据,或者虽然能下载到测试数据,但发生错误的数据规模过大,不容易进行调试。

这时,可以编写一个随机数据生成程序,再编写一个使用朴素算法的程序(或者直接在网络上搜索其他人的AC程序),与自己的“错误解法”对拍。我们可以适当调整随机数据的规模,控制在易于人工演算和调试的范围内。虽然数据越小,出错概率越低,但是“对拍”脚本能够批量化执行,在成千上万次检测中,一般总能找到一个造成错误的小规模数据。

3.有一个不错的构思,自己出了一道题目。

此时当然需要生成一些测试数据,并且需要用“对拍”来检测自己编写的“标准程序”的正确性。不过,除了随机数据外,通常还需要增加一些特殊构造的数据,保证数据的全面性。

1.随机数据生成

头文件cstdlib(stdlib.h)包含了randsrand两个函数,以及RAND_MAX常量。

RAND_MAX是一个不小于32767的整数常量,它的值与操作系统、编译环境有关。一般来说,在Windows系统中为32767,在类Unix系统中为2147483647。

rand()函数返回一个0~RAND_MAX之间的随机整数int。

srand(seed)函数接受unsigned int类型的参数seed,以seed为“随机种子”。rand函数基于线性同余递推式生成随机数,“随机种子”相当于计算线性同余时的一个初始参数,感兴趣的可以查阅相关资料。若不执行srand函数,则种子默认为1。

当种子确定下来,接下来产生的随机数列就是固定的,所以这种随机方法也别称为“伪随机”。因此,一般在随机数据生成程序main函数的开头,用当前系统时间作为随机种子。

头文件ctime(time.h)包含time函数,调用time(0)可以返回从1970年1月1日0时0分0秒(Unix纪元)到现在的秒数。执行srand((unsigned)time(0))即可初始化随机种子。

下面的程序可作为随机数据生成器的模版,函数random(n)返回 0 ∼ n − 1 0\sim n-1 0n1之间的随机整数,并综合考虑了操作系统和编译器环境的差异,对int范围内的 n n n均能正常工作。

#include <cstdlib.h>
#include <ctime>
int random(int n)
{
    return (long long)rand()*rand()%n;
}
int main()
{
    srand((unsigned)time(0));
    //...具体内容...
}

若要产生随机实数,则可以先产生一个较大的随机整数,在用它除以10的次幂。若要同时产生负数,则可以先产生一个 0 ∼ 2 n 0\sim 2n 02n之间的随机整数,再减去 n n n,就得到了 − n ∼ n -n\sim n nn之间的随机整数。

随机生成一张 n n n个点 m m m条边的无向图,图中不存在重边、自环,且必须连通,保证 5 ≤ n ≤ m ≤ n ∗ ( n − 1 ) / 4 ≤ 1 0 6 5\leq n\leq m\leq n*(n-1)/4\leq 10^6 5nmn(n1)/4106

pair<int,int> e[1000005];
map<pair<int,int>,bool> h; //防止重边
//先生成一棵树,保证连通
for(int i=1;i<n;++i)
{
    int fa=random(i)+1;
    e[i]=make_pair(fa,i+1);
    h[e[i]]=h[make_pair(i+1,fa)]=1;
}
//再生成剩余的m-n+1条边
for(int i=n;i<=m;++i)
{
    int x,y;
    do{
        x=random(n)+1,y=random(n)+1;
    }while(x==y||k[make_pair(x,y)]);
    e[i]=make_pair(x,y);
    h[e[i]]=h[make_pair(y,x)]=1;
}
//随机打乱,输出
random_shuffle(e+1,e+m+1);
for(int i=1;i<=m;++i)
    printf("%d %d\n",e[i].first,e[i].second);

在树、图结构中,有三类特殊数据常用于对程序进行极端情况下的效率测试:

1.链形数据——有很长的直径。

就是把 N N N个节点用 N − 1 N-1 N1条边连成一条长度为 N − 1 N-1 N1的“链”。这种数据会造成很大的递归深度,也是点分治等算法需要特别注意的数据。

2.菊花形数据——有度数很大的节点。

以1号节点为中心, 2 ∼ N 2\sim N 2N号节点都用一条边与1号节点相连,最终1号节点的度数为 N − 1 N-1 N1。这种数据画出来形似一朵“菊花”,缩点等图论算法若处理不当,复杂度容易在这种数据上退化。

3.蒲公英形数据。

即链形和菊花形数据的综合。令树的一部分构成链,一部分构成菊花,再把两部分连接。

在以上三种数据的基础上,再添加少量的随机的边,即可得到一张包含局部特殊结构、又不失一般性和多样性的图。

2.对拍

假设我们已经编好了三个程序:

1.自己编写的“正解”,即准备提交测评的程序,名为sol.cpp

该程序从文件data.in中读入输入数据,并输出答案到data.out中。

2.自己编写的“朴素解法”程序,名为bf.cpp

该程序从文件data.in中读入输入数据,并输出答案到data.ans中。

3.自己编写的随机数据生成程序,名为random.cpp

该程序输出随机数据到文件data.in中。

依次编写这三个程序,得到三个可执行文件,例如在Windows系统下得到sol.exebf.exerandom.exe

现在我们需要编写一个脚本,循环执行以下过程:

1.运行随机数据生成器random

2.运行“正解”程序sol

3.运行“朴素解法”程序bf

4.对比文件data.outdata.ans的内容是否一致。

Windows和类Unix系统中分别有bat批处理脚本和bash脚本。不过,为了避免介绍一门新的脚本语言,这里就用C++语言来编写“对拍”程序。

头文件cstdlib(stdlib.h)中提供了一个函数system,它接受一个字符串参数,并把该字符串作为系统命令执行。例如代码system("C:\\random.exe")执行C盘根目录下的random.exe文件。

Windows系统命令fc或类Unix系统命令diff可以执行文件比对的工作,它们接受两个文件路径,比较二者是否一致。若一致,返回0,否则返回非零值。

Windows系统对拍程序

#include<cstdlib>
#include<cstdio>
#include<ctime>
int main()
{
    for(int T=1;T<=10000;T++)
    {
        //自行设定适当的路径
        system("C:\\random.exe");
        //当前程序已经运行的CPU时间,Windows下单位ms,Unix下单位s
        double st=clock();
        system("C:\\sol.exe");
        double ed=clock();
        system("C:\\bf.exe");
        if(system("fc C:\\data.out C:\\data.ans"))
        {
            puts("Wrong Answer");
            //程序立即退出,此时data.in即为发生错误的数据
            return 0;
        }
        else
        {
            printf("Accept,测试点 #%d,此时 %.0lfms\n",T,ed-st);
        }
    }
}

编译运行该C++程序即可开始“对拍”过程。

Unix系统对拍程序

Windows系统对拍程序的基础上,更改system中的路径格式,并把fc命令改为diff命令,用时单位改为“秒”。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谷神星ceres

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值