循環和遞歸解決全排列問題

首先先幫大家瞭解一下數字的全排列,數字的全排列在我看來就是把給出的若干數字按照不同的順序就行排序,比如說:{1,2,3}這三個數字就行全排列我們可以排出{1,2,3},{1,3,2},{2,1,3},{2,3,1},{3,2,1},{3,1,2}

       方法一:因爲給的數值比較小所以我們可以用一個笨辦法(同時也是一個容易理解的方法)解決。也就是枚舉法。

int main()
{
	for(int a=1;a<4;a++)
	{
		for(int b=1;b<4;b++)
		{
			for(int c=1;c<4;c++)
			{
				if(a!=b&&b!=c&&a!=c)//排列特徵就是互相不相等
				{
					printf("%d %d %d\n",a,b,c);
				}
			}
		}
	}
}

       大家可以看出我們是用多重嵌套的方法列出所有可能,然後篩選出我們要的那部分,可以說枚舉是大家都能理解的,但是時間複雜度也高,同時實用性差一點,因爲這個題我們只給了3個數我們寫了三個循環,如果給10個數我們就要寫10個循環,時間複雜度也太高了。雖說枚舉可以解決但這道題不提倡使用。

      那麼問題來了,如何輸入一個數實現1~n的全排列呢?

      我們可以這樣想,比如說說我們現在有三個盒子編號分別是1,2,3。然後我們手裏有三張撲克牌分別是1,2,3。現在我們要把撲克牌放入箱子裏面並且每一個盒子只能放一張撲克牌,那麼有多少種方法呢?

     首先我們來到1號盒子面前,這時候我們心想該放哪張撲克牌呢?不如我們就規定一個順序:無論走到哪個箱子我們都按照1,2,3的順序放撲克牌。也就是先放1號撲克牌,再放2號撲克牌,再放3號撲克牌。

首先我們來到了1號箱子,按照我們的規定先把1號撲克牌放入1號箱子

然後我們來到了2號箱子,按照規定我們應該放1號撲克牌,但是手裏現在只有2號和3號撲克牌,所有我們往2號箱子放2號撲克牌。 接着來到3號箱子,按照規定應該先放1號撲克牌再放2號撲克牌,但現在只有3號撲克牌所有我們把3號撲克牌放入3號箱子。

     現在我們撲克牌放完了,這時箱子裏的數字就是我們排列出來的序列。也就是{1,2,3}但是現在還沒有結束,當我們產生了一種序列後我們需要返回。

     首先我們返回3號箱子,取出3號撲克牌後看看能不能放入其它撲克牌從而產生新的序列,當取出3號撲克牌後我們手裏只有一張3號撲克牌所以沒有別的選擇,所以再次返回。

     返回到2號箱子,當取出2號撲克牌後手裏就有兩張牌了,這時候按照之前約定的順序往2號箱子放入3號撲克牌(因爲2號撲克牌之前已經放過了所以這次放3號撲克牌),放好以後向後走一步把僅剩的一張2號牌放入3號箱子中。這時候我們就產生了一種新的序列也就是{1,3,2}。

     接下來我們只需要按照上述方法模擬出{2,1,3},{2,3,1},{3,2,1},{3,1,2}就好了,現在我們基本的邏輯也就理解了,開始解決代碼問題。

#include<iostream>
using namesqace std;
int book[10], a[10],n;//全局變量定義數組默認爲0;所以不用再賦初始值。數組a代表盒子,數組book用來標記撲克牌有沒有用過,因爲默認爲0,所以book[i]==0說明沒用過,book[i]==1說明使用過
void test(int step)//step表示現在正處在第幾個盒子
{
	if(step==n+1)//一共有n個盒子,如果現在處於n+1個盒子說明前n個盒子已經都放入了卡片,所以我們執行打印
	{
		for(int i=1;i<=n;i++)
		{
			printf("%d ",a[i]);
		}
		printf("\n");
		return;//得到一個序列以後返回上一步
	}
		for(int i=1;i<=n;i++)//i代表我們撲克牌的編號
		{
			//首先判斷撲克牌i號撲克牌在不在手上
			if(book[i]==0)//book[1]==0說明在手上,說明有說到過
			{
				a[step]=i;//因爲現在處理的是第step盒子,所以把手上的這張放入該盒子中
				book[i]=1;//因爲i代表撲克牌編號,並且現在i號撲克牌放入了盒子中,所以i號撲克牌後面不能再用了,所以把book[i]=1;
				test(step+1);//按照邏輯盒子放入撲克牌後我們處理下一個盒子。遞歸調用
				book[i]=0;//放完以後要收回撲克牌從而進行下一次排列
			}
		}
	return;
}
int main()
{
	scanf("%d",&n);//輸入1~9之間的數字
	test(1);
}

 我們上述代碼通過遞歸調用實現了9以內的數字全排列,但有同學想問了,如果要求實現任意數字的全排列怎麼辦?

 要對數組中的數字進行全排列有這樣一個經典的例題:

設R={r1,r2,...,rn}(r後面是下標)是要進行排列的n個元素,Ri=R-{ri}.集合X中元素的全排列記爲perm(X).(ri)perm(X)表示在全排列perm(x)的每一個排列前加上前綴ri得到的排列.R的全排列可歸納定義如下:
當n=1時,perm(R)=(r),其中r是集合中唯一的元素;
當n>1時,perm(R)由(r1)perm(R1),(r2)perm(R2),...(rn)perm(Rn)構成.

要解決這個題首先大家明確一下Ri=R-{ri}這個意思。這代表在集合中除去i元素,比如,R={ri,r2,r3},此時R1=R-{r1}={r2,r3}。

下面給大家看看我自己的思路理解,寫的不好比較醜陋大家多多包涵

下面上代碼

#include<iostream>
using namespace std;
void pai(int arr[],int k,int m)
{
	int j;
	if(k==m)//當l==m證明規模爲0執行打印
	{
		for(j=0;j<=m;j++)
		{
			printf("%d ",arr[j]);
		}
		printf("\n");
	}
	else
	{
	for(j=k;j<=m;j++)
    {
		swap(arr[j],arr[k]);//交換下標j和k對應的值
		pai(arr,k+1,m);//規模減小,再次處理小規模
		swap(arr[j],arr[k]);//再次交換回來
	}
	}

}
int main()
{
	int arr[3]={1,2,3};
	pai(arr,0,2);
}

    如果大家看不懂代碼請認真的看一下思路圖,這就是一個交換和回退的過程。希望大家通過這個題理解遞歸的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章