zl程序教程

您现在的位置是:首页 >  其他

当前栏目

算法入门到进阶(三)——搜索技术(子集生成和组合问题)

搜索算法技术入门 生成 进阶 组合 子集
2023-09-11 14:18:26 时间


前言

上一篇博客中,我们求10个数的排列问题中,如果不需要输出全排列,而是输出组合,即子集(子集内部的元素是没有顺序的),那么该如何做呢?

从以前我们所学的知识中,一个包含n个元素的集合,它的子集有2^n个。
用二进制的概念进行对照是最直观的
例如n=3的集合{a1,a2,a3},它的子集和二进制的对应关系如下

空集:0 0 0
a1:  1 0 0
a2:  0 1 0
a3:  0 0 1
.
.
.
a1,a2,a3:1 1 1  

所以,每个子集对应一个二进制数,这个二进制数的每一个都对应这个子集中的某个元素,而且子集中的元素是没有顺序的。

从上面的对应表也可以理解为什么子集的数量是2^n个,因为所有二进制数的总个数是2 ^n。

案例1:打印n位二进制数的所有子集

源码

#include<iostream>
using namespace std;

void print_subset(int n) {
	for (int i = 0; i < (1 << n ); i++) {//所以一共就是2^n个子集
		//i:0 ~ 2^n,,每个i的二进制数都对应一个子集,一次打印一个子集,最后得到所有子集
		for (int j = 0; j <n; j++) {//将1和i的n位中的每一位做与运算,逐个检查
			if (i & (1 << j)) {
				cout <<"1 ";
			}
			else {
				cout << "0 ";
			}
		}
		cout << endl;
	}
}
int main() {
	int n;
	cout << "n:";
	cin >> n;
	print_subset(n);
	system("pause");
	return 0;
}

运行结果

在这里插入图片描述

分析

在这里插入图片描述

回到上一篇博客的问题3

题目

打印n个数中任意m个数的组合。

思路

对照子集生成的二进制方法,已经知道一个子集对应一个二进制数。那么一个有k个元素的子集,它对应的二进制数中有k个1。所以,问题就转化为查找1的个数为k的二进制数,这些二进制数,就是需要打印的子集。

那么如何判断二进制数中1的个数为k,这里和大家分享一个新方法,就是每次消除最后一个1 公式: kk=kk & (kk-1)。每经过一次这样的与运算,原始数据就少一个1.
如1011

1011 & (1011-1)=1010; 少一个1
1010 & (1010-1)=1000;再少一个1
1000 & (1000-1)=0000; 又少一个1

所以三次操作下来,结果为0,那么原来的数值就有3个1.

源码

#include<iostream>
using namespace std;

//第一步就是生成n个数的所有子集
void print_set(int n, int m) {//
	for (int i = 0; i < (1 << n); i++) {//生成所有子集数
		int num = 0;
		int k = i;
		while (k) {  //第二步,获取1的个数
			k &= (k - 1);
			num++;
		}
		if (num == m) {//如果有m个1,就是一个组合
			for (int j = 0; j < n; j++) {//将这个结果i的每一位答应出来
				if (i & (1 << j)) {
					cout << "1 ";
				}
				else {
					cout << "0 ";
				}
			}
		}
		cout << endl;
	}
}

int main() {
	int n, m;
	cout << "n m:";
	cin >> n >> m;
	print_set(n, m);
	system("pause");
	return 0;
}

运行结果

在这里插入图片描述

分析

在这里插入图片描述

总结

排列和组合的区别就是,排列是有序的,组合是无序的
1 2 3, 3 2 1对组合是一样的,对排列就不一样。

同时大家记住这个找1的个数方法哦,在很多时候,用起来都是很好的。

下一篇博客我们分析 BFS(广度优先算法)和队列