组合型枚举的三种方法(DFS/栈/Gosper‘s Hack)

组合型枚举题目

DFS

#include<bits/stdc++.h>
using namespace std;

int n,m;
vector<int>vec;

void dfs(int k)
{
    if(vec.size()>m||vec.size()+(n-k+1)<m) return;
    //到达枚举边界,输出结果并结束
    if(k==n+1)
    {
        for(int i=0;i<vec.size();i++)
            cout<<vec[i]<<" ";
        cout<<endl;
        return;
    }
    //选择这个数
    vec.push_back(k);
    dfs(k+1);
    //回溯
    vec.pop_back();
    //不选择这个数
    dfs(k+1);
}

int main()
{
    cin>>n>>m;
    dfs(1);
    return 0;
}


栈(模拟机器实现)

一台典型的32位计算机采用“堆栈结构“”来实现函数调用,它在汇编语言中把函数所需的第k个、第k-1个…第1个参数依次传入栈,然后执行call指令,该指令把返回地址(当前语句的下一条语句的地址入栈,然后跳转到address位置的语句。在函数返回时,它执行ret指令。该指令把返回地址出栈,并跳转到该地址继续执行。
对于函数中定义的C++局部变量,在每次执行call与ret指令时,也会在“栈”中相应地保存与复原,而作用范围超过该函数的变量,以及通过new和molloc函数动态分配的空间则保存在另一块\称为“堆“”的结构中,栈指针、返回值、局部的运算会借助CPU的寄存器完成。
由此可知:
1.局部变量在每层递归中都占有一份空间,声明过多或递归过深就会超过栈所能储存的范围,造成栈溢出;
2.非局部变量对于各层递归都共享同一份空间,需要及时维护、还原现场,以防止在各层递归之间储存和读取的数据相互影响。
3.可以用一个数组模拟栈,使用变量模拟指针和返回值,使用switch/case或者goto/label模拟语句跳转。

#include<bits/stdc++.h>
using namespace std;
int st[100010],top,address=0,n,m;
vector<int>chosen;

void call(int x,int ret_address)
{
	int old_top=top;
	st[++top]=x;//储存参数x
	st[++top]=ret_address;//返回地址标号
	st[++top]=old_top;//在栈顶记录以前的top值
}
int ret()
{
	int ret_address=st[top-1];
	top=st[top];//恢复以前的top值
	return ret_address;
}

int main()
{
	cin>>n>>m;
	call(1,0);
	while(top)
	{
		int x=st[top-2];//获取参数
		switch(address)
		{
			case 0:
				if(chosen.size()>m||chosen.size()+(n-x+1)<m)
				{
					address=ret();//return
					continue;
				}
				if(x==n+1)
				{
					for(int i=0;i<chosen.size();i++)
						cout<<chosen[i]<<' ';
					cout<<endl;
					address=ret();//return
					continue;
				}
				call(x+1,1);
				address=0;
				continue;//回到while循环开头,相当于开始新的递归
			case 1:
				chosen.push_back(x);
				call(x+1,2);//返回后会执行case 2
				address=0;
				continue;//回到while循环开头,相当于开始新的递归
			case 2:
				chosen.pop_back();
				address=ret();	
		}
	}
	return 0;
}

Gosper’s Hack

Gosper’s Hack是一种生成n元集合所有k元子集的算法。时间复杂度是 O ( C n k ∗ n ) O(C_{n}^{k}*n) O(Cnkn)
详见此BLOG

void Gospers_Hack(int k, int n)
{
    int cur=(1<<k)-1;
    while(cur<1<<n)
    {
        int lowbit=cur&-cur;
        int high=cur+lowbit;
        cur=(((high^cur)>>2)/lowbit)|high;
    }
}

e.g:

Gospers_Hack(3,5)
{
	cur=00111;
	while(cur<100000)
	{
		lowbit=00001;
		high=01000;
		high^cur=01111;
		(high^cur)>>2)=00011;
		(high^cur)>>2)/lowbit)=00011;(抹掉末尾0)
		cur=01011;
		......
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春弦_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值