@amanojaku

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

幅優先探索について

解決したいこと

stackで深さ優先探索のプログラムを作成していました。それを変更してqueueで幅優先探索のプログラムに変更しようとしたところマップをプリントすることが出来ませんでした。最短経路を探索してマップを表示するにはどうすればいいか知りたいです。

発生している問題・エラー

under flow
Stack Underflow.

該当するソースコード

#include <stdio.h>
#include <stdlib.h>

#define BUF_SIZE 200
#define SIZE_X 20
#define SIZE_Y 10

int visit[SIZE_X][SIZE_Y];

struct coord {
  int x;  //x座標
  int y;  //y座標
  int px; //一つ前のx座標
  int py; //一つ前のy座標
};

struct queue {
  struct coord buf[BUF_SIZE];
	int first;
	int last;
};

int enqueue(struct queue *s, struct coord p) {
	if(s->last < BUF_SIZE){
		s->buf[s->last] = p;
		s->last++;
		return 1;
	}else{
		printf("overflow\n");
		return -1;
	}
}

struct coord *dequeue(struct queue *s) {
	if(s->first > s->last){
		printf("under flow\n");
		return NULL;
	}
	int n = s->first;
	s->first++;
	return &(s->buf[s->first]);
}

/* display_queue関数
 * 機能: キューの内部(格納されている値)の表示
 * 引数s: 内部を表示するスタック構造体へのポインタ
 */
void display_queue(struct queue *s) {
	int i = s->first-1;
	while(i >= 0){
		printf("%d %d\n",s->buf[i].x,s->buf[i].y);
		i--;
	}
}

/* read_map関数
 * 機能: ファイルからマップを読み込む
 * 引数map: 読み込んだマップを格納する配列
 */
void read_map(char map[][SIZE_Y]) {
	FILE *fp;
	int ch;
	int i=0;
	int j=0;
	fp =fopen("meiro1.txt","r");
	while((ch = fgetc(fp)) != EOF){
		if(ch == '\n'){
			j++;
			i = 0;
		}else{
			map[i][j] = ch;
			i++;
		}
	}
	fclose(fp);
}

/* print_map関数 
 * 機能: マップを画面に表示する。
 * 引数map: 表示するマップが格納されている配列
 */
void print_map(char map[][SIZE_Y]) {
	for(int j=0;j < SIZE_Y;j++){
		for(int i=0;i < SIZE_X;i++){
			printf("%c",map[i][j]);
		}
		printf("\n");
	}
}

/* record_path関数
 * 機能: 探索経路(探索順路)を記録する。新たなマスを探索する毎に、そのマスを記録するために呼び出すこと。
 * 引数rec: 探索経路を記録する配列
 * 引数x,y: 現在位置の座標
 * 引数px,py: 一つ前の位置の座標
 */
void record_path(struct coord rec[][SIZE_Y], int x, int y, int px, int py) {
  rec[x][y].px = px;
  rec[x][y].py = py;
}

/* print_path関数
 * 機能: 記録した探索経路を基に、SからGまでの経路を表示する。
 * 引数map: 表示するマップの配列。これに経路を示す'.'を書き加える
 * 引数rec: 探索経路が記録された配列
 * 引数gx,gy: ゴールの座標
 */
void print_path(char map[][SIZE_Y], struct coord rec[][SIZE_Y], int gx, int gy) {
  int x, y, tx, ty;

  // ゴールの位置から始める
  x = gx; y = gy;

  // ゴールの前の座標に移動(ゴールに'.'を打たない)
  tx = rec[x][y].px;
  ty = rec[x][y].py;
  x = tx; y = ty; 

  // スタートまで一手ずつ戻りながらmapに'.'を打つ
  while(map[tx][ty] != 'S') {
    map[tx][ty] = '.';
    tx = rec[x][y].px;
    ty = rec[x][y].py;
    x = tx; y = ty; 
  }
  print_map(map);
}

/********************** 
 *  メイン関数
 **********************/
