acm搜索算法dfs排列组合.docx

上传人:b****8 文档编号:30664521 上传时间:2023-08-19 格式:DOCX 页数:39 大小:33.29KB
下载 相关 举报
acm搜索算法dfs排列组合.docx_第1页
第1页 / 共39页
acm搜索算法dfs排列组合.docx_第2页
第2页 / 共39页
acm搜索算法dfs排列组合.docx_第3页
第3页 / 共39页
acm搜索算法dfs排列组合.docx_第4页
第4页 / 共39页
acm搜索算法dfs排列组合.docx_第5页
第5页 / 共39页
点击查看更多>>
下载资源
资源描述

acm搜索算法dfs排列组合.docx

《acm搜索算法dfs排列组合.docx》由会员分享,可在线阅读,更多相关《acm搜索算法dfs排列组合.docx(39页珍藏版)》请在冰豆网上搜索。

acm搜索算法dfs排列组合.docx

acm搜索算法dfs排列组合

排列与组合(非递归的DFS算法)

组合的生成与排列相似,组合中数字相同顺序不同的可以归纳为一个组合,可以按升序生成或降序生成,即后面搜索到的数要大于(或小于)前面的数,我在这里以升序为例。

#include

voidcomb(intm,intn)

{

intt,k,i,a[20];

k=1;

t=0;

a[0]=0;

a[k]=0;

while(k>0){

a[k]++;//向下搜索

while((a[k]<=m)&&(a[k]<=a[k-1]))a[k]++;//判断当前结点是不是可行的,若不是则在容许范围内继续搜索

if(a[k]<=m){//有没有超出范围

if(k==n){//是不是目标解

for(i=1;i<=n;i++)

printf("%d",a[i]);

t++;//输出完毕回溯到上一层,继续搜索其他解

k--;

printf("\n");

}

else{//不是则进行下一层的搜索

k++;

a[k]=0;

}

}

elsek--;//若当前状态找不到可行的解则回溯

}

printf("sum:

%d",t);

}

intmain()

{

intm,n;

scanf("%d%d",&m,&n);

comb(m,n);

return0;

}

#include//组合

usingnamespacestd;

intn[1001],vtd[1001],digits,nums;

voiddfs(intx,intcrnt);

intmain()

{

memset(n,0,sizeofn);

memset(vtd,0,sizeofvtd);

cin>>nums>>digits;

dfs(1,1);

return0;

}

voiddfs(intx,intcrnt)

{

if(x<=digits)

{

for(intj=crnt;j<=nums;j++)

if(!

vtd[j])

{

vtd[j]=1;

n[x]=j;

dfs(x+1,j);

vtd[j]=0;cout<

cout<

}

}

#include//排列

usingnamespacestd;

intn,m;

intvst[11];

intp[11];

void

__read__()

{

cin>>n>>m;

}

void

__outp__()

{

for(inti=1;i<=m;i++)

cout<

cout<

}

void

__dfs__(intx)

{

if(x<=m)

for(inti=1;i<=n;i++)

if(!

vst[i])

{

vst[i]=true;

p[x]=i;

__dfs__(x+1);

vst[i]=false;

}

if(x>m)

__outp__();

}

int

main()

{

__read__();

__dfs__

(1);

return0;

}

[前言]这篇论文主要针对排列组合对回溯算法展开讨论,在每一个讨论之后,还有相关的推荐题。

在开始之前,我们先应该看一下回溯算法的概念,所谓回溯:

就是搜索一棵状态树的过程,这个过程类似于图的深度优先搜索(DFS),在搜索的每一步(这里的每一步对应搜索树的第i层)中产生一个正确的解,然后在以后的每一步搜索过程中,都检查其前一步的记录,并且它将有条件的选择以后的每一个搜索状态(即第i+1层的状态节点)。

需掌握的基本算法:

排列:

就是从n个元素中同时取r个元素的排列,记做P(n,r)。

(当r=n时,我们称P(n,n)=n!

为全排列)例如我们有集合OR={1,2,3,4},那么n=|OR|=4,切规定r=3,那么P(4,3)就是:

{1,2,3};{1,2,4};{1,3,2};{1,3,4};{1,4,2};{1,4,3};{2,1,3};{2,1,4};{2,3,1};{2,3,4};{2,4,1};{2,4,3};{3,1,2};{3,1,4};{3,2,1};{3,2,4};{3,4,1};{3,4,2};{4,1,2};{4,1,3};{4,2,1};{4,2,3};{4,3,1};{4,3,2}

算法如下:

intn,r;

charused[MaxN];

intp[MaxN];

voidpermute(intpos)

{inti;

/*如果已是第r个元素了,则可打印r个元素的排列*/

if(pos==r){

for(i=0;i

cout<<(p[i]+1);

cout<

return;

}

for(i=0;i

if(!

used[i]){/*如果第i个元素未用过*/

/*使用第i个元素,作上已用标记,目的是使以后该元素不可用*/

used[i]++;

/*保存当前搜索到的第i个元素*/

p[pos]=i;

/*递归搜索*/

permute(pos+1);

/*恢复递归前的值,目的是使以后改元素可用*/

used[i]--;

}

}

相关问题

UVA524PrimeRingProblem

可重排列:

就是从任意n个元素中,取r个可重复的元素的排列。

例如,对于集合OR={1,1,2,2},n=|OR|=4,r=2,那么排列如下:

{1,1};{1,2};{1,2};{1,1};{1,2};{1,2};{2,1};{2,1};{2,2};{2,1};{2,1};{2,2}

则可重排列是:

{1,1};{1,2};{2,1};{2,2}.

算法如下:

#defineFREE-1

intn,r;

/*使元素有序*/

intE[MaxN]={0,0,1,1,1};

intP[MaxN];

charused[MaxN];

voidpermute(intpos)

{

inti;

/*如果已选了r个元素了,则打印它们*/

if(pos==r){

for(i=0;i

cout<

cout<

return;

}

/*标记下我们排列中的以前的元素表明是不存在的*/

P[pos]=FREE;

for(i=0;i

/*如果第I个元素没有用过,并且与先前的不同*/

if(!

used[i]&&E[i]!

=P[pos]){

/*使用这个元素*/

used[i]++;

/*选择现在元素的位置*/

P[pos]=E[i];

/*递归搜索*/

permute(pos+1);

/*恢复递归前的值*/

used[i]--;

}

}

相关习题

UVA10098GeneratingFast,SortedPermutations

组合:

从n个不同元素中取r个不重复的元素组成一个子集,而不考虑其元素的顺序,称为从n个中取r个的无重组合,例如OR={1,2,3,4},n=4,r=3则无重组合为:

{1,2,3};{1,2,4};{1,3,4};{2,3,4}.

算法如下:

intn,r;

intC[5];

charused[5];

voidcombine(intpos,inth)

{

inti;

/*如果已选了r个元素了,则打印它们*/

if(pos==r){

for(i=0;i

cout<

cout<

return;

}

for(i=h;i<=n-r+pos;i++)/*对于所有未用的元素*/

if(!

used[i]){

/*把它放置在组合中*/

C[pos]=i;

/*使用该元素*/

used[i]++;

/*搜索第i+1个元素*/

combine(pos+1,i+1);

/*恢复递归前的值*/

used[i]--;

}

}

相关问题:

Ural1034Queensinpeacefulposition

可重组合:

类似于可重排列。

[例]给出空间中给定n(n<10)个点,画一条简单路径,包括所有的点,使得路径最短。

解:

这是一个旅行售货员问题TSP。

这是一个NP问题,其实就是一个排列选取问题。

算法如下:

intn,r;

charused[MaxN];

intp[MaxN];

doublemin;

voidpermute(intpos,doubledist)

{

inti;

if(pos==n){

if(dist

return;

}

for(i=0;i

if(!

used[i]){

used[i]++;

p[pos]=i;

if(dist+cost(point[p[pos-1]],point[p[pos]])

permute(pos+1,dist+cost(point[p[pos-1]],point[p[pos]]));

used[i]--;

}

}

[例]对于0和1的所有排列,从中同时选取r个元素使得0和1的数量不同。

解这道题很简单,其实就是从0到2^r的二元表示。

算法如下:

voiddfs(intpos)

{

if(pos==r)

{

for(i=0;i

cout<

return;

}

p[pos]=0;

dfs(pos+1);

p[pos]=1;

dfs(pos+1);}

相关问题:

Ural

1005Stonepile

1060FlipGame

1152TheFalseMirrors

[例]找最大团问题。

一个图的团,就是包括了图的所有点的子图,并且是连通的。

也就是说,一个子图包含了n个顶点和n*(n-1)/2条边,找最大团问题是一个NP问题。

算法如下:

#defineMaxN50

intn,max;

intpath[MaxN][MaxN];

intinClique[MaxN];

voiddfs(intinGraph[])

{

inti,j;

intGraph[MaxN];

if(inClique[0]+inGraph[0]<=max)return;

if(inClique[0]>max)max=inClique[0];

/*对于图中的所有点*/

for(i=1;i<=inGraph[0];i++)

{

/*把节点放置到团中*/

++inClique[0];

inClique[inClique[0]]=inGraph[i];

/*生成一个新的子图*/

Graph[0]=0;

for(j=i+1;j<=inGraph[0];j++)

if(path[inGraph[i]][inGraph[j]])

Graph[++Graph[0]]=inGraph[j];

dfs(Graph);

/*从团中删除节点*/

--inClique[0];}

}

intmain()

{

intinGraph[MaxN];

inti,j;

cin>>n;

while(n>0)

{

for(i=0;i

for(j=0;j

cin>>path[i][j];

max=1;

/*初始化*/

inClique[0]=0;

inGraph[0]=n;

for(i=0;i

dfs(inGraph);

cout<

cin>>n;

}

return0;}

尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手。

由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论。

以在n个数中选取m(0

1.首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止。

2.从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m。

很明显,上述方法是一个递归的过程,也就是说用递归的方法可以很干净利索地求得所有组合。

下面是递归方法的实现:

///求从数组a[1..n]中任选m个元素的所有组合。

///a[1..n]表示候选集,n为候选集大小,n>=m>0。

///b[1..M]用来存储当前组合中的元素(这里存储的是元素下标),

///常量M表示满足条件的一个组合中元素的个数,M=m,这两个参数仅用来输出结果。

voidcombine(inta[],intn,intm,intb[],constintM)

{

for(inti=n;i>=m;i--)//注意这里的循环范围

{

b[m-1]=i-1;

if(m>1)

combine(a,i-1,m-1,b,M);

else//m==1,输出一个组合

{

for(intj=M-1;j>=0;j--)

cout<

cout<

}

}

}

因为递归程序均可以通过引入栈,用回溯转化为相应的非递归程序,所以组合问题又可以用回溯的方法来解决。

为了便于理解,我们可以把组合问题化归为图的路径遍历问题,在n个数中选取m个数的所有组合,相当于在一个这样的图中(下面以从1,2,3,4中任选3个数为例说明)求从[1,1]位置出发到达[m,x](m<=x<=n)位置的所有路径:

1234

234

34

上图是截取n×n右上对角矩阵的前m行构成,如果把矩矩中的每个元素看作图中的一个节点,我们要求的所有组合就相当于从第一行的第一列元素[1,1]出发,到第三行的任意一列元素作为结束的所有路径,规定只有相邻行之间的节点,并且下一行的节点必须处于上一行节点右面才有路径相连,其他情况都无路径相通。

显然,任一路径经过的数字序列就对应一个符合要求的组合。

下面是非递归的回溯方法的实现:

///求从数组a[1..n]中任选m个元素的所有组合。

///a[1..n]表示候选集,m表示一个组合的元素个数。

///返回所有排列的总数。

intcombine(inta[],intn,intm)

{

m=m>n?

n:

m;

int*order=newint[m+1];

for(inti=0;i<=m;i++)

order[i]=i-1;//注意这里order[0]=-1用来作为循环判断标识

intcount=0;

intk=m;

boolflag=true;//标志找到一个有效组合

while(order[0]==-1)

{

if(flag)//输出符合要求的组合

{

for(i=1;i<=m;i++)

cout<

cout<

count++;

flag=false;

}

order[k]++;//在当前位置选择新的数字

if(order[k]==n)//当前位置已无数字可选,回溯

{

order[k--]=0;

continue;

}

if(k

{

order[++k]=order[k-1];

continue;

}

if(k==m)

flag=true;

}

delete[]order;

returncount;

}

下面是测试以上函数的程序:

intmain()

{

constintN=4;

constintM=3;

inta[N];

for(inti=0;i

a[i]=i+1;

//回溯方法

cout<

//递归方法

intb[M];

combine(a,N,M,b,M);

return0;

}

由上述分析可知,解决组合问题的通用算法不外乎递归和回溯两种。

在针对具体问题的时候,因为递归程序在递归层数上的限制,对于大型组合问题而言,递归不是一个好的选择,这种情况下只能采取回溯的方法来解决。

n个数的全排列问题相对简单,可以通过交换位置按序枚举来实现。

STL提供了求某个序列下一个排列的算法next_permutation,其算法原理如下:

1.从当前序列最尾端开始往前寻找两个相邻元素,令前面一个元素为*i,后一个元素为*ii,且满足*i<*ii;

2.再次从当前序列末端开始向前扫描,找出第一个大于*i的元素,令为*j(j可能等于ii),将i,j元素对调;

3.将ii之后(含ii)的所有元素颠倒次序,这样所得的排列即为当前序列的下一个排列。

其实现代码如下:

template

boolnext_permutation(BidirectionalIteratorfirst,BidirectionalIteratorlast)

{

if(first==last)returnfalse;//空範圍

BidirectionalIteratori=first;

++i;

if(i==last)returnfalse;//只有一個元素

i=last;//i指向尾端

--i;

for(;;)

{

BidirectionalIteratorii=i;

--i;

//以上,鎖定一組(兩個)相鄰元素

if(*i<*ii)//如果前一個元素小於後一個元素

{

BidirectionalIteratorj=last;//令j指向尾端

while(!

(*i<*--j));//由尾端往前找,直到遇上比*i大的元素

iter_swap(i,j);//交換i,j

reverse(ii,last);//將ii之後的元素全部逆向重排

returntrue;

}

if(i==first)//進行至最前面了

{

reverse(first,last);//全部逆向重排

returnfalse;

}

}

}

下面程序演示了利用next_permutation来求取某个序列全排列的方法:

intmain()

{

intia[]={1,2,3,4};

vectoriv(ia,ia+sizeof(ia)/sizeof(int));

copy(iv.begin(),iv.end(),ostream_iterator(cout,""));

cout<

while(next_permutation(iv.begin(),iv.end()))

{

copy(iv.begin(),iv.end(),ostream_iterator(cout,""));

cout<

}

return0;

}

注意:

上面程序中初始序列是按数值的从小到大的顺序排列的,如果初始序列无序的话,上面程序只能求出从当前序列开始的后续部分排列,也就是说next_permutation求出的排列是按排列从小到大的顺序进行的。

1。

最近一直在考虑从m个数里面取n个数的算法。

最容易理解的就是递归,但是其效率,实在不能使用。

一直找寻中,今日得果

2。

算法来源与互联网

组合算法

本程序的思路是开一个数组,其下标表示1到m个数,数组元素的值为1表示其下标

代表的数被选中,为0则没选中。

首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。

然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为

“01”组合,同时将其左边的所有“1”全部移动到数组的最左端。

当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得

到了最后一个组合。

例如求5中选3的组合:

11100//1,2,3

11010//1,2,4

10110//1,3,4

01110//2,3,4

11001//1,2,5

10101//1,3,5

01101//2,3,5

10011//1,4,5

01011//2,4,5

00111//3,4,5

全排列算法

从1到N,输出全排列,共N!

条。

分析:

用N进制的方法吧。

设一个N个单元的数组,对第一个单元做加一操作,满N进

一。

每加一次一就判断一下各位数组单元有无重复,有则再转回去做加一操作,没

有则说明得到了一个排列方案。

//////////////////////////////////////////////////////

递归算法

全排列是将一组数按一定顺序进行排列,如果这组数有n个,那么全排列数为n!

个。

现以{1,2,3,4,5}为

例说明如何编写全排列的递归算法。

1、首先看最后两个数4,5。

它们的全排列为45和54,即以4开头的5的全排列和以5开头的4的全排列。

由于一个数的全排列就是其本身,从而得到以上结果。

2、再看后三个数3,4,5。

它们的全排列为345、354、435、453、534、543六组数。

即以3开头的和4,5的全排列的组合、以4开头的和3,5的全排列的组合和以5开头的和3,4的全排列的组合.

从而可以推断,设一组数p={r1,r2,r3,...,rn},全排列为perm(p),pn=p-{rn}。

因此perm(p)=r1perm(p1),r2perm(p2),r3perm(p3),...,rnperm(pn)。

当n=1时perm(p}=r1。

为了更容易理解,将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1