From: http://dsqiu.iteye.com/blog/1715253
本篇博文,D.S.Qiu将对《C语言名题精选百则——排列,组合和集合》进行整理推出,不光只是书上的名题,还会依据互联网的资源进行不断补充,加强。等全书各个章节都整理完,会做一个总汇。如果你有建议、批评或补充,请你不吝提出(email:gd.s.qiu@gmail.com,或者直接在本文末评论)。你的支持和鼓励(一个人整理真的很累,几度想放弃),我将渐行渐远!
《排列,组合和集合》主要是介绍了关于集合的子集以及子集字典排序,Gray码,排列以及字典排列,集合的分割,整数的分割等8个组合数学基本的问题,介绍比较繁复(废话比较多),只要看了就能理解(点击查看更多数组和字符串问题),相对简单,但是要是尽善进美还有待不断的挖掘。下一篇是《查找》(其实关于有序数组的一些问题),更多关注:http://dsqiu.iteye.com。
问题3.1列出所有子集(DIRECT.C )
编写一个程序,列出{1,2,3,…,n}这个集合的所有子集,包括空集合。
【说明】
列出一个集合的所有子集有很多做法,题目中并没有要求依某个特定的次序来排列, 因此是不难做出来的。
因为集合中一共有n个元素,所以总共就会有2^n子集;例如{1,2,3}有如下子集:
{}
{1} {2} {3}
{1,2} {1,3} {2,3}
{1,2,3}
【解答】
看到2^n,就想起了二进制数,可以使用二进制的第 i 位表示是否包含集合的第 i 个元素,如数字6的二进制形式是110,表示取集合的第2,3两个元素组成的子集。这样0~2^n -1的数字就可以表示全部子集,每一个数字代表一个子集,实现应该不难。
【问题实现】
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXSIZE 20
- #define LOOP 1
- void main(void)
- {
- char digit[MAXSIZE];
- int i, j;
- int n;
- char line[100];
- printf("\nDirect Generation of All Subsets of a Set");
- printf("\n=========================================");
- printf("\n\nNumber of Elements in the Given Set --> ");
- gets(line);
- n = atoi(line);
- /* ---You'd better check to see if n is too large--- */
- for (i = 0; i < n; i++) /* clear all digits to 0 */
- digit[i] = '0';
- printf("\n{}"); /* outpout empty set {} */
- while (LOOP) {
- for (i = 0; i < n && digit[i] == '1'; digit[i] = '0', i++)
- ; /* find first 0 position */
- if (i == n) /* if none, all pos. are 1 */
- break; /* thus all elem. are in set*/
- else
- digit[i] = '1';/* now add one to this pos */
- for (i = 0; i < n && digit[i] == '0'; i++)
- ; /* find first 1 position */
- printf("\n{%d", i+1); /* show its numner and */
- for (j = i + 1; j < n; j++) /* others */
- if (digit[j] == '1')
- printf(",%d", j + 1);
- printf("}");
- }
- }
问题3.2列出所有子集——字典顺序(LEXICAL.C )
编写一个程序,用字典顺序(Lexical Order)把一个集合的所有子集找出来。
【说明】
如果不知道何谓字典顺序,在此作一个简单的说明。假设给定的集合有n个元素,
{1,2,3,4}与{1,2,4}是两集合,它们前面的两个元素相同,但第三个不同,因此包含小的元 素的集合就排在前面。请回想一下,这与字符串的比较有什么不一样呢?完全相同,惟一 的差异,就是在集合中的元素要从小到大排好。
下面左边是n=3,右边是n=4的结果,如表3-1所示。
【解答】
事实上,这是一个十分简单的程序,除了空集合之外,最“小”的一个集合就是{1}再下一个就是包含1,而且再加上1的下一个元素(1+1=2)的集合,即{1,2};下一个元素 自然就是含有1与2,并且还有2的下一个元素(2+1=3)的集合{1,2,3}了。就这样一直到包含了所有元素为止,亦即{1,2,3,····,n}。下一个集合是谁?绝不是{1,2,3,…,n-1},.因为它 比{1,2,3,…,n}小,事实上应该是{1,2,3,…,n-2,n}。为什么呢?在{1,2,3,…,n-1,n}与 {1,2,3,…,n-2,n}之间,前n-2个元素完全相同,但是n-1<n,这不就证实了以上说法了吗?
由于以上原因,因此可以用一个数组set[]来存放集合的元素,一开始时set[0]=1,表 示{1};另外,用一个变量position,指出目前最右边的位置何在,在开始时自然就是1。 接下来,就陆续地产生下一个集合了。注意,目前集合中最右边的元素是set[position],如 果它的值比n小,那就表示还可以加进一个元素,就像是从{1,2,3}加上一个元素变成{1,2,3, 4}一样(n>4)。这倒是容易做,下一个元素在set[position+1],因此在那存入set[position+1] 这个值就行了;同时position也向右移一位。如果目前最右边元素set [position]已经是n, 因而不能再增加元素了。例如,当n=4时,如果有{1,3,4},那自然不能像前面所说的加入一个5。这时看最右边元素的位置,亦即position,是不是在第一位(如果n=6,而现在的集合是{6}),如果不在第一位,那就可以往回移一位,并且把那个位置的值加上1。例如, 如果现在有{1,3,4},而n=4;最右边(4)的位置不是在第一位,因而退回一位,等于是{1,3}; 但这是不对的,因为{1,3}比{1,3,4} “小”,要做得比{1,3,4}大,把3加上1而变成{1,4}就 行了。如果最右边(4)的位置是在第一位,那么程序就完成了。
【问题实现】
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXSIZE 20
- #define LOOP 1
- void main(void)
- {
- int set[MAXSIZE];
- int n, i;
- int position;
- char line[100];
- printf("\nAll Possible Subsets Generation by Lexical Order");
- printf("\n================================================");
- printf("\n\nNumber of Elements in the Set --> ");
- gets(line);
- n = atoi(line);
- printf("\n{}"); /* the empty set */
- position = 0; /* start from the 1st pos. */
- set[position] = 1; /* it gets a '1' */
- while (LOOP) { /* loop until done... */
- printf("\n{%d", set[0]); /* print one result */
- for (i = 1; i <= position; i++)
- printf(",%d", set[i]);
- printf("}");
- if (set[position] < n) { /* this pos. can be inc*/
- set[position+1] = set[position] + 1; /* YES*/
- position++; /* inc. next pos. */
- }
- else if (position != 0) /* NO, the 1st pos? */
- set[--position]++; /* backup and increase */
- else /* NO, the 1st pos and can */
- break; /* not be inc. JOB DONE! */
- }
- }
【习题】
(1)有n个元素的集合的子集个数有2^n个,为什么前面所有的程序都不用一个for从 1数到2^n,而用break离开循环呢?(提示:想一想当n=50或100时会有什么后果)
(2)这个程序稍加改动就可以求出n个元素集合中元素个数不超过m(m<n)的所有子 集,请把它写出来。
(3)这个程序也可以改成求出n个元素的集合中元素个数恰好是m(m<n)的所有子 集,请把它写出来;请验查一下是否恰好有C(n,m)个?
注意:在编写(2)与(3)两题时,切不可以把所有子集都求出来,看看元素的个数, 如果在所要的范围,就提出该集合。这样的写法是不能接受的,虽然正确;应该在编写本 程序的概念上动动脑筋,只产生所要的集合,而不产生任何多余的部分才是正途。
(4)请写一个程序,把II个元素的集合的子集,用字典顺序的反顺序列出来(注意, 不能保存各个子集),然后用反顺序列出来,因为当n很大时内存就不够用了;试一试了解 上面所讲的观点,直接把程序列出来。
问题 3.3 产生 Gray 码(GRAYCODE.C )
编写一个程序,用Gray码(Gray Code)的顺序列出一个集合的所有子集。
【说明】
这个问题其实是在看有没有办法把Gray (人名)码用程序编写出来,有了Gray码, 找出对应的集合是件简单的事,问题3.2己经讲过了。
什么是Gray码? nbit的Gray码是一连串共有2^n个元素的数列,每一个元素都有nbit, 而且任何相邻的两个元素之间只有1bit的值不同。例如,3个bit的Gray码:
000 001 011 010 110 111 101 100
是一组Gray码,任何相邻两个元素都只有1bit值不同。但是,Gray码却并不是惟一的, 把它循环排列或是用反过来的顺序写,也会得到一组Gray码;比如说,如果把最后3个元 素放到最前面去,就会得到:
111 101 100 000 001 011 010 110
也是一组Gray码。
Gray码是一个很直观的几何意义,不妨把nbit看成是n度空间中一个点的坐标,因此 有2^n个坐标点,正好是n维空间中的一个正立方体的2^n个角落。如图3-1a所示,当n=2 时就是平方,是个正方形,如图3-1b所示,时就是个正立方体了。
如果能够从原点出发把每个顶点都走一次再回到原点,且每一个顶点都不重复,那么 沿途经过的点的坐标,就是一个Gray码。在图3-1a中,依箭头顺序,会得到00,10,11,01;对图3-1b而言,则是000,001,011,010,110,111,101,100。当然,用不同的走法,就会有不同的Gray码。例如在图3-1b中,000,100,101,011,111,110,010就是一个很好的例子。
看看下面n=4的Gray码:
- /* ------------------------------------------------------ */
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXSIZE 20
- #define YES 1
- #define LOOP 1
- #define FLIP_DIGIT(x) x = ((x) == '0' ? '1' : '0')
- #define FLIP(x) x = (1 - (x))
- void main(void)
- {
- char digit[MAXSIZE];
- int position[MAXSIZE];
- int even;
- int n;
- int i, count = 0;
- char line[100];
- printf("\nAll Subset Listing by Gray Code");
- printf("\n===============================");
- printf("\n\nNumber of Elements in Set Please --> ");
- gets(line);
- n = atoi(line);
- printf("\n"); /* initialization */
- for (i = 0; i < n; i++) {
- digit[i] = '0';
- printf("0");
- }
- printf(" : {}\n"); /* the empty set */
- even = YES;
- while (LOOP) {
- if (even) /* for even positions:0,2,..*/
- FLIP_DIGIT(digit[0]); /* flip the 1st bit */
- else { /* for odd positions... */
- for (i = 0; i < n && digit[i] == '0'; i++)
- ; /* find the first 1 bit */
- if (i == n-1) break; /* if it is the last..*/
- FLIP_DIGIT(digit[i+1]); /* NO, flip its nbr*/
- }
- for (count = 0, i = n - 1; i >= 0; i--) {
- printf("%c", digit[i]); /* print the bits */
- if (digit[i] == '1') /* and collect pos */
- position[count++] = i + 1;
- }
- printf(" : {%d", position[count-1]);
- for (i = count - 2; i >= 0; i--) /* print pos */
- printf(",%d", position[i]);
- printf("}\n");
- FLIP(even); /* next will be odd(or even)*/
- }
- }
在上面的例子中,4个元素经过上次旋转就会回到原来的样子,将前3个元素旋转一 次(见上面加框的地方),得到一个新的组合,这个组合旋转4次,又回到原样,因而再把前3个元素旋转一次;新组合旋转4次又还原,于是前3个元素再旋转一次,不过3个元 素经过3次旋转之后又会还原,所以这3个元素的前两个元素还要旋转一次,一直到两个元
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXSIZE 20
- #define ROTATE(p) { int i, temp; \
- temp = perm[p]; \
- for (i = p-1; i >= 0; i--) \
- perm[i+1] = perm[i]; \
- perm[0] = temp; \
- }
- void main(void)
- {
- int perm[MAXSIZE];
- int position;
- int n;
- int i;
- char line[100];
- printf("\nPermutation by Rotation Method");
- printf("\n==============================");
- printf("\n\nNumber of Elements --> ");
- gets(line);
- n = atoi(line);
- for (i = 0; i < n; i++) /* initialize to 1,2,...,n */
- perm[i] = i + 1;
- position = n - 1;
- while (position != 0) { /* if still have positions..*/
- printf("\n"); /* display result */
- for (i = 0; i < n; i++)
- printf("%d ", perm[i]);
- position = n - 1; /* starts from the last pos */
- ROTATE(position); /* rotate them. */
- while (perm[position]==position+1 && position!=0) {
- position--; /* if last pos are equal and*/
- ROTATE(position); /* not zero, rotate again*/
- }
- }
- }
- #include <stdio.h>
- #include <stdlib.h>
- #define LOOP 1
- #define SWAP(a,b) { int t; t = a; a = b; b = t; }
- #define REVERSE(a,b) { int i, j; \
- for (i=(a), j=(b); i < j; i++, j--) \
- SWAP(perm[i], perm[j]); \
- }
- #define DISPLAY(n) { int i; \
- printf("\n"); \
- for (i = 0; i < n; i++) \
- printf("%d ", perm[i]); \
- }
- void again(int perm[], int L, int R)
- {
- int i = R;
- while (LOOP) {
- if (R - L + 1 > 2) {
- again(perm, L+1, R);
- DISPLAY(R);
- }
- if (i > L ) {
- SWAP(perm[L], perm[i]);
- REVERSE(L+1, R);
- DISPLAY(R);
- i--;
- }
- else
- break;
- }
- }
- void permut(int perm[], int n)
- {
- int i;
- for (i = 0; i < n; i++)
- perm[i] = i + 1;
- again(perm, 0, n-1);
- }
- #define MAXSIZE 20
- void main(void)
- {
- int perm[MAXSIZE];
- char line[100];
- int n;
- gets(line);
- n = atoi(line);
- permut(perm, n);
- }
- #include <stdio.h>
- #include <stdlib.h>
- #define MAXSIZE 20
- #define LOOP 1
- void main(void)
- {
- int set[MAXSIZE];
- int n;
- int k;
- int position;
- int i, j;
- char line[100];
- printf("\nAll k Elements Subsets from a n Elements Set");
- printf("\n============================================");
- printf("\n\nNumber of Elements in the Set --> ");
- gets(line);
- n = atoi(line);
- printf("\n\nNumber of Elements in Subset ---> ");
- gets(line);
- k = atoi(line);
- for (i = 0; i < k; i++) /* initialize the subset to */
- set[i] = i + 1; /* {1,2,3,...,k} */
- printf("\n{%d", set[0]); /* display it */
- for (j = 1; j < k; j++)
- printf(",%d", set[j]);
- printf("}");
- position = k - 1; /* 'position' indicates the */
- while (LOOP) { /* pos to be increased */
- if (set[k-1] == n) /* if last pos is full, then*/
- position--; /* move position left */
- else /* or if not full, then the */
- position = k - 1; /* pos is always the last*/
- set[position]++; /* so add one to the pos. */
- for (i = position+1; i < k; i++) /* add inc. all*/
- set[i] = set[i-1] + 1; /* pos to its right */
- printf("\n{%d", set[0]); /* display it. */
- for (j = 1; j < k; j++)
- printf(",%d", set[j]);
- printf("}");
- if (set[0] >= n-k+1) break; /* if 1st pos full */
- }
- }
- #include <stdio.h>
- #include <stdlib.h> /* for malloc() */
- #define ALWAYS 1
- void display(int *, int);
- void set_partition(int n)
- {
- int *code, *maxi; /* arrays for code[], maxi[]*/
- int i, j;
- code = (int *) malloc(sizeof(int)*n); /* get memory */
- maxi = (int *) malloc(sizeof(int)*n);
- for (i = 0; i < n; i++) /* initialization */
- code[i] = 1, maxi[i] = 2;
- while (ALWAYS) { /* loop until done. */
- display(code, n); /* display one partition */
- for (i = n-1; code[i] == maxi[i]; i--)
- ; /* find next 'increasible' */
- if (i > 0) { /* found ? */
- code[i]++; /* YES, update */
- for (j = i + 1; j < n; j++) {
- code[j] = 1;
- maxi[j] = maxi[i]+((code[i]==maxi[i]) ? 1 : 0);
- }
- }
- else /* NOT FOUND, done. */
- break;
- }
- free(code);
- free(maxi);
- }
- /* ------------------------------------------------------ */
- /* FUNCTION display : */
- /* This function displays the code of the partition. */
- /* ------------------------------------------------------ */
- void display(int *code, int n)
- {
- int i;
- printf("\n");
- for (i = 0; i < n; i++)
- printf("%3d", *(code+i));
- }
- /* ------------------------------------------------------ */
- void main(void)
- {
- char line[100];
- int n;
- printf("\nSet Partition Program for {1,2,3,...,N}");
- printf("\n=======================================");
- printf("\n\nN Please --> ");
- gets(line);
- n = atoi(line);
- printf("\nCodes of Partitions.");
- printf("\ni-th position = j means i in partition j\n");
- set_partition(n);
- }
- unsigned long compute(int, int);
- unsigned long int_part_no(int);
- unsigned long int_part_no(int n)
- {
- return compute(n, n); /* convert to another form */
- }
- unsigned long compute(int number, int maximum)
- {
- if (number == 1 || maximum == 1)
- return 1;
- else if (number < maximum)
- return compute(number, number);
- else if (number == maximum)
- return 1 + compute(number, maximum-1);
- else
- return compute(number,maximum-1) +
- compute(number-maximum,maximum);
- }
- /* ------------------------------------------------------ */
- #include <stdio.h>
- #include <stdlib.h>
- void main(void)
- {
- int n;
- char line[100];
- printf("\nNumber of partitions of an Integer");
- printf("\n==================================");
- printf("\n\nN --> ");
- gets(line);
- n = atoi(line);
- printf("\nThere are %lu partitions.", int_part_no(n));
- }
- //* ------------------------------------------------------ */
- /* PROGRAM integer partition : */
- /* Given a positive integer n, this program lists all */
- /* partition of n in anti-lexical order. */
- /* */
- /* Copyright Ching-Kuang Shene July/21/1989 */
- /* ------------------------------------------------------ */
- #include <stdio.h>
- #include <stdlib.h> /* for atoi() */
- #define MAXSIZE 20
- void display(int [], int [], int);
- void main(void)
- {
- int partition[MAXSIZE+1]; /* the actuall partition */
- int mult[MAXSIZE+1]; /* multiplicity */
- int part_no; /* no. of parts */
- int sum, size, remainder, count;
- int n;
- char line[100];
- printf("\nPartition of Integer");
- printf("\n====================");
- printf("\n\nInput a Positive Integer --> ");
- gets(line);
- n = atoi(line);
- partition[1] = n; /* at the biginning, we have*/
- mult[1] = part_no = count = 1; /* only one part.*/
- display(partition, mult, part_no);
- do { /* size one sum in 'sum' */
- sum = (partition[part_no]==1) ? mult[part_no--]+1 : 1;
- size = partition[part_no] - 1; /* dec. size */
- if (mult[part_no] != 1) /* last part with mul=1*/
- mult[part_no++]--; /* NO, cut this part */
- partition[part_no] = size; /* set new part=size */
- mult[part_no] = sum/size + 1; /* fill other*/
- if ((remainder = sum % size) != 0) {
- partition[++part_no] = remainder;
- mult[part_no] = 1;
- }
- count++;
- display(partition, mult, part_no);
- } while (mult[part_no] != n);
- printf("\n\nThere are %d partitions in anti-lexical order",
- count);
- }
- /* ------------------------------------------------------ */
- /* FUNCTION display : */
- /* This routine displays the given partition. */
- /* ------------------------------------------------------ */
- void display(int partition[], int mult[], int part_no)
- {
- int i, j;
- printf("\n");
- for (i = 1; i <= part_no; i++) /* for each part */
- for (j = 1; j <= mult[i]; j++) /* and its mult. */
- printf("%3d", partition[i]); /* show them */
- }