C言語関係掲示板

過去ログ

No708 多倍長の最大公約数

[戻る] [ホームページ]
No.8512

最大公約数についてです
投稿者---美香(2003/07/18 20:34:41)


多倍長の最大公約数に関してなんですが、多倍長の場合はどうすればいいのですか??

#include <stdio.h>
#include <string.h>

#define MAX 72			/* 扱う桁数の最大値 */
#define N ((MAX-1)/4+1)		/* MAX桁を記憶するための配列サイズ */

/* プロトタイプ宣言 */
void input_num(int D[],int []);	/* 整数をキーボードから入力すると
							   4桁ずつ区切って配列D[]に記憶する */

void display(int D[]);		/* 配列 D[] に記憶している数を表示 */

void sub(int A[], int B[], int C[]);
				
int comp(int A[], int B[]); 

int main(void) {
  /* 大きな整数を記憶する配列 */
  int A[N], B[N], C[N];			 
  int j=1;

  input_num(A,j++);
  input_num(B,j);
  comp(A,B);
  sub(A, B, C);
  printf("\n "); display(A);
  printf("\n "); display(B);
  printf("\n 最大公約数は"); display(B);

  return(0);
}

/* 配列A,Bに記録された数の和を配列Cに記録 */
void sub(int A[], int B[], int C[]) {
  int i, carry,r;
  int x=comp(A,B); 
  for (i=0; i<=N-1; i++) {
    C[i]=0;
  }


  if(x==1){
    for(i=0;i<=N/2-1;i++){
      C[i]=A[i]%B[i];
    }
    for(i=0;i<=N/2-1;i++){
      while(A[i]%B[i]!=0){
    for(i=0;i<=N/2-1;i++){
      B[i]=A[i];
    }
    for(i=0;i<=N/2-1;i++){
      C[i]=B[i];
    }
      for(i=0;i<=N/2-1;i++){
	C[i]=A[i]%B[i];
      }
    }
    }
  }
}



int comp(int A[], int B[]) {
  int i,a=1,b=0;
  
  for(i=N/2;i>=0;i--){

    if(A[i]>B[i]){
      return(a);
    break;
    }

    else if(A[i]<B[i]){
      return(b);
      break;
    }
  }
  return;
}

/* 整数をキーボードから入力すると4桁ずつ区切って配列D[]に記憶する関数 */

void input_num(int D[],int j) {
  char line[MAX];
  int	 last;
  int	 i,digit;

  printf(" 整数%d:%d 桁以下の整数を入力して下さい:",j, MAX/2);
  fgets(line, sizeof(line), stdin);
  for (i=0; i<=N-1; i++) {
    D[i] = 0;
  }
  last= strlen(line)-2;	/* 1の桁のindex */
  for (i=0; last >=0; i++) {
    for (digit=1; digit<=1000 && last>=0; digit*=10, last--) {
      D[i] += (line[last]-'0')*digit;
    }
  }
  return;
}

/* 配列 D[] に記憶している数を表示 */
void display(int D[]) {
  int i,digit,temp,flag=1;
  /* digit は桁数、temp は表示されていない部分 */
  
  for (i=N-1; i>0; i--) {
    if (D[i]==0) printf("    ");
    else break;
  }
 
  printf("%4d",D[i]);

  for (i-- ; i>=0; i--) {
    for(digit=1000, temp=D[i]; digit>=1; digit/=10) {
      printf("%d", temp/digit);	/* digit で割った商を表示 */
      temp%=digit;		/* digit で割った余りに置き換え */
    }
  }
  return;
}

						


No.8514

Re:最大公約数についてです
投稿者---美香(2003/07/19 19:11:55)


関数部分がわかりません。

No.8516

Re:最大公約数についてです
投稿者---あかま(2003/07/20 00:30:58)


>関数部分がわかりません。
そりゃ、大抵は関数にしますから関数が分からないのは当然ですねぇ。
これだと、全部作ってくれといっているのと変わりませんよ?

で、作ってみたわけですが(笑)
ひどく効率が悪いです。
数字によっては有限の時間で終わらないかもしれません。
説明しときます。

最大公約数の求め方は、正直よく知らないのですが
とりあえず共通で割れる素因数を掛け合わせていけば求まります。

入力する多倍長をA,B、素因数をCとすると
1.共通で割れる素因数を求めるには、A/C,B/Cの多倍長同士の割り算をする関数が必要になります。
多倍長の割り算の仕方が分からなかったので、A-Cをひたすら繰り返し引けた回数が割り算の商と同じになります。
で、答えはA桁-C桁(10桁の数字を3桁の数字で割ると7桁ぐらいの答えが出ます)程度になるので商も多倍長で返す必要があります。で作りました。