int main(void) {

  /***** 変数の宣言(必要に応じて変数を追加すること)*****/
  char map[SIZE_X][SIZE_Y];  // マップを格納する配列
  struct coord rec[SIZE_X][SIZE_Y]; // 探索経路を記録する配列
  struct coord tmp; // 一時変数として利用可能
  struct coord *p;  // 一時変数として利用可能
  struct queue s;   // 探索に利用するキュー
  int i,j;          // ループ変数
  int x,y,px,py;    // 座標を格納する一時変数
  int gx,gy;        // ゴールの位置を格納

  /***** 各変数の初期化 *****/
  s.first = 0; 
  tmp.x = -1; tmp.y = -1; tmp.px = -1; tmp.py = -1;
  for (i=0; i<SIZE_X; i++) {
    for (j=0; j<SIZE_Y; j++) {
      visit[i][j] = 0;
      rec[i][j] = tmp;
    }
  }

  /***** マップの読み込み *****/
  read_map(map); 

  /***** スタート地点をenqueue *****/
  tmp.x = 1; tmp.y = 1; tmp.px = -1; tmp.py = -1;
  if (enqueue(&s, tmp) == -1) {
    printf("Stack Overflow.\n");
    return 1;
  }
  /***** 探索処理 *****/
  while(1) {
    // 処理する座標をdequeue
    p = dequeue(&s);
    if (p == NULL) {
      printf("Stack Underflow.\n");
      return 1;
    }
    x = p->x, y = p->y; px = p->px; py = p->py; 

		if(map[x][y] == 'G'){
			printf("Goal found:x=%d,y=%d,\n",x,y);
			record_path(rec,x,y,px,py);
			gx = x;
			gy = y;
			break;
		}
		if(map[x][y] == '#'){
			continue;
		}
		if(visit[x][y] ==1) continue;
		visit[x][y] = 1;
		
		record_path(rec,x,y,px,py);
		
		tmp.x = x;tmp.y = y-1;tmp.px = x;tmp.py = y;
		if(enqueue(&s,tmp) < 0) return 1;
		
		tmp.x = x-1;tmp.y = y;tmp.px = x;tmp.py = y;
		if(enqueue(&s,tmp) < 0) return 1;
		
		tmp.x = x;tmp.y = y+1;tmp.px = x;tmp.py = y;
		if(enqueue(&s,tmp) < 0) return 1;
		
		tmp.x = x+1;tmp.y = y;tmp.px = x;tmp.py = y;
		if(enqueue(&s,tmp) < 0) return 1;
  }
  /***** 探索経路の表示 *****/
  print_path(map,rec,gx,gy);

}

自分で試したこと

under flowの条件がよく分からず色々なものを試した。

0 likes

2Answer

今後のためにも、ご自身でデバッグできるところまでデバッグしてから、その内容を質問するとよいです。

//145行目
-  s.first = 0; 
+  s.first = s.last = 0; 

//41行目
- return &(s->buf[s->first]);
+ return &(s->buf[n]);

//??行目

最後の箇所はqueue関係です。
今のqueueは、一方方向に伸びていくだけの構造ですが、queueが空になったときに、first,lastを先頭に戻すことで、メモリを再利用することができます。「一方方向に伸びていくだけでよい」のであれば、サイズを大きくするしか無いです(メモリ無駄喰いですが)。

0Like

Comments

  1. @amanojaku

    Questioner

    今回の場合queueが空になるタイミングはどこか聞いてもいいでしょうか。

  2. 今回の場合queueが空になるタイミングはどこか聞いてもいいでしょうか。

    dequeue関数の中で、s->first++後のfirstがlastと等しくなったタイミングです。

  3. @amanojaku

    Questioner

    ありがとうございます!

ちょっと気になったのですが、どのような環境で開発されているのでしょうか。
実は前回の質問(深さ優先の方)もチラ見していたのですが
ちゃんとデータ(変数の中身)の把握・制御できていないまま開発を進めているような印象を受けました。

デバッガーでステップ実行して変数の中身が確認できるようなリッチな環境があれば楽ですし
そうでなければ愚直にプリントデバッグで重要なデータの中身をあちこちで出力させながらの実行が必要ですが
「どのあたりの処理まで想定通りのデータになっているか」
「どのあたりから様子がおかしくなっているか」が分かれば、
その前後の処理が思った通り実装できているか、そもそもデータ構造やサイズ指定が適切か……等を見直すための手掛かりとなります。


さて問題箇所ですが
オンラインコンパイラ―で動かそうとしてみたところ、コンパイル時に警告が出ました

Main.c:39:6: warning: unused variable 'n' [-Wunused-variable]
        int n = s->first;
            ^
1 warning generated.

ここも怪しいですね。

せっかく display_queue 関数があるのに、超重要データである first も last も表示させていない&そもそも確認に使っている様子がなさげ? なのが非常にもったいないです。

前述の警告で少なくとも1ヵ所怪しいのは分かってはいますが、
是非あちこちでデバッグ出力を試して、どこがどうおかしいのか調査する練習もできるとよいと思います。

0Like

Comments

  1. 他にも気になった点として……

    • ロジック
      • 探索ループの次の座標として4方向とりあえず全部キューに乗せちゃうのが無駄の多い処理
        • 乗せる前に visit で探索済みか否か判定できる
        • 乗せる前に map で壁かどうか判定できる
        • ただ無駄なだけでなく、キューがリングバッファになってないのと合わさって早めにオーバーフローさせがち
    • デバッグ出力
      • display_queue で出力する内容
        • first,last を表示していない(前述のとおり)
        • 出力対象が現在使用されている範囲ではない (lastを使っていないし、おそらくfirstもズレている)
      • 探索済みマップ(visit)のデバッグ表示があってもよいかも
        • 今回は必須ではないかもしれないが、状況把握はしやすくなるかもしれない
  2. @amanojaku

    Questioner

    ロジックのご指摘本当に参考になります。visitとmapで探索済みであるか、壁であるかを判定してその後に次の座標とすることで大幅にBUF_SIZEが減少しました。ただリンクバッファの実装方法がいまいちよく分かりません。理屈は分かるのですが、s->firstの変更するタイミングとunder flowの判定をどうすればよいのか疑問です。可能であれば回答お願いします。

  3. @amanojaku

    Questioner

    ちなみに座標の判定はこのように変更しました。
    if(map[x][y-1] != '#'){
    if(visit[x][y-1] != 1){
    if(enqueue(&s,tmp) < 0) return 1;
    }
    }

Your answer might help someone💌