一、题目
输入:一个最多包含n个正整数的文件,每个数都小于n,其中n = 10^7。如果在输入文件中有任何整数重复出现就是致命错误。没有其他数据与该整数相关联。
输出:按升序排列的输入整数的列表。
约束:最多有(大约)1MB的内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化了。
题目分析:(书中也强调了“正确的问题”)
这里明确了输入,输出和约束条件三个方面的内容。输入实际上告诉我们输入数据的特点,而约束条件告诉我们算法设计时对空间复杂度和时间复杂度均有要求,特别是内存空间的限制,可以估计各种算法所占用的内存并判断是否满足要求。
二、数据生成
在进行规划算法之前,我们先设计一个程序,这个程序通过输入最大的一个正整数(含)M和正整数个数N,输出一个txt文件。txt文件中每行存放一个正整数,共N行,每个正整数均小于等于M,且两两互不相同。
版本一 rand()函数
借助下面这个程序,我们可以生成题目所要求的数据。这个程序可以产生不同数据规模下的数据,后续进行算法运行时间性能分析时可以把它们作为测试数据使用。产生的数据可以不连续,也可以连续(通过设置输入的最大值和数据量相同),可以不重复,也可以重复(通过设置输入标志位为0)。
实际测试时发现,当生成10^7个不重复的数据量时相当耗时,不够实用,但若生成可重复的数据此程序是相当快的。
下面为程序实现代码:算法复杂度O(n^3),n为生成的随机数数量。:
/** * @file generate_random_data.c * @brief generate random data:generate different numbers by line to * random_data.txt. * your should input the max value and generate counts. * @author chenxilinsidney * @version 1.0 * @date 2014-12-29 */ #include
#include #include #include "memory.h" // #define NDEBUG #include // #define NDBG_PRINT #include "debug_print.h" typedef unsigned int TYPE; int main(void) { TYPE max_value = 0; TYPE random_number = 0; TYPE flag_can_repeat = 0; TYPE index, k; /// get max value /// to use "%" operater with rand(), max value should less than RAND_MAX printf("MAX VALUE of the random number: %u\n", (unsigned)RAND_MAX - 1); printf("Please input the max value(excluded) of the random number: "); if(scanf("%u", &max_value) != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get the right max value(excluded).\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } else if(max_value > (RAND_MAX - 1)) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("value exceeds the limit max value.\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// get count printf("Please input the count of the random number: "); if(scanf("%u", &random_number) != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get the right count.\n"); DEBUG_PRINT_VALUE("%u", random_number); fflush(stdout); assert(0); exit(EXIT_FAILURE); } else if(random_number > max_value) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("count exceeds the max value.\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } printf("Please choose if data can be same in them(input 1(YES)/0(NO):"); if(scanf("%u", &flag_can_repeat) != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get the choice.\n"); DEBUG_PRINT_VALUE("%u", flag_can_repeat); fflush(stdout); assert(0); exit(EXIT_FAILURE); } else if(flag_can_repeat != 0 && flag_can_repeat != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get right choice(1 or 2).\n"); DEBUG_PRINT_VALUE("%u", flag_can_repeat); fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// generate random number TYPE* list = SMALLOC(random_number, TYPE); srand(time(NULL)); printf("start generating random numbers...........\n"); if(flag_can_repeat) { for(index = 0; index < random_number; index++) { TYPE flag = 1; while(flag) { list[index] = rand() % max_value; flag = 0; for(k = 0; k < index; k++) { if(list[k] == list[index]) { flag = 1; break; } } } } } else { for(index = 0; index < random_number; index++) { list[index] = rand() % max_value; } } /// write data to file FILE* fp = fopen("random_data.txt", "w"); if(fp == NULL) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } for(index = 0; index < random_number; index++) { fprintf(fp, "%u\n", list[index]); } if(fclose(fp) != 0) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } printf("random numbers saved to random_data.txt!\n"); /// free memory SFREE(&list); /// return return EXIT_SUCCESS; }
辅助程序debug_print.h,用于调试输出信息:
/** * @file debug.h * @brief debug with STDIO, your may use the follow three macro to debug width * a programme by IO. to remove the debug define NDBG_PRINT before including * the header.(this look likes
) * @author chenxilinsidney * @version 1.0 * @date 2014-12-17 */ #include #ifndef NDBG_PRINT #define DEBUG_PRINT_STATE printf("File: %s Line: %d\n", __FILE__, __LINE__) #define DEBUG_PRINT_VALUE(FORMAT, VALUE) printf("value " #VALUE \ " = " FORMAT "\n", (VALUE)) #define DEBUG_PRINT_STRING(STRING) printf(STRING) #else #define DEBUG_PRINT_STATE ((void)(0)) #define DEBUG_PRINT_VALUE(FORMAT, VALUE) ((void)(0)) #define DEBUG_PRINT_STRING(STRING) ((void)(0)) #endif
辅助程序memory.h,memory.c,用于安全开辟和释放内存:
/** * @copyright Copyright (C),1988-1999, Cita109 Tech. Co., Ltd. * @file memory.h * @brief memory allocate and free with checking. * @author author time version desc * @author Chen Xi Lin 14/12/17 1.2 build the module * @version 1.2 * @date 2014-06-08 */ #ifndef __MEMORY_H__ #define __MEMORY_H__ #include
#include #include #define SMALLOC(num, type) ((type *)safe_malloc((num)*sizeof(type))) #define SCALLOC(num, type) ((type *)safe_calloc((num),sizeof(type))) #define SREALLOC(ptr, num, type) ((type *)safe_realloc((ptr),(num)*sizeof(type))) #define SFREE(pointer) (safe_free((void**)(pointer))) void* safe_malloc(size_t size); void* safe_calloc(size_t num_elements, size_t element_size); void* safe_realloc(void* ptr, size_t new_size); void safe_free(void** pointer); #endif // __MEMORY_H__
/** * @copyright Copyright (C),1988-1999, Cita109 Tech. Co., Ltd. * @file memory.c * @brief memory allocate and free with checking. * @author author time version desc * @author Chen Xi Lin 14/12/17 1.2 build the module * @version 1.2 * @date 2014-06-08 */ #include "memory.h" /** * @brief malloc with checking. * * @param[in] size bytes of memory * * @return pointer to memory */ void* safe_malloc(size_t size) { void* new_mem; new_mem = malloc(size); if (NULL == new_mem) { printf("Out of memory!\n"); fflush(stdout); assert(0); exit(EXIT_FAILURE); } return new_mem; } /** * @brief malloc with checking. * * @param[in] num_elements element nums * @param[in] element_size size of element * * @return pointer to memory */ void* safe_calloc(size_t num_elements, size_t element_size) { void* new_mem; new_mem = calloc(num_elements, element_size); if (NULL == new_mem) { printf("Out of memory!\n"); fflush(stdout); assert(0); exit(EXIT_FAILURE); } return new_mem; } /** * @brief realloc with checking. * * @param[in] ptr pointer to old memory * @param[in] new_size new memory size * * @return pointer to memory */ void* safe_realloc(void* ptr, size_t new_size) { void* new_mem; new_mem = realloc(ptr, new_size); if (NULL == new_mem) { printf("Out of memory!\n"); fflush(stdout); assert(0); exit(EXIT_FAILURE); } return new_mem; } /** * @brief free with assignment to NULL. * * @param[in] pointer pointer to memory */ void safe_free(void** pointer) { free(*pointer); *pointer = NULL; }
版本二 洗牌法
生成随机数据的另一种方法是洗牌法(随机两两交换数据),但是洗牌时所用的数据是连续的,并且数据的随机性与洗牌的次数相关。
但是生成的不重复的数据时算法效率明显高于上面的方法。算法复杂度O(n),n为洗牌次数。
仔细思考,若要用洗牌法的同时也可以使得数据不连续,可以通过产生更多数据来使用洗牌法,但只保留部分数据来达到同样的效果,本程序实现了这样的功能(洗牌的数据量由输入的最大值来决定,洗牌的次数和输出的数据由输入的要求数据数决定)。
下面为洗牌法程序实现代码,并采用重定向语句进行输入输入输出:
/** * @file generate_random_data.c * @brief generate random data:generate different numbers by line to * random_data.txt. * your should input the max value(excluded) and generate counts. * @author chenxilinsidney * @version 1.0 * @date 2014-12-29 */ #include
#include #include #include "memory.h" // #define NDEBUG #include // #define NDBG_PRINT #include "debug_print.h" typedef unsigned int TYPE; int main(void) { TYPE max_value = 0; TYPE random_number = 0; TYPE index; /// get max value /// to use "%" operater with rand(), max value should less than RAND_MAX if(scanf("%u", &max_value) != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get the right max value(excluded).\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } else if(max_value > (RAND_MAX - 1)) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("value exceeds the limit max value.\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// get count if(scanf("%u", &random_number) != 1) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("can not get the right count.\n"); DEBUG_PRINT_VALUE("%u", random_number); fflush(stdout); assert(0); exit(EXIT_FAILURE); } else if(random_number > max_value) { DEBUG_PRINT_STATE; DEBUG_PRINT_STRING("count exceeds the max value.\n"); DEBUG_PRINT_VALUE("%u", max_value); fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// initialize number TYPE* list = SMALLOC(max_value, TYPE); for(index = 0; index < max_value; index++) { list[index] = index; } /// generate random number TYPE index_from, index_to; srand(time(NULL)); for(index = 0; index < max_value; index++) { index_from = rand() % max_value; index_to = rand() % max_value; while(index_from == index_to) { index_to = rand() % max_value; } TYPE temp = list[index_to]; list[index_to] = list[index_from]; list[index_from] = temp; } /// write data to stdout for(index = 0; index < random_number; index++) { printf("%u\n", list[index]); } /// free memory SFREE(&list); /// return return EXIT_SUCCESS; } 小结:
关于生成随机数据的算法,上面两个版本主要利用的是库函数和洗牌法。关于随机数据的生成,如果有时间的话,可以进一步谷歌查阅更多资料。
三、位图数据结构及算法分析
可用一个10位长的字符串来表示一个所有元素都小于10的简单的非负整数集合,例如,可以用如下字符串表示集合{1,2,4,5,8}:
0 1 1 1 0 1 0 0 1 0 0位图数据结构 该数据结构描述了一个有限定义域的稠密集合,其中的每一个元素最多出现一次并且没有其他任何数据与该元素相关联。即使这些条件没有完全满足(例如,存在重复元素或额外的 数据),也可以用有限定义域内的键作为一个表项更复杂的表格的索引。(摘自书中)
分析:使用这种数据结构需要满足的一大条件是数据为“有限定义域”,这样我们才能够建立有限的空间作为位图数据结构的存储区域。题目中给的数据就满足这个条件,他们的定义域范围是0-10^7。
若含有其它数据,可以先把主要的数据作为关键字,再开辟多余空间存放其它数据。书中习题6提出了整数重复出现的情况,针对这种情况我们可以开辟新的内存(与对应关键字连续存放或其他位置存放都可以,看实际运用需求)用于存放重复次数,内存的大小只要满足能够存放最大重复数据这一条件即可。这里也解决了数据重复的问题。
若数据不时完全“有限定义域”的,我们实际上可以先把里面满足“有限定义域”的数据用位图数据结构存储,然后对于其他数据,我们以集合或再分类的形式插入到位图数据结构中,比如题目中把超过10^7的数据(假设存在)归为一类插入到位图数据结构后面,并开辟一个空间存放这些数据(要求这些数据是较少的)。
这里利用位图数据结构解决了排序的问题,实际上先解决的是数据的存放问题(有限内存)。利用位图数据结构,我们对“有限定义域”的数据,都可以进行存放。对这些数据存在标志位,可以不按标志位分类进行存储,而采用位图数据结构,大大节约空间成本。
应用总结:
1.对整数数据快速排序,从而再实现快速查找。
2.判断整数数据是否存在。
3.判断整数数据是否重复,找出不重复数据。
位图数据结构伪代码:
下面为位图数据结构实现代码,按照题目要求对数据进行处理,这里使用的是4字节数据类型分段进行存储:/* phase 1: initialize set to empty */ for i = [0, N) bit[i] = 0 /* phase 2: insert present elements into the set */ for each i in the input file bit[i] = 1 /* phase 3: write the sorted output */ for i = [0, N) if bit[i] = 1 write i on the output file
注意以下几点:
1.与2相关的除法运算改为移位运算:n / 32 -> n >> 5
2.于2相关的求余运算改为与运算:n % 32 -> n & 0x1F (这点参考别的代码,以前没有注意这个方面,值得学习)
3.这里使用的二进制置位(赋1)运算方法,为:value |= mask,尽管不使用到二进制复位运算(赋0),但是值得一提:
value &= ~mask,而检查二进制位1或0的方法:return value & mask.
4.本文采用的文件流是标准输入输出,在linux系统下,可以采用重定向语句来实现简化程序:./a.out < input > output
5.C++语言中可以使用函数库
/** * @file sort_by_bitmap_structure.c * @brief sort a list of random data quickly by bitmap structure. * @author chenxilinsidney * @version 1.0 * @date 2014-12-30 */ #include
#include #include // #define NDEBUG #include #include "memory.h" #include "type.h" // #define NDBG_PRINT #include "debug_print.h" typedef uint32 TYPE; /// max random data value #define MAX_VALUE 10000000 /// bitmap structure initialize to empty #define BITMAP_BITS 32 #define BITMAP_SHIFT 5 #define BITMAP_MASK 0x1F #define BITMAP_LENGTH ((unsigned)((MAX_VALUE)+(BITMAP_BITS)-1)>>(BITMAP_SHIFT)) TYPE bitmap_structure[BITMAP_LENGTH] = {0}; int main(void) { /// insert present elements into the set /// read data from file FILE* fr = fopen("random_data.txt", "r"); if(fr == NULL) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// set bitmap structure TYPE value; TYPE count = 0; while(fscanf(fr, "%u\n", &value) == 1) { bitmap_structure[value >> BITMAP_SHIFT] |= 1 << (value & BITMAP_MASK); count++; } printf("get total number: %d.\n", count); /// close read data file if(fclose(fr) != 0) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// write sorted output /// set write data file FILE* fw = fopen("sorted_data.txt","w"); if(fw == NULL) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// write sorted data to file TYPE i; for(i = 0; i < count; i++) { if(bitmap_structure[i >> BITMAP_SHIFT] & (1 << (i & BITMAP_MASK))) { fprintf(fw, "%u\n", i); } } /// close write data file if(fclose(fw) != 0) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } return EXIT_SUCCESS; } 四、多趟算法
多趟算法严格解决了题目中1MB内存的上限,因为若使用位图数据结构,10^7个数据需要的内存为10^7bits = 1250000bytes ~= 1.22MB > 1MB。多趟算法在本题中指的是多趟遍历文件采取同一算法进行处理,再整合多趟处理下来的各个结果。这里我一开始想的是,先读取数据(文件)前半部分进行排序,再去读数据(文件)后半部分进行排序,产生的问题是两个各自排完序后再合成时是无法全部有序排列的,无法解决题目问题。仔细想了想,这个想法确实是错误的。
正确的想法是:在我们执行多趟算法时应该先对输入的数据进行筛选才对。这里筛选方法是第一趟先选出小于(5 * 10^6)的数据进行排序写入文件,第二趟再选出大于等于(5 * 10^6)的数据进行排序写入文件,这样就解决了严格1MB内存上限问题同时也解决题目的排序问题。
利用两趟算法并使用位图数据结构排序解决题目的算法实现如下:
/** * @file sort_by_bitmap_structure.c * @brief sort a list of random data quickly by bitmap structure in two steps. * @author chenxilinsidney * @version 1.0 * @date 2014-12-30 */ #include
#include #include // #define NDEBUG #include #include "memory.h" #include "type.h" // #define NDBG_PRINT #include "debug_print.h" typedef uint32 TYPE; /// max random data value #define MAX_VALUE 5000000 /// bitmap structure initialize to empty #define BITMAP_BITS 32 #define BITMAP_SHIFT 5 #define BITMAP_MASK 0x1F #define BITMAP_LENGTH ((unsigned)((MAX_VALUE)+(BITMAP_BITS)-1)>>(BITMAP_SHIFT)) TYPE bitmap_structure[BITMAP_LENGTH] = {0}; int main(void) { /// insert present elements into the set /// read data from file in first step FILE* fr = fopen("random_data.txt", "r"); if(fr == NULL) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// set bitmap structure in first step. TYPE value; TYPE count = 0; while(fscanf(fr, "%u\n", &value) == 1) { if(value < MAX_VALUE) { bitmap_structure[value >> BITMAP_SHIFT] |= 1 << (value & BITMAP_MASK); count++; } } printf("get total number in first step: %d.\n", count); /// write sorted output /// set write data file FILE* fw = fopen("sorted_data.txt","w"); if(fw == NULL) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// write sorted data to file in first step TYPE i; for(i = 0; i < count; i++) { if(bitmap_structure[i >> BITMAP_SHIFT] & (1 << (i & BITMAP_MASK))) { fprintf(fw, "%u\n", i); } } /// read data from file in second step fr = freopen("random_data.txt", "r", fr); if(fr == NULL) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// set bitmap structure in second step. count = 0; while(fscanf(fr, "%u\n", &value) == 1) { if(value >= MAX_VALUE) { value -= MAX_VALUE; bitmap_structure[value >> BITMAP_SHIFT] |= 1 << (value & BITMAP_MASK); count++; } } printf("get total number in second step: %d.\n", count); /// write sorted data to file in second step for(i = 0; i < count; i++) { if(bitmap_structure[i >> BITMAP_SHIFT] & (1 << (i & BITMAP_MASK))) { fprintf(fw, "%u\n", i + MAX_VALUE); } } /// close read data file if(fclose(fr) != 0) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// close write data file if(fclose(fw) != 0) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
五、拓展(部分来自课后习题)1.如果不缺内存,利用库的语言来实现排序算法:
在C语言库中可以利用
中的qsort函数进行排序(学习自《C和指针》一书),另外书中也介绍了对排好序的数组进行查找的函数bsearch函数(二分法),这里没有用到,未来实践时可以留意并使用。 使用库函数进行排序代码:
/** * @file sort_by_bitmap_structure.c * @brief sort a list of random data by standard library. * @author chenxilinsidney * @version 1.0 * @date 2014-12-30 */ #include
#include #include // #define NDEBUG #include #include "memory.h" #include "type.h" // #define NDBG_PRINT #include "debug_print.h" typedef uint32 TYPE; #define MAX_VALUE 10000000 uint32 list[MAX_VALUE] = {0}; /** * @brief this function is used to compare a and b used by qsort function. * * @param[in] a first data * @param[in] b second data * * @return >1 if a > b, =0 if a == b, <0 if a < b */ int list_compare(void const* a, void const* b) { TYPE first = *(TYPE*)a; TYPE second = *(TYPE*)b; if(first > second) return 1; else if(first == second) return 0; else return -1; } int main(void) { /// read data from file FILE* fr = fopen("random_data.txt", "r"); if(fr == NULL) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// read data to list TYPE count = 0; while(fscanf(fr, "%u\n", list + count) == 1) { ++count; } /// close read data file if(fclose(fr) != 0) { perror("random_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// sort data by standard library qsort(list, count, sizeof(TYPE), list_compare); /// set write data file FILE* fw = fopen("sorted_data.txt","w"); if(fw == NULL) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } /// write sorted data to file TYPE i; for(i = 0; i < count; i++) { fprintf(fw, "%u\n", list[i]); } /// close write data file if(fclose(fw) != 0) { perror("sorted_data.txt"); DEBUG_PRINT_STATE; fflush(stdout); assert(0); exit(EXIT_FAILURE); } return EXIT_SUCCESS; }
备注:
1.转载请注明出处:http://blog.csdn.net/chensilly8888/
2.全文源码均开源(在UBUNTU + GCC4.8.2下编译并测试通过),可下载或查看:https://github.com/chenxilinsidney/funnycprogram/tree/master/programming_pearls/chapter_1
3.有写错或写漏之处请指正,谢谢!