2020华为软件精英挑战赛——总结

2020华为软件精英挑战赛

github:https://github.com/jadyntao/HuaWei_CodeCraft_2020

队伍:助教小分队(粤港澳赛区)

  • 初赛:0.2102(赛区第5)
  • 复赛:11.8445(赛区第2)
  • 决赛:909.8489(全国第24)

初赛

初赛题目:

题目概述

通过金融风控的资金流水分析,可有效识别循环转账,辅助公安挖掘洗钱组织,帮助银行预防信用卡诈骗。基于给定的资金流水,检测并输出指定约束条件的所有循环转账,结果准确,用时最短者胜。

输入
  • 本端账号ID和对端账号ID为一个32位的无符号整数
  • 转账金额为一个32位的无符号整数
  • 转账记录最多为28万条
  • 每个账号平均转账记录数< 10
  • 账号A给账号B最多转账一次
输出
  • 第一行输出:满足限制条件下的循环转账个数。
    • 说明:数据集经过处理,会保证满足条件的循环转账个数小于300万。
  • 第二行开始:输出所有满足限制条件的循环转账路径详情。
    • 循环转账的路径长度最小为3(包含3)最大为7(包含7)
    • 输出循环转账路径要按照指定排序策略进行排序:每条循环转账中,ID(ID转为无符号整数后)最小的第一个输出;总体按照循环转账路径长度升序排序;同一级别的路径长度下循环转账账号ID序列,按照字典序(ID转为无符号整数后)升序排序。

算法理论

