質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

3回答

2666閲覧

N個の要素を2つずつのペア,ローテーションする効率的な方法

pypy7

総合スコア15

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2020/04/21 06:44

N個の要素(1,2,・・・,N)があり(Nは68や120ぐらいの大きさの数字を想定しています),それらを重複がないように2個ずつのペアに分けたいです(例えば(1,2),(3,4),・・・,(N-1,N)のようにN/2個のペアを作りたい).

この作業を何回も繰り返したいのですが,できるかぎり違う組み合わせでペアを組みたいです.

そして入力として要素の番号と何回目の作業かを表す数字を与えたらペア相手の要素番号が返ってくるようなできるだけ計算量の小さい数式を作りたいのですが何か効率的な方法はないでしょうか.

おそらくN個の要素を2グループに分けたら簡単にローテーションできると思うのですが,それだとすべての組み合わせパターンは網羅できないかと思います.
全ての組み合わせパターンを網羅できるような方法はありますか?(またはそれに近い数の組み合わせ)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2020/04/23 02:29

> 要素の番号と何回目の作業かを表す数字を与えたらペア相手の要素番号が返ってくるようなできるだけ計算量の小さい数式 これの意味するところは,【実際に(再帰とか何とかで)頑張って指定回目までのパターンを計算せずとも,特定の要素番号のペア相手だけならばどうにかしてわかるだろう】的な話なのかな. 例えば,データが4個{0,1,2,3}のときのパターンは (0,1)(2,3) (0,2)(1,3) (0,3)(1,2) の3パターンしかないが,このパターンの並べ方の上では 「要素0番」の「k回目」の相手を求める式は f(0,k)=k と書ける. そんな感じで全パターンを「ある並べ方」に並べた状態を前提とするならば, 「要素i番」の「k回目」の相手を算出する「式」を f(i,k)=??? という形で書けないか? という話か. (全パターンを愚直に再帰で求める手順には法則性があるのだから,f(i,k)=??? という式で表すことは可能そうに思うが…)
guest

回答3

0

こんにちは。
以下ではペアの順序は無視して考えています。
例:(1,3)(2,4) = (1,3)(4,2) = (2,4)(1,3)

全て網羅するためのアルゴリズムですが、私が思いついたのは単純に再帰で求める方法でした。
順序を無視するのでペアになる片方一つを固定して、その残りの数値を分岐させ、二つずつ再帰でネストして求めるという方針です。

C

1#include <stdio.h> 2#define N 6 3 4void search(int *); 5int Index(int *,int ,int ); 6void show(int *); 7void show_array(int *); 8 9//グローバル変数 10int stock[N+1]; 11int n,k,count; 12 13 14int main(void){ 15 int i; 16 // array = [0,0,0,...,0,-1] 17 int array[N+1]={0}; 18 array[N] = -1; 19 // stock = [1,1,1,...,1,-1] 20 for(i=0;i<N;i++){stock[i] = 1;} 21 stock[N] = -1; 22 // グローバル変数の初期化 23 count = 0; 24 n = k = 0; 25 26 //入力 27 printf("繰返し回数->"); 28 scanf("%d",&k); 29 printf("要素の番号->"); 30 scanf("%d",&n); 31 32 //探索 33 search(array); 34 return 0; 35} 36 37//再帰関数 38void search(int *array){ 39 int i; 40 int index_array0 = Index(array,0,0); 41 int index_stock1 = Index(stock,1,0); 42 43 if(count >= k){ 44 return ; 45 } 46 //探索中 47 if(index_array0 != -1){ 48 //一つ目を配置 49 array[index_array0] = index_stock1 + 1; 50 stock[index_stock1] = 0; 51 52 //二つ目を配置 53 //stockの1の数だけ分岐 54 i = Index(stock,1,0); 55 do{ 56 array[index_array0 +1]=i+1; 57 stock[i] = 0; 58 59 //再探索 60 search(array); 61 62 //戻す一つ 63 stock[i] = 1; 64 array[index_array0 +1] = 0; 65 66 }while((i=Index(stock,1,i+1)) != -1); 67 //もう一つ戻す 68 stock[index_stock1] = 1; 69 array[index_array0] = 0; 70 //解 71 }else{ 72 //表示 73 count++; 74 if(count == k){ 75 show_array(array); 76 show(array); 77 } 78 } 79} 80 81 82 83/* 添え字を返す関数 */ 84// 第一引数 : 配列の先頭アドレス 85// 第二引数 : 検索対象の数値 86// 第三引数 : 検索開始インデックス 87int Index(int *p,int a,int i){ 88 int *q = p; 89 int c = 0; 90 while( (*(q+i) != -1) && (*(q+i)!=a)){ 91 c++; 92 q++; 93 } 94 95 if(*(q+i) == -1){ 96 return -1; 97 }else{ 98 return i+c; 99 } 100} 101 102 103 104// 入力されたペアを表示 105void show(int *p){ 106 int i; 107 for(i=0;i<N;i+=2){ 108 if( *(p+i)==n){ 109 printf("%dのペアは%d\n",n,*(p+i+1)); 110 } 111 if( *(p+i+1)==n ){ 112 printf("%dのペアは%d\n",n,*(p+i)); 113 } 114 } 115} 116 117 118// 配列の全てを表示 119void show_array(int *p){ 120 int i; 121 for(i=0;i<N;i+=2){ 122 printf("(%d %d) " ,*(p+i),*(p+i+1));} 123 printf("\n"); 124}