2.共通で割れるCの素因数はとりあえず割ってみないと分かりません。
そこでとりあえず2〜Bの値までCをひたすらカウントアップ、その値でA,Bを割ってみるということをやりました。

3.Cの素因数をかけ合わせる関数は、1.の逆の操作をしてCの回数ひたすら足していきます。
当然、この答えも多倍長です。で作りました。

で、全体にかかわるループ回数は
1.がA桁-B桁回ぐらい
2.がB桁回

二つのループ回数をかけて、A桁-B桁+B桁で結局A桁回ぐらいループがあります。
で、さらに2.のような処理も入りますので、さらに多くなります。
(かなり大雑把な上に間違っている可能性大)

結局何が言いたいかといいますと
1234567890とかAに入力するとループ回数が少なくとも1000000000回ぐらいになりますよと。
終わりませんよと。

だからといって小さい数を入力すると多倍長の意味なくなるし。。。
このジレンマなんか悲しいな(笑)
こんなのはバブルソートで1万個の要素を並べ替えようとした以来だ。


No.8517

Re:最大公約数についてです
投稿者---あかま(2003/07/20 00:31:43)


#include <stdio.h>
#include <string.h>

#define MAX 72			/* 扱う桁数の最大値 */
#define N ((MAX-1)/4+1)		/* MAX桁を記憶するための配列サイズ */

/* プロトタイプ宣言 */
void input_num(int D[],int);	/* 整数をキーボードから入力すると
							   4桁ずつ区切って配列D[]に記憶する */
void display(int D[]);		/* 配列 D[] に記憶している数を表示 */
/*↓割り算.第3引数に商が多倍長で返る.第1引数と第3引数が同じ配列でもok.割り切れるなら真を返す*/
int div(int [],int [],int []);
void koubaisu(int A[],int B[],int C[]);
void mult(int A[], int B[], int C[]);
void sub(int A[], int B[], int C[]);
void hiku(int A[], int B[], int C[]);				
int comp(int A[], int B[]); 
void hiku2(int A[], int B[]);

int main(void) {
  /* 大きな整数を記憶する配列 */
  int A[N], B[N], C[N];			 
  int j=1,i;
  
  input_num(A,j++);
  input_num(B,j);

  koubaisu(A,B,C);
  printf("\n 最大公約数は"); display(C);

  return(0);
}

void koubaisu(int A[],int B[],int C[]){
	int i,a1[N],a2[N],b1[N],b2[N],D[N];
	int *Ap1,*Ap2,*Bp1,*Bp2,*buf;
	
	Ap1 = a1,Ap2 = a2;
	Bp1 = b1,Bp2 = b2;
	
	for(i = 0;i < N;i++){
		Ap1[i] = A[i];
		Bp1[i] = B[i];
		D[i] = C[i] = 0;
	}
	C[0] = 1;
	for(D[0] = 2;!div(Ap1,D,Ap2) && !div(Bp1,D,Bp2);){
		buf = Ap1,Ap1 = Ap2,Ap2 = buf;
		buf = Bp1,Bp1 = Bp2,Bp2 = buf;
		mult(C,D,C);
	}

	for(D[0] = 3;comp(Bp1,D);){
		while(!div(Ap1,D,Ap2) && !div(Bp1,D,Bp2)){
			buf = Ap1,Ap1 = Ap2,Ap2 = buf;
			buf = Bp1,Bp1 = Bp2,Bp2 = buf;
			mult(C,D,C);
			printf("現在計算中,今");
			display(D);
			printf("で割り終わりました。今のとこ公倍数は");
			display(C);
			printf("\n");
		}

		for(D[i=0]+= 2;D[i] >=10000 && i < N-1;){//桁上げ
			D[i] -=10000;
			D[++i]++;
		}
	}
}

int div(int A[],int B[],int C[]){
	int i;
	int D[N];
	
	for(i = 0;i < N;i++){
		D[i] = A[i];
		C[i] = 0;
	}
	for(i = 0;comp(D,B);i++){
		hiku2(D,B);
		for(C[i=0]++;D[i] >=10000 && i < N-1;){//桁上げ
			C[i] -=10000;
			C[++i]++;
		}
		
	}
	for(i = 0;i < N;i++){
		if(D[i]){
			return 1;
		}
	}
	return 0;
}


