zoukankan      html  css  js  c++  java
  • Python在竞赛中的应用-测试数据的构造与对拍 --算法专题精解(31)

    本系列文章将于2021年整理出版,书名《算法竞赛专题解析》。
    前驱教材:《算法竞赛入门到进阶》 清华大学出版社
    网购:京东 当当      作者签名书

    如有建议,请加QQ 群:567554289,或联系作者QQ:15512356
    @

      很多人认为Python是最受欢迎的编程语言,它编码简洁,有强大的库。
      Python早已应用在算法竞赛中,能节省大量比赛时间。
      大学的某些算法竞赛(ICPC)已经支持用Python语言提交代码。即使比赛不支持用Python提交代码,而Linux系统一般是预装Python的,可以用Python做辅助工作。
      Python的优点有编码简便、处理大数非常简单、构造测试数据比C++更简单等。Python的主要问题是执行时间慢,比C++、java慢得多,竞赛队员可能不会直接用Python交题。不过,Python可以作为工具使用,常用的是构造数据、写对拍代码。一个典型的应用是:如果能用打表法交题,可以先用python打表出数据,然后用c++或直接用Python提交。
      读者会发现,用Python做这些事,比用C++容易得多,这在紧张的比赛中是很有用的。
      若读者想学习用Python写算法竞赛题的代码,建议到力扣网站(leetcode-cn.com)练习,每个题都有人提交Python题解。
      Python的强大,主要是因为它庞大的库。Python有两种版本Python 2、Python 3,两者不兼容。本节基于 Python 3。Python编译环境下载地址:https://www.python.org/downloads/
      下面介绍Python在算法竞赛中的应用。

    C.1 计算大数

      下面的题目是计算大数。


    阶乘之和 洛谷 P1009https://www.luogu.com.cn/problem/P1009
    题目描述:计算S=1!+2!+3!...+n! (n≤50)


      计算结果是一个天文数字,如果用C++编码,需要用高精度,很复杂。而Java和python都能直接处理大数。
      用Java编码很简单:

    import java.math.*;
    import java.util.*;
    public class Main {
    	public static void main(String[] args) {
             Scanner in= new Scanner(System.in);
             int n = in.nextInt();                               
             BigInteger s = new BigInteger("1");
             BigInteger ans = new BigInteger("0");
             for (int i = 1; i <= n; i++) {
                  s = s.multiply(new BigInteger(String.valueOf(i)));
                  ans = ans.add(s);
             }
             System.out.println(ans);
        }
    }
    

      用Python编码更简单:

    n = int(input()) 
    s = 1 
    ans = 0
    for i in range(1,n+1,1):    
        s *= i
        ans += s 
    print(ans)
    

    C.2 构造随机数和随机字符串

      用Python构造测试数据,比c++简单得多。它能直接产生极大的数字,方便地产生随机字符等。下表列出了一些典型的随机数、随机字符串构造方法[1]

    (1)导入库

    import random  
    

      可以写成:

    from random import *
    

      此时后面的代码能够简单一点,例如把random.randint直接写为randint

    (2)在指定范围内生成一个很大的随机整数:

    print (random.randint(-9999999999999999,9999999999999999))  
    

      输出示例:428893995939258

    (3)在指定范围内(0到100000)生成一个随机偶数:

    print (random.randrange(0, 100001, 2))   
    

      输出示例:14908
    (4)生成一个0到1之间的随机浮点数:

    print (random.random())   
    

      输出示例:0.2856636141181378
    (5)在指定范围内(1到20)生成一个随机浮点数:

    print (random.uniform(1, 20))   
    

      输出示例:9.81984258258233
    (6)在指定字符中生成一个随机字符:

    print (random.choice('abcdefghijklmnopqrst@#$%^&*()')) 
    

      输出示例:d
    (7)在指定字符中生成指定数量的随机字符:

    print (random.sample('zyxwvutsrqponmlkjihgfedcba',5))  
    

      输出示例:['z', 'u', 'x', 'w', 'j']
    (8)导入库

    import string
    

      若写成from string import *,下面的string.ascii_letters改为ascii_letters
    (9)用a-z、A-Z、0-9生成指定数量的随机字符串:

    ran_str = ''.join(random.sample(string.ascii_letters + string.digits, 8))
    print (ran_str)
    

      输出示例:iCTm6yxN
    (10)从多个字符中选取指定数量的字符组成新字符串:

    print (''.join(random.sample(['m','l','k','j','i','h','g','d'], 5)))
    

      输出示例:mjlhd
    (11)打乱数组的顺序:

    items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]   
    random.shuffle(items)
    for i in range(0,len(items),1):      #逐个打印
       print (items[i]," ",end='')
    

      输出示例:1 0 8 3 5 7 9 4 6 2

    C.3 数组去重

    1、整数去重
      随机生成的整数,很多是重复的,而一般情况下需要不重复的数据。下面给出两种去重方法。
      第1种方法用set(),速度极快。set()的时间复杂度差不多是O(n)的,和生成随机数的时间差不多。set()去重不能保持数据的原顺序。set()去重的原理是hash,所以有时候它返回的结果中部分数据看起来像排过序,但整体上并不是有序的。虽然set()去重后的数据的顺序不是那么随机,但是可以用shuffle()再次把数据打乱,得到排序随机的数组。
      第2种方法是暴力去重,非常非常慢,不过它的结果保持了原数据的顺序。

    def NonRepeatList1(data):   #函数1:set去重,不保持原顺序
        return list(set(data))
    def NonRepeatList2(data):   #函数2:暴力去重,保持原顺序
        return [i for n, i in enumerate(data) if i not in data[:n]]
    #下面测试上面2个函数
    import random
    import time
    time0 = time.time()
    a = []
    for i in range(0,100000,1):                #10万个随机数
        a.append(random.randint(-100000000,100000000))    #随机数取值范围
    #print (a)                                 #可以打印数组看看
    print ("random time =",time.time()-time0)  #统计随机数的生成时间
    
    time0 = time.time()
    b = NonRepeatList1(a)                      #去重,不保持原顺序
    #print (b)                                 #打印看看顺序
    random.shuffle(b)                          #再次打乱顺序
    #print (b)                                 #打印看看是否乱序
    print ("set time =",time.time()-time0)     #统计set()去重的时间
    
    time0 = time.time()
    c = NonRepeatList2(a)                      #去重,保持原顺序
    #print (c)                                 #打印看看是否保持原序
    print ("enum time =",time.time()-time0)    #统计暴力去重的时间
    

      代码中统计了两种方法的执行时间,在作者的电脑上运行,10万个数的时间是:

    random time = 0.10671424865722656
    set time = 0.06288003921508789
    enum time = 99.96579337120056
    

      可见set()是极快的,是暴力去重的1600倍。
      再试试1000万个数,生成随机数和去重的时间是:

    random time = 10.069396495819092
    set time = 10.71152925491333
    

    2、小数去重
      如果要生成不同的小数,简单的办法是先用上面的代码生成去重整数数组,然后把每个整数除以10的幂次即可。例如生成2位小数,把每个整数除以100。

    d = []
    for i in range(0,len(b),1):         #b是去重后的整数数组
        d.append(b[i] / 100)
    

    C.4 构造测试数据和对拍

    1、构造测试数据
      仍然以Hdu 1425为例。


    hdu 1425 sort http://acm.hdu.edu.cn/showproblem.php?pid=1425
    给你n个整数,请按从大到小的顺序输出其中前m大的数。
    输入:每组测试数据有两行,第一行有两个数n, m(0 < n, m < 1000000),第二行包含n个各不相同,且都处于区间[-500000, 500000]的整数。
    输出:对每组测试数据按从大到小的顺序输出前m大的数。


      首先构造测试数据。下面的代码生成60多万个不同的无序的随机数。首先产生100万个随机数,然后用set去重,最后用shuffle打乱即可。

    #设本代码的文件名是aa.py
    import random
    a= []
    b= []
    for i in range(0,1000000,1):      #100万个随机数
        a.append(random.randint(-500000,500000))
    b=list(set(a))               #去重
    
    #print("lena=",len(a))      #验证a的个数是不是100万个
    #print("lenb=",len(b))      #b的个数有60多万个
    
    random.shuffle(b)          #打乱b
    print(len(b),random.randint(1,len(b)))   #打印n、m
    for i in range(0,len(b),1):              #逐个打印
        print ( b[i],end=' ')  
    
    #下面的做法是存到一个文件里面,其实不需要
    ''' 
    f = open("d:data.in", "w")    #输出到文件里
    print(len(b),random.randint(1,len(b)),file=f)
    for i in range(0,len(b),1):      #逐个打印
        print ( b[i],end=' ',file=f)    
    f.close()
    '''
    

    2、对拍
      下面以Windows环境为例说明构造测试数据和对拍的过程,linux环境类似。
      把上面的代码存为文件aa.py,执行下面的命令,输出测试数据到文件data.in

    D:>C:UsershpAppDataLocalProgramsPythonPython39python aa.py >data.in
    

      作者的python安装在目录“C:UsershpAppDataLocalProgramsPythonPython39”,读者可以按自己的目录操作,或者设置环境变量,就不用输这个目录了。
      下面给出Hdu 1425的对拍代码,功能是:先输入n和m,然后输入n个数,排序后,打印出前m大的数。与C++对拍代码相比,Python代码更简单。

    #设本代码的文件名是bb.py
    n,m = map(int,input().split())  #输入n、m
    a=[int(n) for n in input().split()]  #输入n个数
    a.sort()  #排序
    for i in range(n-1,n-m,-1):  #打印出前m-1大的数
        print ( a[i],end=' ')
    print ( a[n-m])              #打印出第m大的数
    

      把代码存为文件bb.py。Windows环境下,执行以下命令,读输入数据data.in,输出数据到py.out

    D:>C:UsershpAppDataLocalProgramsPythonPython39python bb.py <data.in >py.out
    

      前一节“测试数据的构造与对拍https://blog.csdn.net/weixin_43914593/article/details/106863166”的c++代码是:

    #include<bits/stdc++.h>
    using namespace std;
    const int MAXN = 1000001;
    int a[MAXN];
    int main(){
        int n,m;
        while(~scanf("%d%d", &n, &m)){
            memset(a, 0, sizeof(a));
            for(int i=0; i<n; i++){
                int t;
                scanf("%d", &t); //此题数据多,如果用很慢的cin输入,肯定TLE
                a[500000+t]=1;    //数字t,登记在500000+t这个位置
            }
            for(int i=MAXN; m>0; i--)
                if(a[i]){
                    if(m>1)  printf("%d ", i-500000);
                    else      printf("%d
    ", i-500000);
                    m--;
                }
        }
        return 0;
    }
    

      设代码的文件名是hash,执行代码,读取输入data.in,输出到文件hash.out

    D:>hash <data.in >hash.out
    

      下面比较2个代码的输出是否一样,执行以下命令:

    D:>fc py.out hash.out /n
    

      若文件一样,输出(上面一行中的hash.out,在下面显示HASH.OUT,这不是笔误):

    正在比较文件 py.out 和 HASH.OUT
    FC: 找不到差异	
    

    3、把所有过程写成批处理文件[2]

    (1)windows环境
      把上述过程写成windows环境的bat批处理。它是一个死循环,在每个循环生成数据并对拍,直到发现错误,出错误的那组输入和输出都留在文件中,可以查看。下面代码中的路径是作者电脑的路径,请读者改为自己电脑的路径。

    @echo off
    set path=C:MinGWin
    g++ -o hash.exe hash.cpp
    :loop
    set path=C:UsershpAppDataLocalProgramsPythonPython39
    python aa.py >data.in
    hash.exe <data.in >hash.out
    python bb.py <data.in >py.out
    set path=C:WindowsSystem32
    fc py.out hash.out
    if errorlevel == 1 pause
    goto loop
    

    (2)linux环境

      linux的文件比较命令是diff。

    [root]#diff -c hash.out sort.out
    

      本文没有在linux环境中试Python,这里给出一个c++的参考,功能类似。

    #!bin/bash
    while true; do
        gcc hash.cpp -o hash.exe
        gcc sort.cpp -o sort.exe
        gcc makedata.cpp -o makedata.exe
        ./makedata.exe >data.in
        ./hash.exe <data.in >hash.out
        ./sort.exe <data.in >sort.out
        if diff hash.out sort.out; then
            echo OK
        else 
            echo wrong
            break
        fi
    done
    

    1. 参考https://www.cnblogs.com/zqifa/p/python-random-1.html ↩︎

    2. 感谢华东理工大学17级队员李震 ↩︎

  • 相关阅读:
    codevs1080线段树练习
    NOIP2015 子串
    codevs1204 寻找子串位置
    字符串匹配的KMP算法
    TYVJ1460 旅行
    基础
    搜索
    二叉排序树
    二叉树
    poj
  • 原文地址:https://www.cnblogs.com/luoyj/p/14241687.html
Copyright © 2011-2022 走看看