ところでN=4のときであれば

  1. (1,2)(3,4)
  2. (1,3)(2,4)
  3. (1,4)(2,3)

とペアの組は三種類あり、N=6のときであれば15種類あります。
このペアというのは、辺のない正N角形で各頂点から1本ずつ線を引いている図形と対応できるので
(N-1)!! = (N-1)(N-3)(N-5)...*1 個あります。(二重階乗)
このように階乗で増えていくのでN=14くらいから再帰が深くなりすぎて最後までは辿りつかないようです。

ここより先追記。
二重階乗であることを使えば再帰しなくても済んだので効率的に求めることができました。
N=200で1億回目でも簡単に求まります。
自分が試したところN=10000000程度でも問題はなかったです。

C

1#include <stdio.h> 2#define N 200 3 4void show(int *,int ,int ); 5 6int main(void){ 7 int i,k,n,index,count=0; 8 // S = {0,0,0,...,0,-1} 9 int S[N/2] = {0}; 10 S[N/2-1] = -1; 11 12 //入力 13 printf("繰返し回数->"); 14 scanf("%d",&k); 15 printf("要素の番号->"); 16 scanf("%d",&n); 17 //昇順列の二つずつ各ペアに置換の求める 18 while(count<k-1){ 19 index = 0; 20 count++; 21 //毎回表示するならこれを実行 22 //show(S,count,n); 23 24 S[index]++ ; 25 // 繰り上がり 26 while(S[index] >= N-1-2*index){ 27 S[index] = 0; 28 S[index+1]++; 29 index++; 30 } 31 //全部調べたら終了 32 //配列最後の-1は表示時の終了条件で使うので代入 33 if(S[N/2-1] != -1){ 34 S[N/2-1] = -1; 35 break; 36 } 37 } 38 39 //表示 40 show(S,k,n); 41 return 0; 42} 43 44void show(int *S,int k,int n){ 45 int i,temp,index=0; 46 // A[] = {1,2,3,4,...,N} 47 int A[N]; 48 for(i=0;i<N;i++){A[i] = i+1;} 49 while(*S != -1){ 50 /* Sの置換表現通りに入れ替え */ 51 temp = A[2*index+1]; 52 A[2*index+1] = A[ 2*index+1+(*S)]; 53 A[2*index+1+(*S)] = temp; 54 //次の置換へ 55 index++; 56 S++; 57 } 58 59 printf("\n%d回目: ",k); 60 for(i=0;i<N;i+=2){printf("(%d,%d) ",A[i],A[i+1]);} 61 for(i=0;i<N;i+=2){ 62 if( A[i]==n ){printf("\n%dのペアは%dです。\n",n,A[i+1]);break;} 63 if(A[i+1]==n){printf("\n%dのペアは%dです。\n",n, A[i] );break;} 64 if(i==N-2){printf("%dのペアはありません\n",n);} 65 } 66 67}

