1. 程式人生 > >遞迴詳解(STL字典序和非字典序全排列 )。

遞迴詳解(STL字典序和非字典序全排列 )。

遞迴的定義就是函式本身呼叫自己,定義看起來很簡單,我感覺在具體問題中是很難實現的,首先這個思想也是很難懂的,具體的過程也是抽象的,對於ACM新手是很難搞懂的。

接下來,我就幾個例子來講一下我的見解。

在數學與電腦科學中,遞迴(Recursion)是指在函式的定義中使用函式自身的方法。實際上,遞迴,顧名思義,其包含了兩個意思:,這正是遞迴思想的精華所在。

遞迴:你打開面前這扇門,看到屋裡面還有一扇門。你走過去,發現手中的鑰匙還可以開啟它,你推開門,發現裡面還有一扇門,你繼續開啟它。若干次之後,你打開面前的門後,發現只有一間屋子,沒有門了。然後,你開始原路返回,每走回一間屋子,你數一次,走到入口的時候,你可以回答出你到底用這你把鑰匙打開了幾扇門。

迴圈:你打開面前這扇門,看到屋裡面還有一扇門。你走過去,發現手中的鑰匙還可以開啟它,你推開門,發現裡面還有一扇門(若前面兩扇門都一樣,那麼這扇門和前兩扇門也一樣;如果第二扇門比第一扇門小,那麼這扇門也比第二扇門小,你繼續開啟這扇門,一直這樣繼續下去直到開啟所有的門。但是,入口處的人始終等不到你回去告訴他答案。

遞迴問題必須可以分解為若干個規模較小,與原問題形式相同的子問題。找到 原問題與子問題的 的連線點,繼續往 下一狀態進行操作,如果 說 原問題有什麼 操作 ,那麼 子問題 也是會 進行相同的操作,這時,就要對狀態的改變 和要進行的操作 要進行仔細的 判斷 ,是否可以遞迴,並進行 操作 的 思考模擬 ,是否可行 。

對初試狀態 操作 要明確,然後你才能保證 每一個 子問題,都可以按照 正確的

 

遞迴其實就是 遞與歸 ,先遞去,然後再 歸來。

看一下 下面 這個 題目:

 

全排列:這類問題應該是ACM新手最頭疼的問題,用到遞歸回溯,知識點,很快你就進入了迷的狀態。對於 abc 這個 字串,所有的全排列 是 abc acb bac bca cba cab 看樣子 是固定一位字元,將後面的 字元 與它 換換 位置 ,如果後面還有的話,就繼續固定 下一個字元,直到 最後剩下一個 的 時候 ,就去回溯,執行遞去的每步操作 ,比如 123  這個字串,先進 最外層的 ,就是 交換 第一位的 ,發現 可以再進一層 就  是 交換 第二位的 字元 ,再進 ,發現 是最後一個 了 ,就退出 ,回溯 ,進行 第二層 交換的操作,完了後 ,退回到 第一層 ,進行 第一位 的操作,就這樣 一直操作,直到第一位 的字元 已經 過了 a b c 一遍後 ,就退回 主函式 , 在這 過程中 ,可以 進行 操作 ,輸出 每個 序列 。

 

 

輸出字串的全排列:


#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105; 
int count=0;
void all_pailie(char *A,int k,int n)
{
	int i;
	if(k==n)
		{
			count++;
			for(i=0;i<=n;i++)
				cout<<A[i]<<' ';
			cout<<endl;		
		}
	else
	{
		for(i=k;i<=n;i++)
		{
			swap(A[i],A[k]);
			all_pailie(A,k+1,n);
			swap(A[i],A[k]);	
		}
	}	
}
int main()
{
	int i;
	char n[maxn];
	cin>>n;
	all_pailie(n,0,strlen(n)-1);
	cout<<count<<endl;	
	return 0;
}

很明顯,這個程式碼是有BUG的,如果你的字串中有重複的字元的話,程式也會看作是不同的字元去排列,如果題目有具體的要求,這個解法肯定是行不通的,還要進行去重操作。

接下來,看看 去重操作:

程式碼:

#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105; 
int count=0;
char a[maxn];
bool check(int x,int y)
{
	for(int i=x;i<y;i++)
		if(a[i]==a[y])
			return true;
	return false;
}
void all_pailie(char *A,int k,int n)
{
	int i;
	if(k==n)
		{
			count++;
			for(i=0;i<=n;i++)
				cout<<A[i]<<' ';
			cout<<endl;		
		}
	else
	{
		for(i=k;i<=n;i++)
		{
			if(!check(k,i))
			{
				swap(A[i],A[k]);
				all_pailie(A,k+1,n);
				swap(A[i],A[k]);	
			}
		}
	}	
}
int main()
{
	int i;
	cin>>a;
	all_pailie(a,0,strlen(a)-1);
	cout<<count<<endl;	
	return 0;
}

 

數字的全排列:輸入 整數,輸出他的全排列 ,這個應該不會出重複的問題。就是一個 整數n ,從1 到 n的 排列。


#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn=105; 
int count=0;
void all_pailie(int *A,int k,int n)
{
	int i;
	if(k==n-1)
		{
			count++;
			for(i=0;i<n;i++)
				cout<<A[i]<<' ';
			cout<<endl;		
		}
	else
	{
		for(i=k;i<n;i++)
		{
			swap(A[i],A[k]);
			all_pailie(A,k+1,n);
			swap(A[i],A[k]);	
		}
	}	
}
int main()
{
	int i;
	int n;
	int a[maxn];
	cin>>n;
	for(i=0;i<n;i++)
		a[i]=i+1;
	all_pailie(a,0,n);
	cout<<count<<endl;	
	return 0;
}

對於STL 的解法 就更容易了 ,就直接 呼叫函式 就可以了 。

程式碼: 對於這個 函式名 ,是不能變的,一變就會報錯,這種解法,可以去重,還可以 按字典序來排。集很多功能於一體 的簡單寫法。,但還是 需要 每次先使陣列 是遞增的順序才可以,用sort 就可以。

這是 去重 和 遞增的 字典序。用 next_permutation

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=105;
void permutation(char* str,int length)
{
	sort(str,str+length);       
	do
	{
		for(int i=0;i<length;i++)
			cout<<str[i]<<' ';
		cout<<endl;
	}while(next_permutation(str,str+length));

}
int main()
{
	char str[maxn];    //字串 
	cin>>str;
	permutation(str,strlen(str));  //函式 引數是函式名,和長度     
	return 0;
}

當要求 遞減的時候 ,就需要先把陣列降序,才可以使用prev_permutation,就需要 prev_permutation

程式碼:

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=105;

bool cmp(int a,int b)
{
	return a>b;
}

void permutation(char* str,int length)
{
	sort(str,str+length,cmp);       
	do
	{
		for(int i=0;i<length;i++)
			cout<<str[i]<<' ';
		cout<<endl;
	}while(prev_permutation(str,str+length));

}
int main()
{
	char str[maxn];    //字串 
	cin>>str;
	permutation(str,strlen(str));  //函式 引數是函式名,和長度     
	return 0;
}

這樣減少了時間去敲 遞迴的 程式碼了,還要考慮去重 和 字典序和

非字典序。

來道題:

題面:Bob and math problem HDU - 5055

Recently, Bob has been thinking about a math problem.
There are N Digits, each digit is between 0 and 9. You need to use this N Digits to constitute an Integer.
This Integer needs to satisfy the following conditions:

  • 1. must be an odd Integer.
  • 2. there is no leading zero.
  • 3. find the biggest one which is satisfied 1, 2.


Example:
There are three Digits: 0, 1, 3. It can constitute six number of Integers. Only "301", "103" is legal, while "130", "310", "013", "031" is illegal. The biggest one of odd Integer is "301".

Input

There are multiple test cases. Please process till EOF.
Each case starts with a line containing an integer N ( 1 <= N <= 100 ).
The second line contains N Digits which indicate the digit a1,a2,a3,⋯,an.(0≤ai≤9)a1,a2,a3,⋯,an.(0≤ai≤9).

Output

The output of each test case of a line. If you can constitute an Integer which is satisfied above conditions, please output the biggest one. Otherwise, output "-1" instead.

Sample Input

3
0 1 3
3
5 4 2
3
2 4 6

Sample Output

301
425
-1

程式碼:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=105;
char a[maxn];
char b[maxn];
bool cmp(char a,char b)
{
	return a>b;
}
bool permutation(char *str,int length)
{
	sort(str,str+length,cmp);
	do
	{
		if(str[length-1]%2==1&&str[0]!='0')
		{
			for(int i=0;i<length;i++)
				cout<<str[i];
				cout<<endl;
				return true;	
		}
	}while(prev_permutation(str,str+length));	
	return false;
}
int main()
{
	int num;
	while(cin>>num)
	{
		memset(a,0,sizeof(a));
		memset(b,0,sizeof(b));
		for(int i=0;i<num;i++)
			cin>>b[i];
		strcpy(a,b);
		if(!permutation(a,strlen(a)))
			cout<<"-1"<<endl;
	//	else
	//		cout<<endl;		
	}
	return 0;
}

原理就是上面的那個 函式 。是不是 很簡單 。希望大家 多學習。