一、产生可能的集合
说明
给定一组数字或符号,产生所有可能的集合(包括空集合), 例如给定1 2 3,
则可能的集合为:
{}、{1}、{1,2}、{1,2,3}、{1,3}、{2}、{2,3}、{3}。
解法:如何产生二进位数
第一种:可以使用unsigned型别加上&位元运算来产生
第二种:使用阵列搜寻
如果不考虑字典顺序,则有个简单的方法可以产生所有的集合,思考二进位数字加法,并注意 1出现的位置,如果每个位置都对应一个数字,则由1所对应的数字所产生的就是一个集合,例 如:
了解这个方法之后,剩下的就是如何产生二进位数?有许多方法可以使用,您可以使用unsigned 型别加上&位元运算来产生,这边则是使用阵列搜寻,首先阵列内容全为0,找第一个1,在还 没找到之前将走访过的内容变为0,而第一个找到的0则变为1,如此重复直到所有的阵列元素 都变为1为止,例如:
这里采用阵列搜索的方法,所以要注意代码里面是如何将0逐个转变为1的。
000 => 100 => 010 => 110 => 001 => 101 => 011 => 111
如果要产生字典顺序,例如若有4个元素,则:
{} => {1} => {1,2} => {1,2,3} => {1,2,3,4} => {1,2,4} => {1,3} => {1,3,4} =>
{1,4} => {2} => {2,3} => {2,3,4} => {2,4} => {3} => {3,4} =>
000 {} 001 {3} 010 {2} 011 {2,3} 100 {1} 101 {1,3} 110 {1,2} 111 {1,2,3} {4}
简单的说,如果有n个元素要产生可能的集合,当依序产生集合时,如果最后一个元素是n,而 倒数第二个元素是m的话,例如: {a b c d e n}
则下一个集合就是{a b c d e+1},再依序加入后续的元素。
例如有四个元素,而当产生{1 2 3 4}集合时,则下一个集合就是{1 2 3+1},也就是{1 2 4},由于 最后一个元素还是4,所以下一个集合就是{1 2+1},也就是{1 3},接下来再加入后续元素4,也 就是{1 3 4},由于又遇到元素4,所以下一个集合是{1 3+1},也就是{1 4}。
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 20
int main(void) {
char digit[MAXSIZE];
int i, j;
int n;
printf("输入集合个数:");
scanf("%d", &n);
for(i = 0; i < n; i++)
digit[i] = '0';
printf("\n{}"); // 空集合
while(1) {
// 找第一个0,并将找到前所经过的元素变为0
for(i = 0; i < n && digit[i] == '1'; digit[i] = '0', i++);
if(i == n) // 找不到0
break;
else // 将第一个找到的0变为1
digit[i] = '1';
// 找第一个1,并记录对应位置
for(i = 0; i < n && digit[i] == '0'; i++);
printf("\n{%d", i+1);
//将后续1的位置都加入进来
for(j = i + 1; j < n; j++)
if(digit[j] == '1')
printf(",%d", j + 1);
printf("}");
}
printf("\n");
return 0;
}
//C(字典顺序)
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 20
int main(void) {
int set[MAXSIZE];
int i, n, position = 0;
printf("输入集合个数:");
scanf("%d", &n);
printf("\n{}");
set[position] = 1;
while(1) {
printf("\n{%d", set[0]); // 印第一个数
for(i = 1; i <= position; i++)
printf(",%d", set[i]);
printf("}");
//主要是通过回退来达到,输出所有集合的目的。
if(set[position] < n) { // 递增集合个数
set[position+1] = set[position] + 1;
position++;
}//当到达最高位时,开始回退
else if(position != 0) { // 如果不是第一个位置
position--; // 倒退
set[position]++; // 下一个集合尾数
}
else // 已倒退至第一个位置
break;
}
printf("\n");
return 0;
}
二、子集
假设有个集合拥有m个元素,任意的从集合中取出n个元素,则这n个元素所形成的可能子集有哪些?
解法
假设有5个元素的集点,取出3个元素的可能子集如下:
{1 2 3}、{1 2 4 }、{1 2 5}、{1 3 4}、{1 3 5}、{1 4 5}、{2 3 4}、{2 3 5}、{2 4 5}、 {3 4 5}
这些子集已经使用字典顺序排列,如此才可以观察出一些规则:
1.如果最右一个元素小于m,则如同码表一样的不断加1
2.如果右边一位已至最大值,则加1的位置往左移
3.每次加1的位置往左移后,必须重新调整右边的元素为递减顺序
所以关键点就在于哪一个位置必须进行加1的动作,到底是最右一个位置要加1?还是其它的位 置?
在实际撰写程式时,可以使用一个变数positon来记录加1的位置,position的初值设定为n-1, 因为我们要使用阵列,而最右边的索引值为最大的n-1,在position位置的值若小于m就不断加 1,如果大于m了,position就减1,也就是往左移一个位置;由于位置左移后,右边的元素会经 过调整,所以我们必须检查最右边的元素是否小于m,如果是,则position调整回n-1,如果不 是,则positon维持不变。
#include <stdio.h>
#include <stdlib.h>
#define MAX 20
int main(void) {
int set[MAX];
int m, n, position;
int i;
printf("输入集合个数m:");
scanf("%d", &m);
printf("输入取出元素n:");
scanf("%d", &n);
for(i = 0; i < n; i++)
set[i] = i + 1;
// 显示第一个集合
for(i = 0; i < n; i++)
printf("%d ", set[i]);
putchar('\n');
position = n - 1;
while(1) {
//普通的元素递增
if(set[n-1] == m)
position--;
else
position = n - 1;
set[position]++;
// 调整右边元素,就是如果左边的元素改变了,后面的元素都要以此为基准,递增到最后
for(i = position + 1; i < n; i++)
set[i] = set[i-1] + 1;
for(i = 0; i < n; i++)
printf("%d ", set[i]);
putchar('\n');
//五个数取三个数,则最大集合中最小值为(m-n+1),取到最大集合处了
if(set[0] >= m - n + 1)
break;
}
return 0;
}