/* 一个关于算法的面试题,来源于网络 */ /* 请教大家一个算法题[问题点数:10分] chu009 (chu009) 我面试的时候曾经遇到的一道算法题,写在这大家一起讨论下,争取找出个好的解决方案。 题目是这样的:有两个文件,A文件里有大量的电话号码,上亿条,里面有少量的重复号码,要求把A文件里面的重复号码去掉然后存入B文件。 我的解决方法:建立一个二叉排序树存储所有A文件中不重复的电话号码信息。我从A文件每次读取一条电话号码,然后插入到二叉树结构中,如果这条记录已经存在则不进行插入。最后二叉排序树中包含了所有A文件中不重复的电话号码信息。改进的方式是在插入过程中将二叉排序树调整为二叉平衡树。 将二叉树按某种方式进行遍历,将电话号码信息依次写入B文件。如果按中序遍历,则B文件中的电话号码是有序的。 这里我建立二叉树的时间复杂度是O(nlgn),写入B文件O(n),但是二叉树节点需要存储电话号码信息就要占用内存,上亿节点占用多大的内存啊,这是对方给我提出的challenge,我没给出更好的方法。 我的出发点是降低时间复杂度,但是没有解决内存占用问题。 但是不把A文件中的节点存入内存,假如这样做:将A文件一次取一条记录,然后在B文件从头至尾查找是否有重复的,如果没有重复的加入到B文件末尾,然后A文件再取下一条,重复此过程。 这样虽然节省了内存,但是时间复杂度为O(N*N)(上亿条记录,这个时间复杂度是很恐怖的),而且每插入一条就要把B文件读取一遍也是非常耗时的。 我没有想出更好的方法,大家帮忙看看吧。 */ /*============================================================================== 文 件 名 : Win32CApp.c 功 能 : 从A文件中读入手机号码,去掉重复出现的手机号,内容输出到文件B中。 日 期 : 2011-10-16 作 者 : jernymy 版 权 : <Copyright(C) ... jernymy.> 版 本 : V1.0 ------------------------------------------------------------------------------- 说 明 : 使用位图的模式,参考编程珠玑(2)中介绍的一种算法。 以下是中国的手机号码分段情况 新联通 (中国联通+中国网通)手机号码开头数字 130、131、132、145、 155、156、185、186 新移动 (中国移动+中国铁通)手机号码开头数字 134、135、136、137、 138、139、147、150、151、152、157、158、159、182、183、187、188 新电信 (中国电信+中国卫通)手机号码开头数字 133、153、189、180 号码段情况可以看到手机号码区间在130 0000 0000 - 189 9999 9999 范围 = 600 0000 0000, 无法用以一个u32的整形表示到。 而160,170均不可用,所以将18用16来替换,于是180->160, 189->169 则范围40 0000 0000,已经在u32最大范围之内 (0xFFFF FFFF) 42 9496 7295,所以我们只需要定义足够u32数组即可。 目前定一个区间为40 0000 0000,共需要(40 0000 0000/32)= 1 2500 0000个u32数组表示, 公占用内存空间(40 0000 0000/32)*4=5 0000 0000 Bytes ≈476.837158203125M Bytes,方法可行。 ==============================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <malloc.h> // 42 9496 7295 // 2147483648 typedef unsigned int u32; /* { jernymy - the status error no */ typedef u32 STATUS; #define ERR_OK 0 #define ERR_PRMT_NULL 1 #define ERR_PHONE_ZERO 2 #define ERR_PHONE_NOTINRAGNE 3 #define ERR_PHONE_INVAILD 4 #define ERR_PHONE_LEN 5 #define ERR_MALLOC 10 #define ERR_CREATE_FILE 11 #define ERR_OPEN_FILE 12 /* } jernymy - the status error no */ // 1亿的数据 #define ONE_YI (u32)(10000*10000) // for test #define FILE_PHONE_NUM (u32)(10000*100) #define PHONE_EVERY (u32)(10*ONE_YI) #define VAR_LEN (u32)(32) // phone like 130 xxxx xxxx #define MAX_PHONE_NUM_LEN 11 // process like 30 xxxx xxxx, remove high bit #define MAX_PHONE_USE_LEN 10 // current is 40 0000 0000 #define TOTAL_PHONE_NUM (u32)(40*ONE_YI) // current is 30 0000 0000 #define SPECIAL_PHONE_NUM (u32)(30*ONE_YI) // current is 40 0000 0000/32 = 1 2500 0000 #define MAX_PHONE_BIT (u32)(TOTAL_PHONE_NUM/VAR_LEN) // source file, will process #define PHONE_FILE_NAME "phone.txt" // the dest file, not have repeat phone #define PHONE_NO_RPT_FILE_NAME "phone_norpt.txt" // set array one bit is 1 #define SET_ARY_BIT(ary,id) ( ary[id/VAR_LEN] |= (1 << (id%VAR_LEN)) ) // check array one bit is 1 ? #define IS_SET_BIT(ary,id) ( ary[id/VAR_LEN] & (1 << (id%VAR_LEN)) ) // current phone rang in used static short g_sPhoneAry[] = { 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 145, 147, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 180, 182, 183, 185, 186, 187, 188, 189, }; #define PHONE_TITLE_NUM ( sizeof(g_sPhoneAry) / sizeof(short) ) // u32 array pointer, need malloc static u32 *g_padwPhoneAry = NULL; #define PHONE_REPEAT_FILE_NAME "phone_rpt.txt" static FILE *g_fpRepeat = NULL; /*--------------------------------- { function ---------------------------------*/ /*============================================================================== 函 数 名 : TstInputFile 功 能 : 建立一个手机号码文件,100万个,其中包括一部分重复的号码,仅测试使用 参 数 : 无 返 回 值 : 无 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static void TstInputFile(void) { u32 dwIdx; u32 dwVar; short wAryId; FILE *fpDst; char achPhone[12] = {0}; fpDst = fopen(PHONE_FILE_NAME, "wt"); if (NULL == fpDst) { printf("open file:%s fail\n", PHONE_FILE_NAME); return; } srand( (unsigned)time(NULL) ); printf("PHONE_TITLE_NUM:%d\n", PHONE_TITLE_NUM); // create FILE_PHONE_NUM phone number for (dwIdx = 0; dwIdx < FILE_PHONE_NUM; dwIdx++) { wAryId = rand()%PHONE_TITLE_NUM; dwVar = rand()%FILE_PHONE_NUM; fprintf(fpDst, "%3d%08d\n", g_sPhoneAry[wAryId], dwVar); // create 100 repeat phone number // and total phone may have other repeat if (0 == (dwIdx % (FILE_PHONE_NUM/100)) ) { fprintf(fpDst, "%3d%08d\n", g_sPhoneAry[wAryId], dwVar); } } fclose(fpDst); } /*============================================================================== 函 数 名 : TstCrtRepeatFile 功 能 : 建立一个文件句柄,该文件记录所有重复号码 参 数 : 无 返 回 值 : 无 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static void TstCrtRepeatFile(void) { g_fpRepeat = fopen(PHONE_REPEAT_FILE_NAME, "wt"); if (NULL == g_fpRepeat) { printf("Create file:%s fail\n", PHONE_REPEAT_FILE_NAME); } } /*============================================================================== 函 数 名 : TstAtoll 功 能 : 建立一个文件句柄,该文件记录所有重复号码 参 数 : [in] const char *pchStr - 手机号码字符串 [out] u32 *pdwNumber - 要输出的手机号码整形,将最高位的1去 掉,同时范围在0-3之间。前3为变动 130-159-->100-129-->00-29 180-189-->130-139-->30-39 返 回 值 : STATUS (ERR_OK-success, other-error) 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static STATUS TstAtoll(const char *pchStr, u32 *pdwNumber) { u32 dwLen = 0; char achPhone[MAX_PHONE_NUM_LEN+1] = {0}; if (NULL == pchStr) { printf("parameter is NULL!\n"); return ERR_PRMT_NULL; } if (NULL == pchStr[0]) { printf("parameter[0] is 0, phone is 0!\n"); return ERR_PHONE_ZERO; } // check phone is valid if ( (pchStr[1] > '8') || (pchStr[1] < '3') ) { printf("parameter:%s not a phone!\n", pchStr); printf("must in range (130 0000 0000 - 189 9999 9999)!\n"); return ERR_PHONE_NOTINRAGNE; } // remove the high bit char, 130 xxxx xxxx - 30 xxxx xxxx strncpy(achPhone, pchStr+1, MAX_PHONE_USE_LEN); /* change 18x xxxx xxxx -> 16x xxxx xxxx */ if (achPhone[0] == '8') { achPhone[0] = '6'; } // range in u32 data, so change (13x-16x) (10x-13x) achPhone[0] -= 3; while (achPhone[dwLen] != '\n' && (dwLen < 10)) { if ( (achPhone[dwLen] < '0') || (achPhone[dwLen] > '9') ) { printf("parameter:%s is not a string number!\n", dwLen); return ERR_PHONE_INVAILD; } *pdwNumber = (*pdwNumber * 10) + (achPhone[dwLen]-'0'); dwLen++; } // check phone length is valid if (MAX_PHONE_USE_LEN != dwLen) { printf("parameter:%s length error, must is:%d\n", pchStr, MAX_PHONE_NUM_LEN); return ERR_PHONE_LEN; } return ERR_OK; } /*============================================================================== 函 数 名 : GetPhone 功 能 : 从A文件中读取手机号码,并使用位图模式保存到分配的内存中。 参 数 : 无 返 回 值 : STATUS (ERR_OK-success, other-error) 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static STATUS GetPhone(void) { u32 dwNum; char achPhone[16] = {0}; FILE *fpSrc; // 5 0000 0000 Bytes // 476.837158203125 M Bytes // if here can not new this succ, please use 4 array, every new 1 2500 0000 Bytes printf("TOTAL_PHONE_NUM:%u, VAR_LEN:%u\n", TOTAL_PHONE_NUM, VAR_LEN); printf("malloc %u u32 size memory start\n", MAX_PHONE_BIT); g_padwPhoneAry = (u32 *)malloc(MAX_PHONE_BIT*sizeof(u32)); // g_padwPhoneAry = new u32[MAX_PHONE_BIT]; if (NULL == g_padwPhoneAry) { printf("malloc %u u32 size memory fail\n", MAX_PHONE_BIT); return ERR_MALLOC; } memset(g_padwPhoneAry, 0, MAX_PHONE_BIT*sizeof(u32)); fpSrc = fopen(PHONE_FILE_NAME, "rt"); if (NULL == fpSrc) { printf("open file:%s fail\n", PHONE_FILE_NAME); return ERR_OPEN_FILE; } while (!feof(fpSrc)) { if (!fgets(achPhone, sizeof(achPhone), fpSrc)) { break; } // switch string phone to u32 data dwNum = 0; if (ERR_OK != TstAtoll(achPhone, &dwNum)) { continue; } // check the bit is set, no set the set if (!IS_SET_BIT(g_padwPhoneAry, dwNum)) { SET_ARY_BIT(g_padwPhoneAry, dwNum); } else { // save the data to repeat file if (NULL != g_fpRepeat) { // the string have \n fprintf(g_fpRepeat, "%s", achPhone); } // printf("get a repeat num:%s\n", achPhone); } } fclose(fpSrc); if (NULL != g_fpRepeat) { fclose(g_fpRepeat); g_fpRepeat = NULL; } return ERR_OK; } /*============================================================================== 函 数 名 : PutPhone 功 能 : 将内存中的数据存入到文件B中,此时已经没有重复的数据。 参 数 : 无 返 回 值 : STATUS (ERR_OK-success, other-error) 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static STATUS PutPhone(void) { u32 dwIdx; u32 dwHigh; FILE *fpDst; char achPhone[12] = {0}; fpDst = fopen(PHONE_NO_RPT_FILE_NAME, "wt"); if (NULL == fpDst) { printf("Create file:%s fail\n", PHONE_NO_RPT_FILE_NAME); return ERR_CREATE_FILE; } // the max range (40 0000 0000) for (dwIdx = 0; dwIdx < TOTAL_PHONE_NUM; dwIdx++) { if (IS_SET_BIT(g_padwPhoneAry, dwIdx)) { dwHigh = dwIdx / SPECIAL_PHONE_NUM; dwHigh += 30; if (dwHigh >= 60) { dwHigh += 20; } // 1xx xxxx xxxx to file B fprintf(fpDst, "1%2d%08d\n", dwHigh, dwIdx); } } fclose(fpDst); return ERR_OK; } /*============================================================================== 函 数 名 : PrintTime 功 能 : 打印输入的字符串和当前时间 参 数 : [in] const char *pchStr - 要打印的字符串 返 回 值 : 无 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ static void PrintTime(const char *pchStr) { time_t dwCurTime = time(NULL); printf("%s:%s", pchStr, ctime(&dwCurTime)); } /*============================================================================== 函 数 名 : main 功 能 : 文件的主函数 参 数 : 无 返 回 值 : 无 日 期 : 2011-10-16 作 者 : jernymy 版 本 : V1.0 ==============================================================================*/ int main(void) { // jernymy used create a phone file for test // TstInputFile(); // jernymy used create a phone repeat file // for save repeat phone, just test TstCrtRepeatFile(); // get phone data, from file string to memory u32 // and remove repeat data printf("get file string phone start...\n"); PrintTime("Start Time"); if (ERR_OK != GetPhone()) { printf("get file string phone fail\n"); return -1; } PrintTime("End Time"); printf("get file string phone finish\n"); printf("---------------------------------------------\n"); // put phone data, from memory u32 to file string printf("put memory phone to file string start...\n"); PrintTime("Start Time"); if (ERR_OK != PutPhone()) { printf("put memory phone to file string fail\n"); return -2; } PrintTime("End Time"); printf("put memory phone to file string finish\n"); return 0; } /*--------------------------------- } function ---------------------------------*/ /* jernymy 测试结果VC6.0上,使用100万的测试数据中有筛选了重复之后剩下的62万多的数据 耗时不到2分钟 硬件信息 CPU--intel Pentium Dual E2160 1.8G 内存-3G get file string phone start... Start Time:Sun Oct 16 19:06:50 2011 TOTAL_PHONE_NUM:4000000000, VAR_LEN:32 malloc 125000000 u32 size memory start End Time:Sun Oct 16 19:06:51 2011 get file string phone finish --------------------------------------------- put memory phone to file string start... Start Time:Sun Oct 16 19:06:51 2011 End Time:Sun Oct 16 19:08:13 2011 put memory phone to file string finish Press any key to continue **/