11 演習 2.8 全順列出力(プログラミング問題)【PTA 浙江大学編「データ構造(第 2 版)」課題集】
1. 元のタイトルへのリンク
2. トピックの説明
元nnを出力するプログラムを書いてくださいn個の正の整数の全順列 (n < 10 n<10n<10 )、9 つのテスト ケースに合格 (つまりnn1 から 9 までのn ) nnn が徐々に増加したときのプログラムの実行時間。
入力形式:
入力は正の整数nnを与えますn(< 10 <10<10)。
出力フォーマット:
1~nnを出力nの完全順列。各順列は、数字の間にスペースを入れずに 1 行を占めます。配列の出力順序は、辞書式順序、つまり、シーケンスa 1 、a 2 、⋯、 an { a_1、a_2、\cdots、a_n } です。a1、a2、⋯、an順番にランク付けb 1 , b 2 , ⋯ , bn { b_1, b_2, \cdots, b_n }b1、b2、⋯、bn以前、kk が存在する場合k使得a 1 = b 1 , ⋯ , ak = bk a_1=b_1, \cdots, a_k=b_ka1=b1、⋯、ak=bkそしてak + 1 < bk + 1 a_{k+1}<b_{k+1}ak + 1<bk + 1。
入力サンプル:
3
出力例:
123
132
213
231
312
321
3. 回答を参照
一つ答えて
#include<stdio.h>
int a[9];
void arrange(int m, int n){
int i,j,num;
if(m==n){
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(a[i]==a[j])
return;
for(i=0;i<n;i++)
printf("%d",a[i]);
printf("\n");
}
else{
for(num=1;num<=n;num++){
a[m]=num;
arrange(m+1, n);
}
}
}
int main(){
int n;
scanf("%d", &n);
arrange(0, n);
return 0;
}
2つ答えて
#include<stdio.h>
int a[9];
void arrange(int m, int n){
int i,j,num;
if(m==n){
for(i=0;i<n;i++)
printf("%d",a[i]);
printf("\n");
}
else{
for(num=1;num<=n;num++){
for(j=0;j<m;j++)
if(a[j]==num)break;
if(j==m){
a[m]=num;
arrange(m+1, n);
}
}
}
}
int main(){
int n;
scanf("%d", &n);
arrange(0, n);
return 0;
}
3つ答えて
#include <stdio.h>
int visited[10]={
0};
int list[10];
void dfs(int n,int m){
int i;
if(m==n+1){
for(int i=1;i<=n;i++)
printf("%d",list[i]);
printf("\n");
}
else{
for(i=1;i<=n;i++){
if(!visited[i]){
list[m]=i;
visited[i]=1;
dfs(n,m+1);
visited[i]=0;
}
}
}
}
int main(){
int n;
scanf("%d", &n);
dfs(n,1);
return 0;
}
4つ答えて
#include <stdio.h>
int a[10];
void shift( int l, int i);
void shiftb( int l, int i );
void Per( int l, int r );
int main(){
int n, i;
scanf("%d", &n);
for (i=0; i<=n; i++) a[i] = i;
Per(1, n);
return 0;
}
void shift( int l, int i){
int j, t=a[i];
for (j=i; j>l; j--)
a[j]=a[j-1];
a[l] = t;
}
void shiftb( int l, int i ){
int j, t=a[l];
for (j=l; j<i; j++)
a[j] = a[j+1];
a[i] = t;
}
void Per( int l, int r ){
int i;
if (r==l) {
for (i=1; i<=r; i++) printf("%d", a[i]);
printf("\n");
}
else {
for (i=l; i<=r; i++) {
shift(l, i);
Per(l+1, r);
shiftb(l, i);
}
}
}
4. 問題解決のアイデア
nnこの質問を出力する前にn個の正の整数の全順列 (n < 10 n<10n<10 )、nnnが 3 などの固定値の場合、完全な配置は 3 サイクルで実現でき、実装方法は次のとおりです。
#include<stdio.h>
int main(){
int i,j,k;
for(i=1;i<=3;i++){
for(j=1;j<=3;j++){
for(k=1;k<=3;k++){
if(i!=j&&j!=k&&i!=k)
printf("%d%d%d\n",i,j,k);
}
}
}
return 0;
}
しかし、この質問のnnnは変数で、ループで実現するとループの層数が不定なので、再帰で実現できます。ループの実装から 3 つのことがわかります。
-
ループの各層は順番に番号を選択し、呼び出し再帰は呼び出しの各層に対して順番に番号を自然に選択します。
-
出力はループが最終層に到達した時点で出力され、再帰関数の呼び出しも当然最終層まで再帰的に呼び出されて出力されます。
//m记录当前层级,若当前层级m到达最大层级n,输出排列完毕之后的数组 if(m==n){ for(i=0;i<n;i++) printf("%d",a[i]); printf("\n"); }
同時に、完全配置の再帰関数には、少なくとも 2 つのパラメーターが必要です: 1. 現在の呼び出しのレベル; 2. 最大レベル、つまり入力nnん。
//排列permutation void Per( int now, int n )
-
出力の各桁は繰り返されません。各桁の出力を繰り返さないようにしたい場合は、次の 4 つのアルゴリズムがあります。
- アルゴリズム 1: 上記のトリプル ループは、出力する前に各桁の再現性をチェックするためのもので、この質問ではnnnは不確かです, つまり、数字の数は不確かです. 出力する前に各桁の再現性を確認する場合は、二重ループを使用して、各桁を他の桁の各桁と比較する必要があります. アルゴリズムの最後の 1 つは、テストポイントがタイムアウトします。
- アルゴリズム 2: 最適化 アルゴリズム 1、最後に数値の再現性をチェックする代わりに、各レイヤーに入るときにチェックします。前の例の 3 重ループからわかるように、配置の各層はすべての数字をトラバースします. たとえば、ループの最初の層は 1 から 3 をトラバースし、ループの 2 層目も 1 から 3 をトラバースします. 現在のレベルで選択された番号が前のレベルに表示されているかどうかを確認します。現在の番号が前の位置に表示されている場合は、その番号をスキップします。現在の番号が前の位置に表示されていない場合は、番号を次の位置に配置します。配列の現在のレベル。
- アルゴリズム 3: アルゴリズム 2 をさらに最適化し、トラバーサル法を使用して繰り返し性を確認せず、使用された各番号をマークし、選択した番号が使用可能かどうかをマークで判断します。
- アルゴリズム 4: 最初に配列を作成し
int a[10];
、それを初期化しfor (i=0; i<=n; i++) a[i] = i;
、配列要素の位置を調整し、配列を完全に配置します。これは、配列要素の値が 1 から 9 まで決定されており、異なる値の繰り返しがないためです。数字。これは、この質問に対する参照回答で使用されているアプローチです。
5. 詳細回答
詳細な回答
アルゴリズムは最後のテスト ポイントでタイムアウトします。
各位置の可能な値を列挙し、すべての可能な順列を生成し、1 から n までの整数の完全な順列を実現し、配列を使用して各位置に値を格納し、出力する前に数値が繰り返されるかどうかを確認し、順列を満たす繰り返し要素のない出力。
具体的な分析は次のとおりです。
- 生成された各順列を格納するために、グローバル配列が定義されます
int a[9];
。 void arrange(int m, int n);
順列を生成する再帰関数を定義します。arrange
関数のパラメータは、m
現在生成されている配列の位置を示し、n
生成される配列の長さを示します。- m=n のときは、完全な配列が生成されたことを意味します.このとき、配列に繰り返し要素があるかどうかを確認し、ある場合はそれを返し、ない場合は配列を出力してラップする必要があります。
- m<n の場合は、配列を生成し続ける必要がありますが、このとき、現在位置 m を 1 から n まで 1 つずつ埋めていき、次の位置の配列を再帰的に生成してみてください。
main
関数内では、まず生成する配列の長さを読み込みn
、arrange
関数を呼び出して初期位置を設定します0
。
#include<stdio.h>
int a[9];
//m是当前层级,n是最大层级
void arrange(int m, int n){
int i,j,num;
//数组排列到n-1位置就排列完了,m==n排列完成输出
if(m==n){
//二重循环检查每位数字的重复性
for(i=0;i<n;i++)
for(j=i+1;j<n;j++)
if(a[i]==a[j])//如果有数字重复则不输出
return;
//如果程序执行到这里,则没有数字重复输出结果
for(i=0;i<n;i++)
printf("%d",a[i]);
printf("\n");
}
else{
//每一层都遍历所有的数字1至n
for(num=1;num<=n;num++){
a[m]=num;
arrange(m+1, n);//递归方程计算下一个层级的数字
}
}
}
int main(){
int n;
scanf("%d", &n);
arrange(0, n);
return 0;
}
2つの詳細な説明に答える
Algorithm 1に基づいて最適化され、各レイヤーで番号を選択するときに、前の番号をトラバースして、番号が繰り返されているかどうかを確認します。
#include<stdio.h>
int a[9];
//m是当前层级,n是最大层级
void arrange(int m, int n){
int i,j,num;
//数组排列到n-1位置就排列完了,m==n排列完成输出
if(m==n){
for(i=0;i<n;i++)
printf("%d",a[i]);
printf("\n");
}
else{
//每一层都遍历所有的数字1至n
for(num=1;num<=n;num++){
//检查当前选择的数字num是否在前面的位置j出现过
for(j=0;j<m;j++)
//如果当前选择的数字num在前面的位置j出现过则退出此循环,遍历下一个数字
if(a[j]==num)break;
//结束上面的循环之后,若j==m,即当前选择的数字num在之前没有出现过
if(j==m){
a[m]=num;
arrange(m+1, n);//递归方程计算下一个层级的数字
}
}
}
}
int main(){
int n;
scanf("%d", &n);
arrange(0, n);
return 0;
}
3つの詳しい説明に答える
深さ優先検索 (DFS)。すべての組み合わせを反復し、各レイヤーで番号を選択するときにマーキングして、番号が使用されているかどうかを確認します。
配列は、int visited[10]
番号が使用されたかどうかを記録します。これはvisited[i] = 0
、番号 i が使用されていないことを示し、visited[i] = 1
番号 i が使用されたことを示します。
配列はint list[10];
結果の順列を表し、ここで は順列の位置 1 の数値list[i]
を表します。i
関数 dfs(n, step) は、検索が生成された配列のステップ位置にある場合の状況を示します。ここで、n は配列の長さ (つまり 1 ~ n) を示し、step は現在の検索位置を示します。
step = n+1 のとき、完全な配置が生成されているので、そのまま出力するだけです。
それ以外の場合は、1~n の番号を現在の位置の番号として順番に使用し、訪問した配列の番号を使用済みとしてマークし、次の位置を再帰的に検索します。現在の場所に戻るときは、訪問した配列のマークを外す必要があります。
#include <stdio.h>
int visited[10]={
0}; //做标记,数组下标对应相应的数字,没用过记作 0, 用过后记作 1
int list[10];
//m是当前层级,n是最大层级
void dfs(int n,int m){
int i;
//m==n+1时,n个层级排列完毕
if(m==n+1){
for(int i=1;i<=n;i++)
printf("%d",list[i]);
printf("\n");
}
else{
//每一层都遍历所有的数字1至n
for(i=1;i<=n;i++){
if(!visited[i]){
//如果选择的数字没用过
list[m]=i; //把它存入数组对应层级的位置
visited[i]=1; //标记为已使用
dfs(n,m+1); //进入下一层
visited[i]=0; //取消标记
}
}
}
}
int main(){
int n;
scanf("%d", &n);
dfs(n,1); //代表从第一层开始搜索
return 0;
}
詳細な回答 4
配列要素の位置を調整し、配列を完全に配置します。
具体的な分析は次のとおりです。
10
長さ の整数配列を定義しa[]
、 と の 3 つの関数を宣言shift()
しshiftb()
ますPer()
。- 関数では
main()
、最初に整数を入力しn
、次にループを使用してa[]
配列を 1 から n までの整数に初期化します。 - 次に、
Per()
関数を呼び出します。 - この関数では
Per()
、左端と右端が同じ場合、現在の完全な配置から1
to までが出力されます。n
- それ以外の場合は、左のエンドポイントから
l
右のエンドポイントまでのr
範囲ごとにi
、次の操作を行います。shift()
関数を呼び出して、配列a[]
内のi
th 要素をl
左エンドポイントの位置に移動します。l+1
到達した範囲でr
再帰的に動作します。shiftb()
関数を呼び出して、a[]
配列を元の状態に復元します。
#include <stdio.h>
int a[10];//程序中用不到a[0],只使用a[1]至a[9]
void shift( int l, int i);
void shiftb( int l, int i );
void Per( int l, int r );
int main(){
int n, i;
scanf("%d", &n);
for (i=0; i<=n; i++) a[i] = i;
Per(1, n);
return 0;
}
//对数组元素进行交换,将数组a[]中第i个元素移动到左端点l的位置
void shift( int l, int i){
int j, t=a[i];
for (j=i; j>l; j--)
a[j]=a[j-1];
a[l] = t;
}
//对数组元素进行交换,将数组a[]还原到原始状态
void shiftb( int l, int i ){
int j, t=a[l];
for (j=l; j<i; j++)
a[j] = a[j+1];
a[i] = t;
}
//排列permutation
void Per( int l, int r ){
int i;
//如果左右两端点相同,则排列完毕,输出当前的全排列
if (r==l) {
for (i=1; i<=r; i++) printf("%d", a[i]);
printf("\n");
}
else {
for (i=l; i<=r; i++) {
//将数组a[]中第i个元素移动到左端点l的位置
shift(l, i);
//每一层调用都会有一个for循环,for循环改变每一层函数调用的左边界位置的数字
Per(l+1, r);
//每一层会先改变了元素位置,内层函数执行完毕后,再把位置改变回去以便下一个循环排列
shiftb(l, i);
}
}
}
6.知識の拡大
深さ優先検索
この質問は主に再帰についてのレビューですが、深さ優先探索については後で詳しく説明しますので、ここでは簡単な紹介にとどめます。
深さ優先検索 (略して DFS) は、一般的に使用されるグラフ トラバーサル アルゴリズムであり、グラフ内のノードを深くトラバースすることによって実装されます。
具体的には、DFS アルゴリズムは、グラフ内の特定のノードから開始し、最初にそのノードを訪問し、次に隣接する未訪問のノードを選択して、すべての隣接ノードが訪問されるまで訪問を続けます。現在のノードに未訪問の隣接ノードがない場合は、前のノードに戻り、すべてのノードが訪問されるまで未訪問の隣接ノードを訪問し続けます。
DFS アルゴリズムは、再帰またはスタックを使用して実装できます。再帰的な実装を使用する場合、訪問済みの配列を使用して訪問したノードを記録し、訪問の繰り返しを防ぐことができます; スタックの実装を使用する場合、スタックの後入れ先出し (LIFO) 機能を使用して、未訪問の隣接ノードを保存できます。現在のノード ノード。