少し数学チックに解いた部分はあるのですが、アルゴリズム的にはN=6の場合
(12)(34)(56)の一つ目の(12)の部分をみて左の数値を固定して、右の数値を自分より後の数値と入れ替えることを考えます。(”後”とは入れ替えないような自分自身と入れ替える場合も含みます)
(1[2])(34)(56)だから2の移動先は2,3,4,5,6の5通りで入れ替えた後はそれより右の組だけを考えます。たとえばこの例で2が5と入れ替わったとしましょう。すると
(15)(34)(26)となり、ここで(15)は固定して残りの(34)(26)で同じことを繰り返すことによって同じパターンが出ずに全てを網羅できます。
ここで入れ替えた所をそれぞれ自分自身からの距離(2と5を入れ替えるのであれば3)を配列にして格納することで再帰することなく、求めることができます。
ちなみN=6の場合だと2の移動先が5通り、その次の右側の数字の移動先が3通り、その次の右側の数字が1通りなので全部で15通りと、ちゃんとN!!通りになっていることが確かめられます。

投稿2020/04/22 12:35

編集2020/04/23 10:57
syalpon

総合スコア24

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

そして入力として要素の番号と何回目の作業かを表す数字を与えたらペア相手の要素番号が返ってくるようなできるだけ計算量の小さい数式を作りたいのですが何か効率的な方法はないでしょうか.

すみません。これの意味がよく分かりません。

全ての組み合わせパターンを網羅できるような方法はありますか?

書いてみました。

#include <stdio.h> #define N 20 int n, a[N]; void generate(int i) { if (i == n) { for (i = 0; i < n; i += 2) printf(" (%d,%d)", a[i], a[i + 1]); putchar('\n'); } else { for (int j = i + 1; j < n; j++) { int t = a[j]; for (int k = j - 1; k > i; k--) a[k + 1] = a[k]; a[i + 1] = t; generate(i + 2); for (int k = i + 1; k < j; k++) a[k] = a[k + 1]; a[j] = t; } } } int main(void) { while (printf("n: "), scanf("%d", &n) == 1 && n < N && n % 2 == 0) { for (int i = 0; i < n; i++) a[i] = i + 1; generate(0); } }

n が大きい時は、表示に時間がかかるので、次のようにファイルに出力しましょう。
$ echo 14 | ./a.out > result.txt

追記
意味が分かりました。

C

1#include <stdio.h> 2 3#define N 68 4 5int k; // 繰返し回数(kaisu) 6int n; // 要素の番号(number) 7int c; // 生成個数(count) 8int a[N]; // 要素の列(array) 9 10void generate(int i) 11{ 12 if (c == k) return; 13 if (i == N) { 14 if (++c == k) { 15 int p; // ペアの相手 16 for (i = 0; i < N; i += 2) { 17 printf(" (%d,%d)", a[i], a[i + 1]); 18 if (a[i + 1] == n) p = a[i]; 19 else if (a[i] == n) p = a[ i + 1]; 20 } 21 printf("\n %d の相手は %d\n\n", n, p); 22 } 23 } 24 else { 25 for (int j = i + 1; j < N; j++) { 26 int t = a[j]; 27 for (int k = j - 1; k > i; k--) a[k + 1] = a[k]; 28 a[i + 1] = t; 29 generate(i + 2); 30 for (int k = i + 1; k < j; k++) a[k] = a[k + 1]; 31 a[j] = t; 32 } 33 } 34} 35 36int main(void) 37{ 38 while (1) { 39 printf("繰返し回数: "); 40 if (scanf("%d", &k) != 1) return 1; 41 printf("要素の番号: "); 42 if (scanf("%d", &n) != 1 || n < 1 || n > N) return 2; 43 for (int i = 0; i < N; i++) a[i] = i + 1; 44 c = 0; 45 generate(0); 46 } 47}

追記2
上記のコードにはバグがありました。調査します。

追記3
j < n を j < N に修正しました。

追記4
関数 generate の中の if (c == k) return; は、ここではなく、
再帰呼出しの generate(i + 2); の直後に置いたほうが、効率が良くなります。

投稿2020/04/22 14:39

編集2020/04/23 07:28
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

2個ずつのペアに分けるだけなら,
N個の要素を適当に並び替えて,先頭から2個ずつ取り出せばよい.

  • 全パターン網羅することに拘らないならば,上記の「適当に並び替えて」の部分は例えば「ランダムに並べ替える処理」とかで.(乱数のSEEDを固定すれば,作業回数に対する再現性も持たせることも可能)

  • 全パターンを網羅することが必須なら,

上記の「適当に並び替えて」の部分を「N個の要素を一列に並べる全ての並べ方を列挙する手段(C++ならstd::next_permutationとかが使える)」を用いるような形態とすればよいのでは.

投稿2020/04/22 04:35

fana

総合スコア11954

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問