金山两道程序题(排列和组合)

编程 1
  给定一个字符串,输出给定字符串所有的组合的函数,例如:字符串 abc  所有组合 abc  acb  bac b    ca cab  cba六种组合
编程 2
  给定一个数组大小m和一个数组array
  m = 10;  array = {1,2,3,4,5,6,7,8,9,10}
  求从array中任意取得n(n<=m)个数,使得和为m,总共有多少种取法,例如:10 1,9 2,3,5
  方法原型 :int  getotalNum (int[] array, int m);
要求有输入输出,而不是仅仅做一个函数出来..

  1. *名称:    problem01.c                                         *
  2. *描述:    给定一个字符串,输出给定字符串所有的组合的函数 *
  3. *假设:    假设字符串中的字母不重复                            *
  4. *思路:    对输入字符数组下标全排列再按照排列顺序输出即可 *
  5. *环境:    Code::Blocks & Windows 7 & x86                      *
  6. *备注:    davelv于09年光棍节19点                                *
  7. *************************************************************/
  8. #include
  9. #include
  10. #include
  11. #define BUF_LEN 1024
  12. //排列输出函数
  13. void perm(char *buf, int start, int end);  
  14. int main(void)  
  15. {  
  16. char buf[BUF_LEN];  
  17.     printf("Please input string less than %d letters:", BUF_LEN);  
  18.     fgets(buf, BUF_LEN, stdin);  
  19. if (buf[strlen(buf)-1] == '/n')  
  20.         buf[strlen(buf)-1] = '/0';//去除控制台附加的/n
  21.     perm(buf, 0, strlen(buf));//排列输出
  22.     system("PAUSE");  
  23. return 0;  
  24. }  
  25. //buf 待字符串;start 排列开始位置;end 排列结束位置
  26. void perm(char *buf, int start, int end)  
  27. {  
  28. int i;  
  29. char temp;  
  30. if (start == end)  
  31.     {//当排列到最后一个字符时输出
  32.        puts(buf);  
  33.     }  
  34. else
  35.     {//否则继续排列
  36. for (i=start; i
  37.         {  
  38. //交换
  39.             temp = buf[start];  
  40.             buf[start] = buf[i];  
  41.             buf[i] = temp;  
  42. //递归排列
  43.             perm(buf, start+1, end);  
  44. //换回原位置
  45.             buf[i] = buf[start];  
  46.             buf[start] = temp;  
  47.         }  
  48.     }  
  49. }  

Code:

  1. /************************************************************
  2. *名称:    problem02.c                                         *
  3. *描述:    整形数组中取n个元素和等于元素个数m(m>=n)            *
  4. *假设:    不能重复取某个元素                                   *
  5. *思路:    从1到m 组和数组元素并求和判断是否等于m           *
  6. *改进:    见107行注释                                         *
  7. *环境:    Code::Blocks & Windows 7 & x86                      *
  8. *备注:    davelv于09年光棍节20点                                *
  9. *************************************************************/
  10. #include
  11. #include
  12. #include
  13. #define BUF_LEN 1024
  14. //比较函数,用于qsort
  15. int compare ( const void *a , const void *b );  
  16. //求有几种取法的函数
  17. int  getotalNum (int array[], int m);  
  18. //数字组合函数
  19. void combine(int array[], int result[], int m, int n);  
  20. int main(void)  
  21. {  
  22. int buf[BUF_LEN], m, i;  
  23.     printf("Please input NO. of integer(s) no more than %d:", BUF_LEN);  
  24.     scanf("%d",&m);  
  25.     printf("Please input integer(s):");  
  26. for(i=0; i
  27.     {  
  28.         scanf("%d",buf+i);  
  29.     }  
  30.     printf("Totle %d way(s)/n", getotalNum(buf,m));  
  31.     system("PAUSE");  
  32. return 0;  
  33. }  
  34. int compare ( const void *a , const void *b )  
  35. {  
  36. return *(int *)a - *(int *)b;  
  37. }  
  38. //返回值为取法的总数,如果是-1则表示函数失败
  39. int  getotalNum (int array[], int m)  
  40. {  
  41. int i, totle;  
  42. int *result;  
  43.     qsort(array, m, sizeof(array[0]), compare);//排序
  44. //排除大于m的数字
  45. for(i=0; i
  46.     {  
  47. if (array[i] > m)  
  48.         {  
  49.             m = i;  
  50. break;  
  51.         }  
  52.     }  
  53.     result = (int*)malloc(m * sizeof(int));//为combine函数分配缓存空间
  54. if (result == NULL)  
  55. return -1;  
  56. //循环从1组和到m
  57.     totle=0;  
  58. for(i=1; i<=m; i++)  
  59.     {  
  60.         combine(array, result, m, i);  
  61.         totle += *result;  
  62.     }  
  63.     free(result);  
  64. return totle;  
  65. }  
  66. //array:存储数字,result:存储本次结果,m:array长度即组合下标,n:组合上标
  67. void combine(int array[], int result[], int m, int n)  
  68. {  
  69. int i,t;//计数器,begin变量的中间变量
  70. static int totle=0,level=0,begin=0,num=0;//已经组合数字和,已递归深度,组合开始元素的下标,符合要求组合的个数
  71. for (i=begin; i<=m-n+level; i++)  
  72.     {  
  73. if ( totle+array[i] <= m)//判断当前组合数字和是否已经超出m限制
  74.         {  
  75.             totle += array[i];  
  76.             result[level] = array[i];//保存当前数字
  77. //判断当前层数是不是N-1
  78. if (level < n-1)  
  79.             {  
  80.                 level++;  
  81.                 t = begin;begin = i+1;  
  82.                 combine(array, result, m, n);        //下层递归
  83.                 begin= t;  
  84.                 level--;  
  85.             }  
  86. else
  87.             {  
  88. if(totle == m)//判断当前数字和是否符合要求
  89.                 {  
  90. int j;  
  91. for(j=0; j<=level; j++)//符合则输出数字
  92.                         printf("%d ",result[j]);  
  93.                     puts("");  
  94.                     num++;        //计数器加一
  95.                 }  
  96.             }  
  97.             totle -= array[i];  
  98.         }  
  99. else
  100. break;//超出限制则跳出
  101. /**************************************************
  102.             *若使用longjmp直接跳回getotalNum可获得更好的性能*
  103.             **************************************************/
  104.     }  
  105. if (level == 0)  
  106.     {  
  107.         result[0] = num;  
  108.         totle = num = 0;//将static变量归零
  109.     }  
  110. }  

原始代码优化的不是很到位,于是今天早上重写了下。

时间复杂度从O(sigma(1,n,A(i,n)))降低到O(A(n,n))也就是O(n!)。

主要方法是把combine函数改写,把外部调用combine的循环移入combine函数内部,这样减少了combine函数中某些结果重复计算的冗余,从而改进了时间复杂度。

新代码如下

Code:

  1. /************************************************************
  2. *名称:    problem02.c                                         *
  3. *描述:    整形数组中取n个元素和等于元素个数m(m>=n)        *
  4. *假设:    不能重复取某个元素                               *
  5. *思路:    从1到m 组和数组元素并求和判断是否等于m           *
  6. *环境:    Code::Blocks & Windows 7 & x86                  *
  7. *备注:    davelv于09-12-06                                 *
  8. *************************************************************/
  9. #include
  10. #include
  11. #include
  12. #define BUF_LEN 1024
  13. //比较函数,用于qsort
  14. int compare ( const void *a , const void *b );  
  15. //求有几种取法的函数
  16. int  getotalNum (int array[], int m);  
  17. //数字组合函数
  18. void combine(int array[], int result[], int m);  
  19. int main(void)  
  20. {  
  21. int buf[BUF_LEN], m, i;  
  22.     printf("Please input NO. of integer(s) no more than %d:", BUF_LEN);  
  23.     scanf("%d",&m);  
  24.     printf("Please input integer(s):");  
  25. for(i=0; i
  26.     {  
  27.         scanf("%d",buf+i);  
  28.     }  
  29.     printf("Totle %d way(s)/n", getotalNum(buf,m));  
  30. return 0;  
  31. }  
  32. int compare ( const void *a , const void *b )  
  33. {  
  34. return *(int *)a - *(int *)b;  
  35. }  
  36. //返回值为取法的总数,如果是-1则表示函数失败
  37. int  getotalNum (int array[], int m)  
  38. {  
  39. int i, totle;  
  40. int *result;  
  41.     qsort(array, m, sizeof(array[0]), compare);//排序
  42. //排除大于m的数字
  43. for(i=0; i
  44.     {  
  45. if (array[i] > m)  
  46.         {  
  47.             m = i;  
  48. break;  
  49.         }  
  50.     }  
  51.     result = (int*)malloc(m * sizeof(int));//为combine函数分配缓存空间
  52. if (result == NULL)  
  53. return -1;  
  54.     combine(array, result, m);  
  55.     totle=*result;  
  56.     free(result);  
  57. return totle;  
  58. }  
  59. //array:存储数字,result:存储结果数量,m:数字个数
  60. void combine(int array[], int result[], int m)  
  61. {  
  62. int i,t;//计数器,begin变量的中间变量
  63. static int totle=0,level=0,begin=0,num=0;//已经组合数字和,已递归深度,组合开始元素的下标,符合要求组合的个数
  64. for (i=begin; i<=m; i++)  
  65.     {  
  66.         totle += array[i];  
  67.         result[level] = array[i];//保存当前数字
  68. if ( totle < m)//判断当前组合数字和是否已经超出m限制
  69.         {  
  70. //判断当前层数是不是M-1
  71. if (level < m-1)  
  72.             {  
  73.                 level++;  
  74.                 t = begin;  
  75.                 begin = i+1;  
  76.                 combine(array, result, m);        //下层递归
  77.                 begin= t;  
  78.                 level--;  
  79.             }  
  80.         }  
  81. else if(totle > m)//超过m则跳出本次循环
  82.         {  
  83.             totle -= array[i];  
  84. break;  
  85.         }  
  86. else
  87.         {  
  88. int j;  
  89. for(j=0; j<=level; j++)//符合则输出数字
  90.                 printf("%d ",result[j]);  
  91.             puts("");  
  92.             num++;        //计数器加一
  93.         }  
  94.         totle -= array[i];  
  95.     }  
  96. if (level == 0)  
  97.     {  
  98.         result[0] = num;  
  99.     }  
  100. }  

利用自己写的测试程序测试新旧两个程序getotleNum()所耗时间结果如下:

Code:

  1. E:/cbwork/testc/bin/Release>driver.exe      
  2. generate_test_data_file   ok!      
  3. run tested programs      
  4. data            old(ms)         new(ms)      
  5. 50              75              2      
  6. 60              289             5      
  7. 70              998             15      
  8. 80              3278            44      
  9. 90              9409            107      
  10. 100             26747           261      
  11. 110             69330           601     

gcc (GCC) 3.4.5 (mingw-vista special),O2开关优化,Win7, Intel 奔腾双核 1.8GHz,1G内存。

排除当时间较少时的噪音干扰,可以得到如下结论。

1、旧程序在数据量每增加10的时候时间增长为原来的约3倍

2、新程序在数据量每增加10的时候时间增长为原来的约2+倍

3、新程序比旧程序在同等数据量的情况下快约100倍。

同时给出测试驱动程序:

Code:

  1. #include
  2. #include
  3. #include
  4. #include
  5. #define PROGRAM_NAME_TYPE "xxx.exe"
  6. #define IN_PIPE "<"
  7. void generate_test_data_file(int from, int to, int step);  
  8. void run_programs(int from, int to, int step);  
  9. int main()  
  10. {  
  11. int from=50,to=110,step=10;  
  12.     printf("generate_test_data_file   ");  
  13.     generate_test_data_file(from,to,step);  
  14.     puts("ok!");  
  15.     puts("run tested programs");  
  16.     puts("data/t/told(ms)/t/tnew(ms)");  
  17.     run_programs(from,to,step);  
  18. return 0;  
  19. }  
  20. void run_programs(int from, int to, int step)  
  21. {  
  22. char cmd[FILENAME_MAX];  
  23. int len;  
  24. for(;from<=to;from+=step)  
  25.     {  
  26.         len=strlen(PROGRAM_NAME_TYPE)+strlen(IN_PIPE);  
  27.         itoa(from,cmd+len,10);  
  28.         printf(cmd+len);  
  29.         printf("/t/t");  
  30.         memcpy(cmd,"old.exe<",strlen("old.exe<"));  
  31.         system(cmd);  
  32.         printf("/t/t");  
  33.         memcpy(cmd,"new.exe<",strlen("new.exe<"));  
  34.         system(cmd);  
  35.         puts("");  
  36.     }  
  37. }  
  38. void generate_test_data_file(int from, int to, int step)  
  39. {  
  40. FILE * fp;  
  41. int i;  
  42. char filename[FILENAME_MAX];  
  43. for(;from<=to;from+=step)  
  44.     {  
  45.         fp=fopen(itoa(from,filename,10),"w");  
  46. if(fp==NULL)  
  47.         {  
  48.             puts("generate test data file error!");  
  49.             exit(1);  
  50.         }  
  51.         fprintf(fp,"%d/n",from);  
  52. for(i=0;i
  53.         {  
  54.             fprintf(fp,"%d ",i);  
  55.         }  
  56.         fclose(fp);  
  57.     }  
  58. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值