C语言名题精选百则——排列,组合与集合

这篇博客整理了C语言解决排列、组合和集合问题的名题,包括列出所有子集及其字典顺序和Gray码。通过二进制表示集合元素,详细解释了如何按特定顺序生成子集。此外,还提出了关于程序优化和Gray码生成的思考,并提供了相关习题。
摘要由CSDN通过智能技术生成

C语言名题精选百则——排列,组合与集合

 

尊重他人的劳动,支持原创

本篇博文,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码:

0000 0001 0011 0010
0110 0111 0101 0100
1100 1101 1111 1110
1010 1011 1001 1000

仔细看看哪一个位置是从0变到1的,它与左右两个邻居的关系如何,或许就有办法写成程序了。或者,如图3-1b所示,沿途经过的方向有何变化,也可以编写出程序。这只是两点提 示,方法不止有这两个。

【解答】
把n=4的Gray码写出来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值