void tasu(int A[], int B[]){
	int i,carry=0;
	for(i=0;i < N-1;i++){
		A[i] += B[i]+carry;
		carry = A[i] >= 10000;
		if(carry){
			A[i] -=10000;
		}
	}
}
void mult(int A[], int B[], int C[]){
	int i;
	int D[N],E[N];
	
	for(i = 0;i < N;i++){
		D[i] = A[i];
		E[i] = C[i] = 0;
		
	}
	E[0]=1;
	while(comp(B,E)){
		
		tasu(C,D);
		
		for(E[i=0]++;E[i] >=10000 && i < N-1;){//桁上げ
			E[i] -=10000;
			E[++i]++;
		}
	}
}
void hiku(int A[], int B[], int C[]) {
  int i, carry;	
	
  /* 配列 C の初期化 */
  for (i=0; i<=N-1; i++) {
    C[i]=0;
  }

  /* A-B を C に記録 */
  for (i=0, carry=0; i<=N/2-1; i++) {
	C[i] = A[i] - B[i] - carry;
	carry = C[i] < 0;
	if (carry) C[i] += 10000;

  }
}

void hiku2(int A[], int B[]) {
  int i, carry;	

  for (i=0, carry=0; i<=N/2-1; i++) {
	A[i] = A[i] - B[i] - carry;
	carry = A[i] < 0;
	if (carry) A[i] += 10000;

  }
}
int comp(int A[], int B[]) {
  int i;
  
  for(i=N/2;i>=0;i--){

    if(A[i]>B[i]){
      return(1);
    }
    else if(A[i]<B[i]){
      return(0);
    }
  }
  return 1;
}

/* 整数をキーボードから入力すると4桁ずつ区切って配列D[]に記憶する関数 */

void input_num(int D[],int j) {
  char line[MAX];
  int	 last;
  int	 i,digit;

  printf(" 整数%d:%d 桁以下の整数を入力して下さい:",j, MAX/2);
  fgets(line, sizeof(line), stdin);
  for (i=0; i<=N-1; i++) {
    D[i] = 0;
  }
  last= strlen(line)-2;	/* 1の桁のindex */
  for (i=0; last >=0; i++) {
    for (digit=1; digit<=1000 && last>=0; digit*=10, last--) {
      D[i] += (line[last]-'0')*digit;
    }
  }
  return;
}

/* 配列 D[] に記憶している数を表示 */
void display(int D[]) {
  int i,digit,temp,flag=1;
  /* digit は桁数、temp は表示されていない部分 */
  
  for (i=N-1; i>0; i--) {
    if (D[i]!=0) break;
  }
 
  printf("%04d",D[i]);

  for (i-- ; i>=0; i--) {
    for(digit=1000, temp=D[i]; digit>=1; digit/=10) {
      printf("%d", temp/digit);	/* digit で割った商を表示 */
      temp%=digit;		/* digit で割った余りに置き換え */
    }
  }
  return;
}


No.8518

Re:最大公約数についてです
投稿者---あかま(2003/07/20 00:43:38)


このプログラムでやっていけないこと
Aより大きいBを入力してはいけない→どう動くかわからない
Bにゼロを入力してはいけない→やっぱりどう動くかわからない

で、このプログラムを効率的にするには
1.効率的な乗除関数を作る。
これが一番効率を上げられますがワタシには無理。

2.Cを2〜Bを一つずつではなく素数のみで割っていく。
できるかな。がんばれば。

3.Cを2〜√Bにする。
できるかな。これなら。


で、最後に。コメントもなにもないきったないソースでごめんなさい。

No.8519

Re:最大公約数についてです
投稿者---YuO(2003/07/20 01:09:32)


>で、作ってみたわけですが(笑)
>ひどく効率が悪いです。

みたいですね……。
ユークリッドの互除法を利用するとよいですよ。
#古代ギリシャ時代から連綿と続く最大公約数の求め方……。

A>Bを仮定して,A = B * Q + Rとします(Q, Rは正整数)
その時,GCD(A, B) == GCD(B, R)が成立します。
これをくり返して,R == 0になるときのBが最大公約数です。
#証明は面倒なので省略。

多倍長計算で,割り算は非常に面倒くさいですが,
これを使うと割り算を使わないでもGCDを計算することが出来ます。
#GCD(A, B) == GCD(A - B, B) ただしA>B


No.8520

Re:最大公約数についてです
投稿者---あかま(2003/07/20 02:11:39)


ども。そーいえばこんなの習った憶えが…
というわけでユークリッド版です。あってるかなこれで。

void koubaisu(int A[],int B[],int C[]){
	int i,*Ap,*Bp,*buf;
	
	Ap = A;
	Bp = B;
	while(1){
		while(comp(Ap,Bp)){
			hiku2(Ap,Bp);
		}
		buf = Ap;
		Ap = Bp;
		Bp = buf;
	
		for(i = 0;i < N;i++){
			if(Bp[i]){
				break;
			}
		}
		if(i == N){
			for(i = 0;i < N;i++){
				C[i] = Ap[i];
			}
			return;
		}
			
	}
}


No.8521

