我的C实践(3):用宏和位运算来实现整数集合
2009-09-30 12:34
357 查看
整数集合用一个位向量来表示。这里是无符号整数的集合,它适合存放范围在0~N-1之内的小整数,N是位向量的位数(这里为32位的unsigned int)。每个整数用集合中的一个位来表示,第i位为1(i=0~N-1),当且仅当整数i属于集合。
1、头文件set.h:整数集合的接口。
解释:
(1)singleset(i):由于要返回只含有一个整数i的集合,因此要将位向量的第i位置1,其余位均为0,只要将1右移i位即可。
(2)first_set_of_n_elements(n):要生成集合{0,1,...,n-1},就要将第0,1,...,n-1位置1,其余位置0。可将1右移n位,这样第n位为1,位向量的值为2**(n-1),这里**为幂。减1后就变成2**(n-1)-1,其第0,1,...,n-1位为1,其余位为0。
2、函数定义文件set.c:整数集合的各个函数实现。
解释:
(1)cardinality(SET x)用于返回集合中元素的个数,采用的方法每去掉一个最小元素,就让计数器增1。这里-x会对x取反并加1,x & -x获得x的最右边的位1(其余位变成0),x^(x & -x)会将x最右边的位1置成0,其余位不变,因此就去掉了集合中的最小元素。
(2)printset(SET z)用于打印集合中所有元素。它用forallelements(e, z)来遍历集合的每个元素,然后打印它。
(3)print_k_of_n(k,n)用于打印集合{0,1,...,n-1}的所有大小为k的子集。它先设置打印参数,然后用first_set_of_n_elements(k)生成初始的大小为k的子集z={0,1,...,k-1}。打印各个子集时,从初始的子集z开始,不断地应用next_set_of_n_elements(z)生成大小为k的新子集,并用printset打印它。一旦产生的集合为空集或包含了元素n,显然这时就不再是{0,1,...,n-1}的子集了,结束循环。
(4)关键看next_set_of_n_elements(SET x)的实现。它必须保证生成大小为k但与z不相同的子集,同时要保证当生成空集或生成包含了元素n的集合时,{0,1,...,n-1}的所有的大小为k的子集(个数为n!/(k!(n-k)!)都已经产生。采用的算法在注释中已经描述得比较清楚了。看一个例子,对于{0,1,2,3,4}的大小为3的所有子集,从初始子集{0,1,2}开始,由于算法只对最小的连续相邻的数(构成一个位1组)进行移动,因此它能遍历到所有大小为3的子集。对组中最右边的2向左移一位,组中其余的位1移到最右端,得{0,1,3},不停地应用该算法,得{0,2,3},{1,2,3},{0,1,4},....,{2,3,4},再移动就得{0,1,5},生成含有元素5的集合,不再是子集了,因此循环就结束。
3、测试文件testset.c:对整数集合的测试。
1、头文件set.h:整数集合的接口。
/* * set.h:整数集合,是一个位向量。适合存放范围在0~N-1之内的小整数,N是unsigned int的 * 位数(32位)。每个整数用集合中的一个位来表示。第i位为1(i=0~N-1),当且仅当i属于集合。 */ #ifndef UINT_SET_H #define UINT_SET_H #include <limits.h> /* 用到CHAR_BIT */ /* 类型SET用来代表集合 */ typedef unsigned int SET; /* SET_BITS: 每个集合的最大位数 */ #define SET_BITS (sizeof(SET)*CHAR_BIT) /* check(i): 判断i是否能够成为集合的一个元素 */ #define check(i) (((unsigned) (i)) < SET_BITS) /* emptyset: 返回无任何元素的空集 */ #define emptyset ((SET) 0) /* singleset(i): 返回只含有一个整数i的集合 */ #define singleset(i) (((SET) 1) << (i)) /* add(s,i): 向集合set中添加一个整数i,将第i位置为1即可 */ #define add(set,i) ((set) | singleset (i)) /* intersect: 返回两个集合的交集 */ #define intersect(set1,set2) ((set1) & (set2)) /* union: 返回两个集合的并集 */ #define union(set1,set2) ((set1) | (set2)) /* setdiff: 返回两个集合的对称差(只属于其中一个集合,而不属于另一个集合的元素) */ #define setdiff(set1,set2) ((set1) ^ (set2)) /* element: 判断i是否在集合set中 */ #define element(i,set) (singleset((i)) & (set)) /* forallelements: 遍历集合s中的每个元素,以便执行一个语句。例如,为了打印 集合S中的每个整数,可以这样写: int j; forallelements(j, s) printf("%d ", j); */ #define forallelements(j,s) / for ((j)=0; (j)<SET_BITS; ++(j)) if (element((j),(s))) /* first_set_of_n_elements(n): 生成集合{0,1,...,n-1}, 这利用了无符号整数的减法性质 */ #define first_set_of_n_elements(n) (SET)((1<<(n))-1) /* next_set_of_n_elements(s): 对集合s,产生一个大小相等的新集合,它与x必定不相同。 如果你从first_set_of_n_elements(k)的结果{0,1,...,k-1}开始,在前面的结果上不断地 应用next_set_of_n_elements,直到产生的集合包含了整数m。这样你就得到{0,1,...,m-1} 的所有大小为k的子集,其个数为“从m件东西中选出k件东西的所有可能的组合数目”,它应该等于 m!/(k!(m-k)!) */ extern SET next_set_of_n_elements(SET x); /* printset(s): 打印集合x的所有元素,输出格式为{1, 2, 3, 4} */ extern void printset(SET z); /* cardinality(s): 返回集合x的基数(即元素个数) */ extern int cardinality(SET x); /* print_k_of_n(k,n): 打印集合{0,1,...,n-1}的所有大小为k的子集,每一行会打印尽可能 多的子集。同时也会打印这些子集的总个数,它应该等于n!/(k!(n-k)!) */ extern void print_k_of_n(int k, int n); #endif
解释:
(1)singleset(i):由于要返回只含有一个整数i的集合,因此要将位向量的第i位置1,其余位均为0,只要将1右移i位即可。
(2)first_set_of_n_elements(n):要生成集合{0,1,...,n-1},就要将第0,1,...,n-1位置1,其余位置0。可将1右移n位,这样第n位为1,位向量的值为2**(n-1),这里**为幂。减1后就变成2**(n-1)-1,其第0,1,...,n-1位为1,其余位为0。
2、函数定义文件set.c:整数集合的各个函数实现。
/* * set.c:整数集合的各个函数实现 */ #include <stdio.h> #include "set.h" /* 返回集合x的基数(即元素个数) */ int cardinality(SET x){ int count = 0; while (x != emptyset) { /* -x会对x取反并加1,x & -x获得x的最右边的位1(其余位变成0), x^(x&-x)会将x最右边的位1置成0,因此就去掉了集合中的最小元素 */ x ^= (x & -x); ++count; } return count; } /* 对集合x,产生一个大小相等的新集合,它与x必定不相同 */ SET next_set_of_n_elements(SET x){ /* 这里的代码利用了无符号运算的一些技巧,例子如下: 如果 x == 001011001111000, 则 smallest == 000000000001000 ripple == 001011010000000 new_smallest == 000000010000000 ones == 000000000000111 the returned value == 001011010000111 基本思想是找到x中最右边的连续位1组,把这个组最左边的位1向左滑动一位, 把其余的所有1都滑到最右端 */ SET smallest, ripple, new_smallest, ones; if(x == emptyset) return x; smallest = (x & -x); /* 得到只含x最右边的位1的数(其余位为0) */ ripple = x + smallest; /* 会把x最右边的连续位1组清0,并且组的左边一位置1,这样组的 最左边位1就向左滑了一位,这个位成为ripple最右边的位1 */ new_smallest = (ripple & -ripple); /* 得到只含ripple最右边的位1的数 */ /* 将new_smallest的这个位1滑到连续位1组的左边,然后将其后面的所有位都置1(其余位变成0) */ ones = ((new_smallest / smallest) >> 1) - 1; return (ripple | ones); /* 得到最后结果并返回 */ } /* 打印集合z的所有元素,输出格式为{1, 2, 3, 4} */ void printset(SET z){ int first = 1; /* 表示集合打印的开始 */ int e; forallelements(e, z) { if(first) printf("{"); else printf(", "); printf("%d", e); first = 0; } if (first) printf("{"); /* 上面没有执行,说明集合中无元素,是空集 */ printf("}"); /* 打印集合的闭括号 */ } #define LINE_WIDTH 54 /* 输出行的行宽 */ /* 打印集合{0,1,...,n-1}的所有大小为k的子集 */ void print_k_of_n(int k, int n){ int count = 0; /* 集合打印时占的宽度,为k*4+3或k*3+3 */ int printed_set_width = k * ((n > 10) ? 4 : 3) + 3; /* 每行打印的集合个数 */ int sets_per_line = LINE_WIDTH / printed_set_width; SET z=first_set_of_n_elements(k); /* 生成集合{0,1,...,k-1} */ /* 生成集合{0,1,...,n-1},然后打印其所有大小为k的子集 */ printf("/nAll the size-%d subsets of ", k); printset (first_set_of_n_elements(n)); printf(":/n"); do{ /* 打印{0,1,...,n-1}的所有大小为k子集 */ printset(z); if((++count) % sets_per_line) printf (" "); else printf("/n"); /* 一行已满时,换行 */ z = next_set_of_n_elements(z); /* 产生一个大小为k的新子集 */ }while((z != emptyset) && !element(n, z)); /* 一旦产生的集合为空集或包含了元素n, 说明不再是子集,结束循环 */ if ((count) % sets_per_line) printf ("/n"); printf("The total number of such subsets is %d./n", count); }
解释:
(1)cardinality(SET x)用于返回集合中元素的个数,采用的方法每去掉一个最小元素,就让计数器增1。这里-x会对x取反并加1,x & -x获得x的最右边的位1(其余位变成0),x^(x & -x)会将x最右边的位1置成0,其余位不变,因此就去掉了集合中的最小元素。
(2)printset(SET z)用于打印集合中所有元素。它用forallelements(e, z)来遍历集合的每个元素,然后打印它。
(3)print_k_of_n(k,n)用于打印集合{0,1,...,n-1}的所有大小为k的子集。它先设置打印参数,然后用first_set_of_n_elements(k)生成初始的大小为k的子集z={0,1,...,k-1}。打印各个子集时,从初始的子集z开始,不断地应用next_set_of_n_elements(z)生成大小为k的新子集,并用printset打印它。一旦产生的集合为空集或包含了元素n,显然这时就不再是{0,1,...,n-1}的子集了,结束循环。
(4)关键看next_set_of_n_elements(SET x)的实现。它必须保证生成大小为k但与z不相同的子集,同时要保证当生成空集或生成包含了元素n的集合时,{0,1,...,n-1}的所有的大小为k的子集(个数为n!/(k!(n-k)!)都已经产生。采用的算法在注释中已经描述得比较清楚了。看一个例子,对于{0,1,2,3,4}的大小为3的所有子集,从初始子集{0,1,2}开始,由于算法只对最小的连续相邻的数(构成一个位1组)进行移动,因此它能遍历到所有大小为3的子集。对组中最右边的2向左移一位,组中其余的位1移到最右端,得{0,1,3},不停地应用该算法,得{0,2,3},{1,2,3},{0,1,4},....,{2,3,4},再移动就得{0,1,5},生成含有元素5的集合,不再是子集了,因此循环就结束。
3、测试文件testset.c:对整数集合的测试。
/* * testset.c 对整数集合的测试 */ #include "set.h" int main(void){ print_k_of_n(0, 4); print_k_of_n(1, 4); print_k_of_n(2, 4); /* 打印集合{0,1,2,3}的所有大小为2的子集 */ print_k_of_n(3, 4); print_k_of_n(4, 4); print_k_of_n(3, 5); print_k_of_n(3, 6); return 0; }
相关文章推荐
- 我的C实践(3):用宏和位运算来实现整数集合
- 我的C实践(3):用宏和位运算来实现整数集合
- 顺序表实现集合及大整数运算
- java是实现的大整数运算!
- inux shell 实现 四则运算(整数及浮点) 简单方法
- 大整数加减运算的C语言实现
- 我的C实践(4):基本操作的位运算实现
- 位运算实现两个整数相加
- 用位运算实现两个整数的加减乘除运算
- 位运算之美——用+,-和位运算实现正整数除法和取模(一)
- 离散数学实践:集合的表示与运算
- VC++ 利用位运算实现两个整数的加法运算
- 只用位运算实现整数的加减乘除运算
- 大整数类的实现包含了加减乘除运算
- 整数集合的实现
- 【数据结构_链表_List_1045】集合的交运算实现
- Java中异或运算实现两个整数的交换以及其功能函数实现
- 位运算表示集合的整数
- 利用位运算实现两个整数的加法运算
- linux shell 实现 四则运算(整数及浮点) 简单方法