实验7 统计话费 实验时间 选做。
实验目的 综合运用所学知识,为实际问题设计高效算法。
问题描述 给定一份通话记录文件 ,包含约10万用户一天共计约100万条通话记录。文件是纯文本格式,每行为一条记录,格式如下: 手机号码(11位)、呼叫类型(2位,00表示主叫,01表示被叫)、通话时长(4位,以秒为单位)、呼叫发生小区(4位)、换行符(2位,即’\r\n’) 例如,文件的第一行:13955191490010225JQHK 表示手机号码为13955191490的用户在JQHK小区被叫,时长为225秒。 计费规则如下:
通话时长以分钟为单位计费,不足一分钟按一分钟计算。
主叫每分钟0.40元(40分)、被叫每分钟0.20元(20分)。
请生成话费账单,输出一份文件,要求输出文件是纯文本格式,每行为一条记录,格式如下: 手机号码(11位)、总费用(8位,以分为单位)、换行符(2位,即’\r\n’) 如输出文件的第一行:1395519149000001240 表示手机号码为13955191490的用户费用是12.40元。
实验内容 请设计计算复杂度尽可能低的算法。**提交程序运行时间最短的5名同学可获得实验加分。 ** (注:提交的源代码将由助教编译运行,源代码必须能够正确编译链接,运行结果必须正确。运行时间将由助教在同一台计算机上多次运行程序取平均值得到。) 实验报告应详细说明所设计的算法是如何工作的,并附完整源代码。
实验想法 第一次看到这题的人都会想用树写吧??所以我花了几个小时看了看AVL 和红黑树 ,也查阅了AVL和红黑树各自的优缺点,但最后我还是不知道对于这个问题来说哪种树更合适。当我打算以后慢慢的去把这两种方案都实现一遍时,我去对老师给的数据进行了研究。竟然所有的电话号码都是139开头的???发现这一点,就越来越想用散列表 来实现。少了139这个无效数据后,每个人只有一个唯一对应的8位的电话号码。我们只需要构造长为100,000,000存放int型的数组即可啦!
我尝试去编写这个程序,可是发现计算机不能分配这么大的内存(其实也不大),而在编程序的过程中,我继续对数据的特征进行挖掘,紧接着我发现139的后两位要么是 05,要么是55,要么是56;此时,我觉得哈希算法实现就在眼前,也许老师给的数据就是指引我们运用哈希算法??
再去掉两位,我们只剩下6位值,和三个不同特征值,这时候,前方又有两个选择,是保留这个哈希冲突(每个链表最长也就3个结点),还是想想别的方法呢?我选择了哈希冲突,不过在一堆报错后放弃了(好像是内存溢出不是我写错了),只好继续想别的方法啦!
继续对数据特征进行挖掘,又发现了一个东西!139XX再后两位总是那么几个值:07-19,51-60,共23个值,一个两位数你就只有23个取值可能,这不是浪费吗?而23×3又刚好小于100,此时我们可以用一个六位数对应到每一个人的电话!六位数的后四位就是电话的后四位,六位数的前两位(1-23表示的是05XX;24-46表示的是55XX;47-69表示的是56XX)这样就成功实现没有哈希冲突的散列函数 啦!
我们研究一下时间复杂度,n个数据均需要插入或者查找,不论是AVL还是红黑树复杂度都是O(nlogn),而哈希查找的时间复杂度为O(n),在时间复杂度上哈希查找有绝对优势。而空间复杂度,我们只需建立一个长度为700,000的unsigned short int型数组。而不同的用户有100,000个,用树写也需要100,000个结点,而每个结点要存好多乱七八糟的数据(电话(要对其进行大小比较),话费,指针,红黑树的颜色或者AVL的BF值等等),相比之下对于内存的占用不相上下!总而言之,恭喜你发现哈希算法是对这个问题最好的实现!
下面给出源代码,100行左右的代码,一种简洁的实现!
有的系统用fgetc()读取换行符\r\n需要两步,有的系统用fgetc()读取换行符\r\n只需要一步,对于如果这类报错,可以直接对代码76行进行注释或者删掉注释。
不同的电脑,不同的系统,不同的IDE,以及大家存放的文件名及路径会有些不同,对于打开不了文件,可以直接在代码的第15行和第80行修改成正确的文件名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 #include <cstdio> #include <cstdlib> #define maxnumber 700000 int main (void ) { int tongji=0 ,calltime,huafei,telnumber,telnumber0,featurenumber1,featurenumber2,key; unsigned short int hashtable[maxnumber]={0 }; bool huchu; char feature1[3 ]; char feature2[3 ]; char tel[5 ]; char tmptime[5 ]; FILE *f; printf ("begin!" ); f=fopen("records.txt" ,"rt" ); if (!f) printf ("打开失败!\n" ); fseek(f,0 ,SEEK_SET); while (tongji<1000000 ){ fgetc(f); fgetc(f); fgetc(f); for (int i=0 ;i<2 ;i++) feature1[i]=fgetc(f); feature1[2 ]='\0' ; featurenumber1=atoi(feature1); if (featurenumber1==5 ) key=0 ; else if (featurenumber1==55 ) key=1 ; else if (featurenumber1==56 ) key=2 ; else { printf ("数据集错误!!%d" ,featurenumber1); exit (1 ); } for (int i=0 ;i<2 ;i++) feature2[i]=fgetc(f); feature2[2 ]='\0' ; featurenumber2=atoi(feature2); if (featurenumber2>=7 &&featurenumber2<=19 ) featurenumber2 -=6 ; else if (featurenumber2>=51 &&featurenumber2<=60 ) featurenumber2 -=37 ; else { printf ("数据集错误!!!%d" ,featurenumber2); exit (1 ); } for (int i=0 ;i<4 ;i++) tel[i]=fgetc(f); tel[4 ]='\0' ; telnumber0=atoi(tel); telnumber=(key*23 +featurenumber2)*10000 +telnumber0; char b; fgetc(f); b=fgetc(f); if (b=='0' ) huchu=true ; else if (b=='1' ) huchu = false ; for (int i=0 ;i<4 ;i++) tmptime[i]=fgetc(f); tmptime[4 ]='\0' ; calltime=((atoi(tmptime)-1 )/60 +1 ); if (huchu==true ) huafei=calltime*40 ; else huafei=calltime*20 ; hashtable[telnumber]+=huafei; fgetc(f); fgetc(f); fgetc(f); fgetc(f); fgetc(f); tongji++; } printf ("end!" ); FILE *fout=fopen("out.txt" ,"wt" ); for (int j=0 ;j<maxnumber;j++){ if (hashtable[j]!=0 ) { int n=j/10000 ; int a1,a2; int a3=j-10000 *n; if (n>=1 &&n<=23 ){ a1=5 ; if (n<=13 ) a2=n+6 ; else a2=n+37 ; } else if (n>=24 &&n<=46 ){ a1=55 ; if (n<=36 ) a2=n-17 ; else a2=n+14 ; } else if (n>=47 &&n<=69 ){ a1=56 ; if (n<=59 ) a2=n-40 ; else a2=n-9 ; } fprintf (fout, "139%02d%02d%04d%08d\n" ,a1,a2,a3, hashtable[j]); } } fclose(f); fclose(fout); printf ("OK!\n" ); }
不止要有手,还要有明亮的双眼