查找有向图中所有的简单环的方法:

  • Tiernan - O(V.const^V)
  • Tarjan - O(VEC)
  • Johnson - O(((V+E)C)
  • Szwarcfiter and Lauer - O(V+EC)
  • DFS

初赛过程

  • test_main1:
    • 知乎大佬的第一个baseline版本 9.5
  • test_main2:
    • 在test_main1的基础上引入5+2策略
  • test_main3:
    • 在test_main1的基础上打算将vector改成array,没完成
  • test_main4:
    • 在test_main1的基础上打算将dfs递归改成迭代,没完成
  • test_main5:
    • 在test_main1的基础上将ofstream改成fwrite,加入了3入度和3出度的dfs剪枝 3.1
  • test_main6:
    • 在test_main5的基础上删除了3出度的dfs剪枝 2.8
  • test_main7:
    • 在test_main6的基础上添加了分长度存结果的vector 2.59
  • test_main8:
    • 在test_main7的基础上将使用vector删重换成使用set
  • test_main9:
    • 在test_main8的基础上添加6+1 2.8
  • test_main10:
    • 在test_main7的基础上将vector改成数组,包括input 2.40
  • main(5):
    • 在test_main7的基础上将vector改成数组,不包括input 2.37 永明的
  • test_main11:
    • 在test_main7的基础上使用reserve修改vector的最大容量,避免频繁扩容。
  • test_main12:
    • 在main(5)的基础上加入6+1
  • test_main13:
    • 在main(5)的基础上修改为先Graph和Graph2每行排序 1.55
  • main(7):
    • 在main(5)的基础上开头用桶排,G1排序,G2不排序,加6+1,每次dfs用lower_bound搜索大于head的起始点开始搜 1.38
  • test_mian14:
    • 在main(7)的基础上加上可视化计时
  • test_main15:
    • 在test_main14的基础上去除了ids
  • test_main16:
    • 在test_main14的基础上去将res和path从vector变成array
  • test61mmap(2):
    • 在test_main14的基础上将fread和fwrite修改成mmap
  • test_main17:
    • 在test_main16的基础上将fread和fwrite修改成mmap 0.67
  • test_main18:
    • 在test_main17的基础上去除了ids 0.6284
  • finalmmapdxcstring2 :
    • 在test_main17的基础上加上多线程dfs,outbuf(12亿)
    • 3线程[0,0.2,0.7,1]是0.4262分,[0,0.12,0.7,1]是0.4045分
  • main(finalmmapdxcstring2):
    • 在finalmmapdxcstring2的基础上改为4线程[0,0.12,0.4,0.7,1] 0.4034分
  • main20:
    • 在main(finalmmapdxcstring2)的基础上去掉res,outbuf(3亿)在dfs里进行,4线程 0.3797(发现total_res_size有错,但结果还是有分,好奇怪)
  • test_main21:
    • 在main20的基础上修正了total_res_size的错误
  • test_main22:
    • 在main20的基础上去掉total_res_size,直接输出700万,0.3711
  • test_main23:
    • 在main22的基础上将4线程的{0,0.12,0.4,0.7,1}改成{0,0.06,0.12,0.23,1} 0.3492
  • test_main24:
    • 在test_main21的基础上修改为多线程抢占,0.3674
  • test_main25:
    • 在test_main23的基础上优化for里的判断语句,dfs里的memcpy发现了bug改为固定7 0.3453
  • main(026):
    • 在test_main24的基础上优化for里的判断语句,dfs里的memcpy改为固定7,在test_main24基础上去掉mmap_write的一句,thread改成pthread 0.26
  • main_final:
    • 在main(026)的基础上dfs_cut减少一层,限制为5W个结点,dfs里的memcpy改为固定6,main函数里优化了*cs+i步骤 0.2101

初赛总结

数据结构上的优化点:
  • IO写入文件

    • 经过测试,速度上mmap > fwrite > ofstream > fprintf
    • ofstream
      • 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
      • 当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中,再写入到物理文件中。
          ofstream outputs(output_file.c_str());
          int res_size = res.size();
          outputs<<res_size<<endl;
          for(auto &i:res){
          	auto path = i.path;
              int i_size = path.size();
              outputs<<path[0];
      
              for(int j=1;j<i_size;j++)
                  outputs<<","<<path[j];
              outputs<<endl;
          }
      
    • fprintf:
      • 由于fprintf写入时,将输入类型先转成字符串,即ASCII码再写入到文件中。
      • 例如,对于整数来说,一位占一个字节,比如1,占1个字节;10,占2个字节;100,占3个字节,10000,占5个字节,所以文件的大小会随数据的大小而改变,对大数据空间占用很大。
          FILE *file = fopen(output_file.c_str(),"w");
      	assert(file!=NULL); 
      	int res_size = res.size();
      	fprintf(file,"%d\n",res_size);
      	for(auto &i:res){
          	auto path = i.path;
              int i_size = path.size();
              fprintf(file,"%u",path[0]);
              for(int j=1;j<i_size;j++){
              	fprintf(file,",%u",path[j]);
      		}
      		fprintf(file,"\n");
          }
          fclose(file);
      
    • fwrite:
      • fwrite 函数按照指定的数据类型将矩阵中的元素写入到文件中,即直接以二进制的形式写入文件中,不经过数据类型转换。
      • fwrite是按二进制写入,所以写入数据所占空间是根据数据类型来确定,比如int的大小为4个字节(一般32位下),那么整数10所占空间为4个字节,100、10000所占空间也是4个字节。所以二进制写入比格式化写入更省空间。
          FILE *file = fopen(output_file.c_str(),"w");
      	assert(file!=NULL); 
      	int res_size = res.size();
      	fprintf(file,"%d\n",res_size);
      	char huanhang[1] = {'\n'};
      	for(auto &i:res){
          	auto path = i.path;
              int i_size = i.length;
              for(int j=0;j<i_size-1;j++){
              	fwrite(str[path[j]].c_str(),sizeof(char),str[path[j]].size(),file);
      		}
      		fwrite(str[path[i_size-1]].c_str(),sizeof(char),str[path[i_size-1]].size()-1,file);
      		fwrite(huanhang,sizeof(char),1,file);
          }
      	fclose(file);
      
    • mmap:
      • mmap 采用内存映射的方式,直接将磁盘上的文件映射到内存(准确的说是虚拟内存)中,不需要其他额外空间,对内存映射区的修改可以与磁盘文件保持同步,故 mmap 的读写速度非常快。
      • 仅支持 linux 系统,另外,mmap 映射区域大小必须是物理页大小(page size)的整倍数(32 位系统中通常是 4k 字节)。
      • mmap可以用于多线程上进行文件读写。
      	int fd=open(output_file.c_str(),O_CREAT|O_RDWR,0666);
      	1==ftruncate(fd,total_offset);
      	char*p=(char*)mmap(NULL,total_offset,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
      	memcpy(p,out.c_str(),out.size());
      	// msync(p,total_offset,MS_SYNC);
      	// munmap(p,total_offset);
      	close(fd);
      
  • 数组代替vector

    • trick:由于不同结点的出度数不一,使用vector是在空间上最合适的方法,但相比于数组来说访问效率要低很多。但是在初赛结束前一天,测试出来线上数据集的最大结点不超过5W,最大出度不超过50,因此改成了静态数组 unsigned
  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值