Re:最大公約数についてです
投稿者---かずま(2003/07/20 03:31:11)


> 多倍長計算で,割り算は非常に面倒くさいですが,
> これを使うと割り算を使わないでもGCDを計算することが出来ます。
> #GCD(A, B) == GCD(A - B, B) ただしA>B

でも、A=123456789012345678901234567890、B=24 のような場合、
引き算なんてやってられませんよね。やっぱり割り算が必要です。
#include <stdio.h>
#include <string.h>

#define MAX 72           /* 扱う桁数の最大値 */
#define N ((MAX-1)/4+1)  /* MAX桁を記憶するための配列サイズ */

static int zero[N];

int get_int(const char *msg, int *a)
{
    char buf[MAX+1];  int i, j = 0;

    fputs(msg, stdout);
    scanf("%s", buf);
    memset(a, 0, N * sizeof(int));
    for (i = strlen(buf) - 4; i > 0; i -= 4) sscanf(buf + i, "%4d", &a[j++]);
    buf[i+4] = 0;
    sscanf(buf, "%d", &a[j]);
    return 0;
}

void put_int(const char *msg, const int *a, const char *msg2)
{
    int i = N;

    fputs(msg, stdout);
    while (--i > 0 && a[i] == 0) ;
    printf("%d", a[i]);
    while (--i >= 0) printf("%04d", a[i]);
    fputs(msg2, stdout);
}

int add(int *c, const int *a, const int *b)
{
    int i, carry = 0;

    for (i = 0; i < N; i++) {
        c[i] = a[i] + b[i] + carry;
        carry = (c[i] >= 10000);
        if (carry) c[i] -= 10000;
    }
    return carry;
}

int subtract(int *c, const int *a, const int *b)
{
    int i, borrow = 0;

    for (i = 0; i < N; i++) {
        c[i] = a[i] - b[i] - borrow;
        borrow = (c[i] < 0);
        if (borrow) c[i] += 10000;
    }
    return borrow;
}

int divide(int *q, int *r, const int *a, const int *b)
{
    int i, j;  int x[2*N];

    if (memcmp(b, zero, N * sizeof(int)) == 0) return 1;
    memcpy(x, a, N * sizeof(int));  memset(x + N, 0, N * sizeof(int));
    for (i = N-1; i >= 0; i--) {
        for (j = 0; j < 10000; j++)
            if (subtract(x + i, x + i, b)) break;
        add(x + i, x + i, b);
        q[i] = j;
    }
    memcpy(r, x, N * sizeof(int));
    return 0;
}

int gcd(int *c, const int *a, const int *b)
{
    int q[N], r[N], p[N];

    memcpy(p, a, N * sizeof(int)), memcpy(c, b, N * sizeof (int));
    for (;;) {
        if (divide(q, r, p, c)) return 1;
        if (memcmp(r, zero, N * sizeof(int)) == 0) return 0;
        memcpy(p, c, N * sizeof(int)), memcpy(c, r, N * sizeof(int));
    }
}

int main(void)
{
    int a[N], b[N], c[N];

    get_int("a: ", a);
    get_int("b: ", b);
    if (gcd(c, a, b)) return puts("zero divide"), 1;
    put_int("gcd(a, b) = ", c, "\n");
    return 0;
}


No.8530

Re:最大公約数についてです
投稿者---あかま(2003/07/21 01:50:01)


せっかくなので、かずまさん方式で掛算作ってみました。

qに下位桁,rに上位桁を指定です。上位桁が必要ないならNULL入れてください。
void mult(int *q, int *r, const int *a, const int *b){
	int i,j; int x[2*N]={0};
	
			for(i = 0;i < N;i++)
				for(j = 0;j < b[i];j++)
					add(x+i,x+i,a);

	memcpy(q,x,N*sizeof(int));
	if(r != NULL) memcpy(r,x+N,N*sizeof(int));
}


No.8531

Re:最大公約数についてです
投稿者---あかま(2003/07/21 03:26:57)


普通に掛算使えばもう少し速くなりますね。
前のプログラムが平均N*5000。
こっちはN*Nになりました。

void mult(int *q, int *r, const int *a, const int *b){
	int i,j,k,l; int x[2*N]={0};
	long buf;
		for(i = 0;i < N;i++){
			for(j = 0;j < N;j++){
				buf = a[i]*b[j];

				k= i+j;
				x[k] += buf%10000;
				if(x[k] >= 10000){
					x[k] -= 10000;
					x[k+1]++;
				}
					
				for(++k,x[k] += buf/10000;x[k] >= 10000;x[++k]++)
					x[k] -= 10000;
					
			}
		}

	memcpy(q,x,N*sizeof(int));
	if(r != NULL) memcpy(r,x+N,N*sizeof(int));
}