ページ 1 / 1
木構造の構築
Posted: 2012年10月26日(金) 02:45
by むつみ
[img]IMG_0250[/img]以前このサイトで質問したことがあるのですが、病気を患ってしまいしばらくこのサイトから離れてしまったため、もう一度質問させていただきます。
私の質問なんですが、木構造(多分木)を構築するというものです。
私が木構造を構築しようとしている背景ですが、
私は大学で回路の遅延を求める研究を行っています。
回路は1つの入力点から始まって、そこから枝分かれしていき最終的に1つの出力点に収束していきます。
[img]IMG_0250[/img]
回路は素子と配線からなるものとしていて、それぞれにランダムに遅延を与えて入力から出力までの間で一番遅延が大きかった道(Critical Path 以下CPと呼ぶ)を調べるということをしています。
この試行を複数回(10万回くらい)繰り返して、回路内のどのPathが何回CPに含まれたかを数え上げられるようにしたいです。
これを実現させるために木構造を構築したいと思っています。
どなたか分かる方がいれば教えて頂けたらなと思います!
c++などではなくcでの構築をしなければならないので、その前提でよろしくお願いします。
Re: 木構造の構築
Posted: 2012年10月26日(金) 12:36
by non
前の続きなのでしょうけど、昔のスレッドを最初から読むのは面倒なので、あらためて、質問の内容を具体的にお願いします。
Re: 木構造の構築
Posted: 2012年10月26日(金) 13:29
by むつみ
分かりました!
私がしたい事というのは、
CP(遅延が最も長かった回路内の道筋)の系列を数え上げて、どんような系列が何回CPに含まれたかを数え上げられるようにしたいと思っています。
実際にはCPはランダムに決められるので、どのような系列になるかは実際に試行してみないと分かりません。
例えば、試行回数を5回に設定して(実際は10万回くらい試行する)、ある回路でCPを求めたとします。
その結果、得られたCPの系列が
①1、 3、 5、 7、 8、 12
②1、 2、 3、 4、 7、 8、 10、 12
③1、 3、 5、 7、 9、 12
④1、 3、 5、 7、 8、 12
⑤1、 4、 6、 7、 9、 10 12
という5本のCPが検出されたとします。
上での番号は回路内の各素子に番号を振ったものなので、
①だったら、CPが (1→3→5→7→8→12) という素子を通ったという意味です。
(実際には素子の数が何千個ある回路を扱ったりもします)
このときに、
1、 3、 5、 7、 8、 12 →2回
1、 2、 3、 4、 7、 8、 10、 12 →1回
1、 3、 5、 7、 9、 12 →1回
1、 4、 6、 7、 9、 10 12 →1回
というふうに、全く同じ道筋を通った場合の回数を数えるということをC言語で実現させなけばいけません。
まず、上のような事を実現させるためには木構造を構築するのが一番良いのでしょうか?
木構造で良いとしたら、
回路は2分木ではなく多分木になっているものもあるので、回路が多分木だった場合にも対応出来るようになっていなければいけません。
このとき、多分木をどのように木構造で表現したらいいかが分かりません。
ネットなどで調べると2分木の構築のことしか書いてありません。
私の分からないことはこんな感じです。
説明不足な部分がありましたら、補足させて頂きますので言って頂ければと思います。
Re: 木構造の構築
Posted: 2012年10月26日(金) 17:32
by beatle
木構造が一番素直か、といわれると違うと思います。
僕だったら、まず次のようなデータ構造を使います。
それぞれのパスをリストで表します。
[1, 3, 4, 7, 8, 12]
[1, 2, 3, 4, 7, 8, 10, 12]
・・・
そして、同じリストが何個あるかを数えます。そのためにはハッシュテーブルが向いているでしょう。
リストをキー、整数を値とするハッシュテーブル(連想配列とか辞書ともいう)を構築します。
それにしても、どうしてC言語にこだわるか良くわかりません。
授業の課題ならともかく、研究ならやりやすい言語を選択するというのも大事なのではと思います。
Re: 木構造の構築
Posted: 2012年10月26日(金) 21:24
by non
まだ、よくわかりません。
CPってクリティカルパスですよね。
ノードがたくさんあって、PERT図を考えればよいのでしょうか?
すると、遅延時間というのはPERTでいうところの所要時間?
で、PERTなら最短を考えるけど、今回は最長を考えるのでしょうか?
で、CPがランダムで与えられるって?各アクティビティの所要時間がランダムで与えられるわけではなく?
経路が与えられるのなら、ただ単にカウントすればよいだけなので、beatleさんが仰るように木構造とかいう話ではないと思うけど・・・
経路を求めたいというのではないの?
やっぱ、理解できてないので、アドバイスできません。
Re: 木構造の構築
Posted: 2012年10月26日(金) 22:26
by むつみ
回答ありがとうございます。
ハッシュテーブルですか、なるほど気が付きませんでした。
確かに木構造を構築するよりも簡単に出来そうな気がします。
しかし、素人の私の考えなので正しいか分かりませんが、
ハッシュテーブルを用いて実現しようとした場合、木構造を構築する場合と比較するとメモリを沢山使ってしまうのではないでしょうか?
例えば、3回の試行でCPが
①1、 2、 3、 4、 7
②1、 2、 3、 5、 7
③1、 2、 3、 6、 7
だった場合に木構造だと1、 2、 3までは同じ道を通っているので①、②、③でそれぞれ別々にリストを作る必要がないので、ここでメモリの削減が出来るのではないかと思いました。
しかし、ハッシュテーブルを用いてそれぞれリストを作成したとしてもメモリが不足する心配がないのであれば、ハッシュテーブルを用いた方法でやってみたいと思います。
言語についてですが、
現在使っているCPを求めるプログラムがc言語で書かれているので、それに合わせてc言語を用いて書きたいと思っています。
まだ、よくわかりません。
CPってクリティカルパスですよね。
>はい、クリティカルパスの事です。
ノードがたくさんあって、PERT図を考えればよいのでしょうか?
すると、遅延時間というのはPERTでいうところの所要時間?
で、PERTなら最短を考えるけど、今回は最長を考えるのでしょうか?
で、CPがランダムで与えられるって?各アクティビティの所要時間がランダムで与えられるわけではなく?
>はい、回路はちょうどPERT図のように考えることができ、各枝での所要時間が毎回変化し、その中の最長を考え プログラム内で計算して試行回数分のCPを求めてくれるので、試行した回数のなかでどんなパスが何回含まれ たかを表示出来るようにしたいです。
Re: 木構造の構築
Posted: 2012年10月26日(金) 23:02
by beatle
メモリはそんなに食わないと思います。
1,2,3のリストを同時に作るのではなくて、逐次的に作っていけば良いと思います。
リスト1を作ってハッシュテーブルに格納→初めての格納なので、リスト1を実際に格納し、リスト1に対応する値を1とする
リスト2を作ってハッシュテーブルに格納→既に同じ内容のリスト1が格納されているので、リスト1に対応する値をインクリメントするだけ(リスト1に対応する値が2となる)
リスト3を作ってハッシュテーブルに格納→リスト2のときと同じように、リスト1に対応する値をインクリメントするだけ(リスト1に対応する値が3となる)
結局、リスト2と3は破棄されて、リスト1だけが生き残ります。
Re: 木構造の構築
Posted: 2012年10月26日(金) 23:27
by むつみ
ありがとうございます。
手順としては、
破棄するタイミングは最終的にまとめて破棄するのではなく、同じ系列があった場合にインクリメントし終わった際に破棄して次の試行へ、、
この試行でのCPがまた先ほどのCPと同じだった場合は回数を数える変数を1つインクリメントしてから今回の系列を破棄して次の試行へ、
この試行で新たなCPが出てきた場合はそのリストは消さずにとっておき、回数を数える変数を1にして次の試行へ
:
:
これを試行回数分繰り返すと最終的に、異なるCPの系列が1つずつ残っていて、
それらの系列が何回含まれたかを、回数を数え上げる為の変数が示してくれている。
といった認識でよろしいでしょうか?
また、これをc言語で実現させることは初心者には難しいでしょうか?
Re: 木構造の構築
Posted: 2012年10月26日(金) 23:34
by beatle
最初はみな初心者なので心配することはないと思います。
要はむつみさんにどれだけのやる気があるかだけの問題です。
C言語でハッシュテーブルを書くこと自体はそんなに難しいことではありません。
が、プログラミングに不慣れな人が作るとバグを埋め込んでしまいやすい部分ではあります。
ハッシュテーブルは有名なデータ構造ですから、検索すればいくらでも解説や実装例、C言語向けのライブラリなどが見つかります。
もし、自分で実装したくて検索したサイトを読んだけど良くわからない、というのならば、この掲示板で質問すれば良いと思います。
その際は、回路の話などは出さず、純粋なハッシュテーブルの質問にしてください。
Re: 木構造の構築
Posted: 2012年10月26日(金) 23:39
by beatle
むつみ さんが書きました:ありがとうございます。
手順としては、
破棄するタイミングは最終的にまとめて破棄するのではなく、同じ系列があった場合にインクリメントし終わった際に破棄して次の試行へ、、
この試行でのCPがまた先ほどのCPと同じだった場合は回数を数える変数を1つインクリメントしてから今回の系列を破棄して次の試行へ、
この試行で新たなCPが出てきた場合はそのリストは消さずにとっておき、回数を数える変数を1にして次の試行へ
:
:
これを試行回数分繰り返すと最終的に、異なるCPの系列が1つずつ残っていて、
それらの系列が何回含まれたかを、回数を数え上げる為の変数が示してくれている。
といった認識でよろしいでしょうか?
また、これをc言語で実現させることは初心者には難しいでしょうか?
そんな感じです。擬似コードで書けば次のようになるでしょう。
コード:
cp_table = MakeHashtable();
cp = NextCP();
while (cp != NULL) {
if (cp in cp_table) {
cp_table[cp]++;
free(cp);
} else {
cp_table.put(cp, 1);
}
cp = NextCP();
}
Re: 木構造の構築
Posted: 2012年10月27日(土) 00:22
by むつみ
ありがとうございます。
やる気は非常にあるのでこのハッシュテーブルついて調べて実装してみたいと思います。
今後の質問なのですが、
おそらく分からない事も沢山出てくると思うので今後もこの掲示板を活用していきたいと思っているのですが、
トピックを変えて新しくハッシュテーブルの質問として挙げた方がよろしいでしょうか?
Re: 木構造の構築
Posted: 2012年10月27日(土) 07:21
by beatle
ハッシュテーブルは純粋なハッシュテーブルとして実装するのが良いと思いますので、回路の話を抜きにしたハッシュテーブルだけの質問を新しいスレッドで行うのがいいと思います。
ハッシュテーブルならよく知っている人でも、回路の話が書いてあると回答してくれないかもしれませんからね。
それに、回路を前提にしたハッシュテーブルを作るのではなく、どんなデータも格納できるような汎用的なハッシュテーブルを学んだほうが今後の役に立つと思います。
Re: 木構造の構築
Posted: 2012年10月27日(土) 09:29
by むつみ
分かりました。
丁寧に教えてくださって本当にありがとうございます。
今後は新しいトピックで質問させて頂く事になると思いますが、
見かけた際にはまたアドバイスなど頂けたら嬉しいです。
よろしくお願いします。
Re: 木構造の構築
Posted: 2012年10月28日(日) 19:17
by かずま
C++ だと <vector> や <map> が使えて楽なんですが、
一つの CP のサイズや、ハッシュテーブルのサイズを固定にすると、
C でも簡単に書けるかな、と思って書いてみました。
malloc のエラーチェックや、free を省略しています。
あくまでも参考程度に眺めてみてください。
コード:
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 997
#define BUF_SIZE 1000
#define DATA_SIZE 500
typedef struct CP {
int *data;
int size;
int count;
struct CP *next;
} CP;
CP *alloc_cp(int *data, int size)
{
CP *cp = malloc(sizeof(CP));
cp->data = malloc(sizeof(int) * size);
memcpy(cp->data, data, sizeof(int) * size);
cp->size = size;
cp->count = 1;
cp->next = NULL;
return cp;
}
int comp(const CP *cp, int *data, int size)
{
int i;
if (cp->size != size) return 1;
for (i = 0; i < size; i++)
if (cp->data[i] != data[i]) return 1;
return 0;
}
void add(CP **hash_table, int hash, int *data, int size)
{
CP **p = &hash_table[hash];
for (; *p && comp(*p, data, size); p = &(*p)->next) ;
if (*p)
(*p )->count++;
else
*p = alloc_cp(data, size);
}
void print(CP **hash_table, int size)
{
int i, j;
CP *cp;
for (i = 0; i < size; i++) {
for (cp = hash_table[i]; cp; cp = cp->next) {
printf("hash=%3d: count=%3d:", i, cp->count);
for (j = 0; j < cp->size; j++) printf(" %d", cp->data[j]);
printf("\n");
}
}
}
int main()
{
CP *hash_table[HASH_SIZE] = { 0 };
char buf[BUF_SIZE];
int data[DATA_SIZE];
while (fgets(buf, sizeof buf, stdin)) {
char *p = buf, *q;
int i, hash = 0;
for (i = 0; i < DATA_SIZE; i++) {
data[i] = (int) strtol(p, &q, 10);
if (p == q) break;
hash += data[i];
p = q;
}
add(hash_table, hash % HASH_SIZE, data, i);
}
print(hash_table, HASH_SIZE);
return 0;
}
入力データ
コード:
1 3 5 7 8 12
1 2 3 4 7 8 10 12
1 3 5 7 9 12
1 3 5 7 8 12
1 4 6 7 9 10 12
1 4 5 8 9 10 12
出力結果
コード:
hash= 36: count= 2: 1 3 5 7 8 12
hash= 37: count= 1: 1 3 5 7 9 12
hash= 47: count= 1: 1 2 3 4 7 8 10 12
hash= 49: count= 1: 1 4 6 7 9 10 12
hash= 49: count= 1: 1 4 5 8 9 10 12
ハッシュ値が等しいものは、リストでつながっています。
Re: 木構造の構築
Posted: 2012年10月28日(日) 19:39
by むつみ
ありがとうございます!
入力に対する出力結果を見た限り、私がしようとしている事はまさにこれです!
これから中身の方も詳しく見させてもらって、分からない事が出てきましたら、また質問させて頂きます。
しかし、CPのサイズは毎回同じとは限らないのですが、CPのサイズが固定でないと難しいのでしょうか?
また、ハッシュテーブルのサイズも固定でないと難しいのでしょうか?
mallocなどを使って動的に確保するのは難しいでしょうか?
Re: 木構造の構築
Posted: 2012年10月28日(日) 19:48
by h2so5
むつみ さんが書きました:しかし、CPのサイズは毎回同じとは限らないのですが、CPのサイズが固定でないと難しいのでしょうか?
また、ハッシュテーブルのサイズも固定でないと難しいのでしょうか?
mallocなどを使って動的に確保するのは難しいでしょうか?
元からサイズを大きめに作っておけばいいのではないでしょうか。
CPについては、使用していない部分は -1 とかで埋めてしまえばいいわけですし。
メモリに余裕があるなら malloc 使うより効率は良くなりますから。
Re: 木構造の構築
Posted: 2012年10月28日(日) 20:40
by かずま
むつみ さんが書きました:ありがとうございます!
しかし、CPのサイズは毎回同じとは限らないのですが、CPのサイズが固定でないと難しいのでしょうか?
説明が不正確で済みません。CP の最大サイズ(DATA_SIZE)が固定です。
個々の CP は、最大サイズの範囲内で、必要な個数だけが確保されます。
むつみ さんが書きました:
また、ハッシュテーブルのサイズも固定でないと難しいのでしょうか?
mallocなどを使って動的に確保するのは難しいでしょうか?
固定よりは少し面倒ですが、できますよ。
でも、必要でしょうか?
例えば、10万個の CP が与えられたとします。
同じものがいくつもあって、異なるものは、1万個だったとします。
997個のエントリーがあって、半分の 500個が使用されたとします。
一つのエントリーあたり、平均20個がリストの要素として入ります。
これで不満なら、テーブルサイズを 9973 にするといういのはどうでしょうか?
Re: 木構造の構築
Posted: 2012年10月28日(日) 21:45
by むつみ
たしかにサイズを大きめに作っておけばいいだけなので、あまり問題ない気がしました。
>例えば、10万個の CP が与えられたとします。
>同じものがいくつもあって、異なるものは、1万個だったとします。
>997個のエントリーがあって、半分の 500個が使用されたとします。
ここで1つ疑問に思ったのですが、
私の認識では異なるCPが1万個だったときに、必要なハッシュテーブルの個数は最低でも1万個だと思っていたのですがそうではないのでしょうか?
上の文を読んだ時に、1万個のCPに対して500個分のハッシュテーブルで間に合うということなのかなって思って、私の認識が間違っているのではないかと思いました。
もし間違っていたとしたら、1つのハッシュテーブルに対して異なる種類の複数のCPを割り当てられるという事なのでしょうか?
私の知識不足のせいで初歩的な質問をしてしまってすいません。
Re: 木構造の構築
Posted: 2012年10月28日(日) 22:16
by beatle
ハッシュテーブルの原理は大丈夫でしょうか。
何か格納したい値に対して、ハッシュ関数を用いてハッシュ値を計算します。
格納したい値が少しでも変化すると、普通はハッシュ値は大きく変化しますが、稀に違う格納値に対して同じハッシュ値になることがあります。ハッシュ値の衝突です。
ハッシュテーブルをただの配列にしてしまうと、ハッシュ値が衝突したときに以前の値を上書きしてしまいます。
そこで、ハッシュテーブルを「連結リストの配列」として作ることで、ハッシュ値が衝突しても値を上書きしないようにします。
ハッシュ値が衝突した場合、連結リストにどんどん追加していくのですね。
そのように構築されたハッシュテーブルは次のように検索します。
1.与えられたキーに対してハッシュ値を計算します。
2.ハッシュ値を添え字だと思って配列にアクセスします
3.配列から連結リストを取り出します
4.与えられたキーを持つ要素を連結リストから探します。
4ではハッシュ値を使わず、直接キーの比較をします。
したがって、連結リストの各要素はキーそのものを保持しておく必要があります。
今回の場合ですと、数列リストがキーになりますね。
Re: 木構造の構築
Posted: 2012年10月28日(日) 23:46
by むつみ
異なる値でもハッシュ値が等しくなってしまうことがあるというのは知っていたのですが、同じハッシュテーブルに複数の数列を格納してしまうとそのテーブル内で数列の区別が出来なくなってしまうのではないかと思ったのですが、
同じテーブル内でも数列によって固有のキーがあるので、それで区別が出来るという事なんですね。
しかし、1万種類のCPがあったとして、それらの数列からハッシュ関数を用いて求めたハッシュ値が全て異なる値になった場合は1万個のハッシュテーブルが必要になるのではないでしょうか?
Re: 木構造の構築
Posted: 2012年10月29日(月) 02:29
by かずま
むつみ さんが書きました:
異なる値でもハッシュ値が等しくなってしまうことがあるというのは知っていたのですが、同じハッシュテーブルに複数の数列を格納してしまうとそのテーブル内で数列の区別が出来なくなってしまうのではないかと思ったのですが、
同じテーブル内でも数列によって固有のキーがあるので、それで区別が出来るという事なんですね。
固有のキーはありません。数列自体が異なるので区別できます。
むつみ さんが書きました:
しかし、1万種類のCPがあったとして、それらの数列からハッシュ関数を用いて求めたハッシュ値が全て異なる値になった場合は1万個のハッシュテーブルが必要になるのではないでしょうか?
プログラムの 72行目を見てください。 ハッシュ値は HASH_SIZE で割った余り
ですから、ハッシュテーブルのサイズに収まります。
それから、ハッシュテーブルは 1個です。
テーブルのエントリー(配列の要素)が HASH_SIZE個になります。
コード:
+------+
hash_table[35] | NULL | +--> 1 3 5 7 8 12
+------+ +------|-----------------------------+
hash_table[36] | *----->| data:*, size:6, count:2, next:NULL |
+------+ +------------------------------------+
hash_table[37] | *----->| data:*, size:6, count:1, next:NULL |
+------+ +------|-----------------------------+
hash_table[38] | NULL | +--> 1 3 5 7 9 12
+------+
| NULL | +--> 1 2 3 4 7 8 10 12
+------+ +------|-----------------------------+
hash_table[47] | *----->| data:*, size:8, count:1, next:NULL |
+------+ +------------------------------------+
hash_table[48] | NULL |
+------+ +------------------------------------+ +------------------------------------+
hash_table[49] | *----->| data:*, size:7, count:1, next: *------> | data:*, size:7, count:1, next:NULL |
+------+ +------+-----------------------------+ +------|-----------------------------+
hash_table[50] | NULL | +--> 1 4 6 7 9 10 12 +--> 1 4 5 8 9 10 12
+------+
同じハッシュ値 49 を持つ 2つの数列は、hash_table[49] のエントリーに
2つ繋がっています。
かずま さんが書きました:
例えば、10万個の CP が与えられたとします。
同じものがいくつもあって、異なるものは、1万個だったとします。
997個のエントリーがあって、半分の 500個が使用されたとします。
一つのエントリーあたり、平均20個がリストの要素として入ります。
これで不満なら、テーブルサイズを 9973 にするといういのはどうでしょうか?
これ、ちょっと訂正します。
「半分の 500個が使用された」は根拠がありません。
1万個のデータを 997個のテーブルエントリーに入れると、
すべてのエントリーについて、一つのエントリーあたり、
平均10個がリストの要素として入ると考えるのが普通です。
平均ですから、0個のものも何個か(何十個か)あるし、中には、
20個、30個というのも何個かはあるでしょう。
だから、9973個にしましょう、と提案しています。
そうすると、平均 1個となり、0個のものがたくさんあるかも
しれませんが、一つのエントリーに多くても 10個かな?
順番に探索してもまあ許せるのではありませんか?
4999個でも十分でしょう。
Re: 木構造の構築
Posted: 2012年10月29日(月) 15:06
by むつみ
詳しい説明ありがとうございます!
1つのエントリに複数の数列を入れてしまうと、探索する際にかなり遅くなるのでしょうか?
確かに初めからエントリの個数を9973個くらいとっておけばエントリが被る可能性は低くなりますね。
それと、かずまさんが書いてくださったプログラムは入力データはどこから得るようになっているのでしょうか?
実行する際に手動で入力するのかなと思ったのですが、どこがその部分に対応しているのか分からなっかので。
Re: 木構造の構築
Posted: 2012年10月29日(月) 15:36
by beatle
むつみ さんが書きました:
1つのエントリに複数の数列を入れてしまうと、探索する際にかなり遅くなるのでしょうか?
確かに初めからエントリの個数を9973個くらいとっておけばエントリが被る可能性は低くなりますね。
はい。遅くなります。
ハッシュテーブルの特徴は、ハッシュテーブルの各エントリに高々1つしか格納されていない場合、ハッシュテーブルに格納されている要素数に関わらず、常に定数時間で検索できることにあります。
これは、検索するキーのハッシュ値を計算する処理が常にほぼ一定の時間だからです。
しかし、エントリに複数の要素が格納された状態になると、そのエントリの検索にはそのエントリに格納されている要素の数だけ時間がかかります。
(最悪の場合は、ハッシュテーブル中のただ1つのエントリにだけ要素が集中し、単なる線形リストになります。線形リストの検索時間は線形リストの要素数に比例します。例えばハッシュ関数が常に定数を返すような実装の場合にそうなります)
ですから、良くある実装では、各エントリの要素数が増えすぎたら自動でエントリ配列の数を増やし、各要素のハッシュ値を再計算し、エントリに割振り直すという処理をします。
まあ、難しいので安易に実装するとバグを埋め込みますけど。
Re: 木構造の構築
Posted: 2012年10月29日(月) 17:38
by むつみ
CPを約10万回求めるとして、ハッシュサイズを素数などの割った時の値がバラツキやすい値に設定しておけば問題ないですかね?
CPの最長の長さを忘れてしまったので、明日までに調べて検討してみようと思っているのですが。
Re: 木構造の構築
Posted: 2012年10月29日(月) 17:42
by beatle
CPの長さはハッシュテーブルを作るときは関係ないと思うのですけども。
ハッシュテーブルに値を格納したり、検索したりするときは、キー(今回では数列)のハッシュ値を求めます。
ハッシュ値はハッシュ関数というもので求めます。
ハッシュ関数はキーを受け取って整数を返します。(今回では数列を受け取ってintを返します)
このハッシュ関数は、同じ数列に対しては同じ整数を、少しでも異なる数列に対しては異なる値を返すようにします。
異なる、というのは長さが異なることはもちろん、長さが一緒で中身が違う数列も異なる数列です。
すなわち数列がどんな長さだろうと、ハッシュ値はint型になります。
Re: 木構造の構築
Posted: 2012年10月30日(火) 01:45
by むつみ
CPの長さを調べると言ったのは、同じエントリ内に複数の数列が格納されたときに、その中の数列を探索する際に、CPが長いほど探索する時間がかかってしまうのではないかと思った為です。
例えば、1つのエントリ(エントリ1)内に2つのCPが格納された時に、
①1、2、3
②1、2、4
の場合は
エントリ1⇒1→2→3→→1→2→4
となりますが2つのCPが
①1、2、3、4、5、6、7、8、9
②1、3、4、5、6、7、8、10、11
の場合は
エントリ1⇒1→2→3→4→5→6→7→8→9→→1→3→4→5→6→7→8→10→11
となるので先頭からリストを辿っていくので時間がかかってしまうのではないかと思いました。
ハッシュ関数は例えば
数列の和(キー)をある数で割った余りを返す。
のようにしてハッシュ値を求めるのですよね?
例えば、7で割った余りをハッシュ値として返す。
と決めた場合に
①1、2、3、4
②1、4、5
という2つの数列は同じエントリに格納されることになりますよね?
Re: 木構造の構築
Posted: 2012年10月30日(火) 13:27
by かずま
むつみ さんが書きました:それと、かずまさんが書いてくださったプログラムは入力データはどこから得るようになっているのでしょうか?
実行する際に手動で入力するのかなと思ったのですが、どこがその部分に対応しているのか分からなっかので。
fgets(..., stdin) ですから、標準入力からの行入力です。
標準入力をファイルに切り替えなければ、デフォルトはキー入力でしょう。
Unix のシェルや Windows のコマンドプロンプトだったら、コマンドラインに
リダイレクト<input-file をつければよいし、
Visual Studio だったら、プロジェクトのプロパティの構成プロパティのデバッグの
コマンド引数に <input-file を書けばよいでしょう。
質問するときに開発環境を書いてくれないから、答えるほうは面倒です
Re: 木構造の構築
Posted: 2012年10月30日(火) 13:39
by かずま
むつみ さんが書きました:ハッシュ関数は例えば
数列の和(キー)をある数で割った余りを返す。
のようにしてハッシュ値を求めるのですよね?
ハッシュ関数を変更しました。
コード:
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 997
#define BUF_SIZE 1000
#define DATA_SIZE 500
typedef struct CP {
int *data;
int size;
int count;
struct CP *next;
} CP;
CP *alloc_cp(const int *data, int size)
{
CP *cp = malloc(sizeof(CP));
cp->data = malloc(sizeof(int) * size);
memcpy(cp->data, data, sizeof(int) * size);
cp->size = size;
cp->count = 1;
cp->next = NULL;
return cp;
}
int comp(const CP *cp, const int *data, int size)
{
int i;
if (cp->size != size) return 1;
for (i = 0; i < size; i++)
if (cp->data[i] != data[i]) return 1;
return 0;
}
int hash(const int *data, int size)
{
int val = 0, i;
for (i = 0; i < size; i++)
val = val * 9 + data[i];
return val % HASH_SIZE;
}
void add(CP **hash_table, int *data, int size)
{
CP **p = &hash_table[hash(data, size)];
while (*p && comp(*p, data, size)) p = &(*p)->next;
if (*p)
(*p)->count++;
else
*p = alloc_cp(data, size);
}
void print(CP **hash_table, int size)
{
int i, j;
CP *cp;
for (i = 0; i < size; i++) {
for (cp = hash_table[i]; cp; cp = cp->next) {
printf("hash=%3d: count=%3d:", i, cp->count);
for (j = 0; j < cp->size; j++) printf(" %d", cp->data[j]);
printf("\n");
}
}
}
int main()
{
CP *hash_table[HASH_SIZE] = { 0 };
char buf[BUF_SIZE];
int data[DATA_SIZE];
while (fgets(buf, sizeof buf, stdin)) {
char *p = buf, *q;
int i;
for (i = 0; i < DATA_SIZE; i++) {
data[i] = (int) strtol(p, &q, 10);
if (p == q) break;
p = q;
}
add(hash_table, data, i);
}
print(hash_table, HASH_SIZE);
return 0;
}
コード:
1 3 5 7 8 12
1 2 3 4 7 8 10 12
1 3 5 7 9 12
1 3 5 7 8 12
1 4 6 7 9 10 12
1 4 5 8 9 10 12
1 2 3 4
1 4 5
実行結果
コード:
hash=122: count= 1: 1 4 5
hash=277: count= 2: 1 3 5 7 8 12
hash=286: count= 1: 1 3 5 7 9 12
hash=314: count= 1: 1 2 3 4 7 8 10 12
hash=382: count= 1: 1 4 6 7 9 10 12
hash=532: count= 1: 1 4 5 8 9 10 12
hash=922: count= 1: 1 2 3 4
Re: 木構造の構築
Posted: 2012年10月30日(火) 20:14
by かずま
ハッシュ関数にバグがありました。
次のように修正します。
コード:
int hash(const int *data, int size)
{
unsigned int val = 0, i;
for (i = 0; i < size; i++)
val = val * 9 + data[i];
return val % HASH_SIZE;
}
val は unsigned でないといけません。
Re: 木構造の構築
Posted: 2012年10月31日(水) 00:29
by むつみ
返信ありがとうございます。
Windowsを使ってやっております。
どこの部分でどんな機能をしているのか確かめたくて書いてくださったプログラムを動かしてみようと思ったのですが、コンパイルは出来たのですが、
実行する際にどのように入力すればいいのか分かりません。
Cygwinを使っているのですが、
$ ./hash.exe
123
145
123
123
$ ./hash.exe 123 145 168
このように2通り入力してみたのですが、どちらも何回enterを押しても実行されません。
初歩的な事を質問してしまってすいません。
Re: 木構造の構築
Posted: 2012年10月31日(水) 02:33
by かずま
コード:
$ gcc -o hash hash.c
$ ./hash
123
145
123
123
^D
hash=123: count= 3: 123
hash=145: count= 1: 145
$ cat >data.txt
123
145
168
^D
$ ./hash <data.txt
hash=123: count= 1: 123
hash=145: count= 1: 145
hash=168: count= 1: 168
$ ./hash <data.txt >result.txt
$ cat result.txt
hash=123: count= 1: 123
hash=145: count= 1: 145
hash=168: count= 1: 168
$ ls -l
total 22
-rw-r--r-- 1 kazuma None 12 Oct 31 02:24 data.txt
-rw-r--r-- 1 kazuma None 1786 Oct 31 02:24 hash.c
-rwxr-xr-x 1 kazuma None 14508 Oct 31 02:25 hash.exe
-rw-r--r-- 1 kazuma None 75 Oct 31 02:27 result.txt
$
^D は、 Ctrl + D です。
Re: 木構造の構築
Posted: 2012年10月31日(水) 02:50
by かずま
かずま さんが書きました:コード:
$ ./hash
123
145
123
123
^D
hash=123: count= 3: 123
hash=145: count= 1: 145
$
酔っぱらっていました。
コード:
$ ./hash
1 2 3
1 4 5
1 2 3
1 2 3
^D
hash=102: count= 3: 1 2 3
hash=122: count= 1: 1 4 5
$
Re: 木構造の構築
Posted: 2012年10月31日(水) 03:18
by かずま
むつみ さんが書きました:2つのCPが
①1、2、3、4、5、6、7、8、9
②1、3、4、5、6、7、8、10、11
の場合は
エントリ1⇒1→2→3→4→5→6→7→8→9→→1→3→4→5→6→7→8→10→11
となるので先頭からリストを辿っていくので時間がかかってしまうのではないかと思いました。
比較関数 comp() をよく見てください。
まず、CP の長さを比較し、それが不一致であれば比較終了です。
長さが一致しても、数列の途中で不一致があれば、そこで比較を打ち切ります。
数列全体を最後まで比較することはまれでしょう。
例えば、 1 2 3 4 5 6 7 8 10 という CP は (1)の全体と比較して不一致となりますが、
(2) との比較は、2番目の 2 と 3 で比較終了です。
Re: 木構造の構築
Posted: 2012年11月01日(木) 23:37
by むつみ
ありがとうございます!
教えてくださった通りにやってみたら出来ました。
それでは、エントリ1に
①1→4→5
②1→5→6
③1→2→5
という3本のCPが格納されているとき、
エントリ1⇒(1→4→5)→→(1→5→6)→→(1→2→5)
のように格納されているとして、
今、新たに④1→2→5というCPが追加されたとします。
このとき、以前に同じCPが出ていないか確かめますが、
この場合はまずエントリ内の先頭に付いている①1→4→5と比較をして、
2と4が異なるということで比較を終了するという事ですよね?
次に②1→5→6と比較をして2と5が異なるので比較を終了。
最後に③1→2→5と比較をして、全て一致したので同じパスと判定してカウントを1つインクリメントする。
といった流れでしょうか?
私が疑問に思っているのは、パスの途中で値が異なったから比較を終了しても、次に比較するパスは比較をしていたパスの最後に付いているので、結局連結リストを先頭から順番に辿っていかなければいけないのではないかと思いました。
Re: 木構造の構築
Posted: 2012年11月02日(金) 02:11
by かずま
むつみ さんが書きました:私が疑問に思っているのは、パスの途中で値が異なったから比較を終了しても、次に比較するパスは比較をしていたパスの最後に付いているので、結局連結リストを先頭から順番に辿っていかなければいけないのではないかと思いました。
次のパスが前のパスの最後についているのではありません。
コード:
| NULL | struct CP
+------+ +-----------+ +------+------+------+
hash_table[136] l * ------>| data: *---->| 1 | 3 | 28 |
+------+ | size: 3 | +------+------+------+
| NULL | | count: 1 |
+------+ | next: * |
| NULL | +--------|--+
+------+ V
| NULL | +-----------+ +------+------+------+
| data: *---->| 1 | 4 | 19 |
| size: 3 | +------+------+------+
| count: 1 |
| next: * |
+--------|--+
V
+-----------+ +------+------+------+
| data: *---->| 1 | 5 | 10 |
| size: 3 | +------+------+------+
| count: 1 |
| next: NULL|
+-----------+
Re: 木構造の構築
Posted: 2012年11月02日(金) 06:51
by beatle
むつみ さんが書きました:それでは、エントリ1に
①1→4→5
②1→5→6
③1→2→5
という3本のCPが格納されているとき、
エントリ1⇒(1→4→5)→→(1→5→6)→→(1→2→5)
のように格納されているとして、
今、新たに④1→2→5というCPが追加されたとします。
このとき、以前に同じCPが出ていないか確かめますが、
この場合はまずエントリ内の先頭に付いている①1→4→5と比較をして、
2と4が異なるということで比較を終了するという事ですよね?
次に②1→5→6と比較をして2と5が異なるので比較を終了。
最後に③1→2→5と比較をして、全て一致したので同じパスと判定してカウントを1つインクリメントする。
といった流れでしょうか?
流れはバッチリですね.
むつみ さんが書きました:結局連結リストを先頭から順番に辿っていかなければいけないのではないかと思いました。
先頭から順に辿っていかなければならないのは連結リスト(=線形リスト)の運命です.
遅いのは仕方ないんです.
だからハッシュテーブルにして,一つ一つの連結リストの長さを縮めようとしているのですよ.
Re: 木構造の構築
Posted: 2012年11月02日(金) 21:02
by むつみ
なるほど、そういうつながり方をしていたのですね。
数列を必ず最後まで辿る必要がないことは分かりました。
現在、NO:28で書いてくださったコードを1つ1つ見ているのですが、正しく理解出来ているか不安な箇所があったり分からない箇所があるので、各コードに対して私なりにコメントを入れてみたので確認して頂けないでしょうか?
お手数おかけしてしまって申し訳ありません。
コード:
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 997 // ハッシュのエントリ数を決めている
#define BUF_SIZE 1000 // ここで何を宣言しているのかが分かりません
#define DATA_SIZE 500 // 数列の最大サイズ
typedef struct CP { // 構造体CPの定義
int *data; //数列の中身
int size; //数列のサイズ
int count; //同じ数列が出現した回数
struct CP *next; //同エントリ内に複数CPを格納する際に連結させる
} CP;
CP *alloc_cp(const int *data, int size) //上の構造体の初期設定
{
CP *cp = malloc(sizeof(CP));
cp->data = malloc(sizeof(int) * size);
memcpy(cp->data, data, sizeof(int) * size);
cp->size = size;
cp->count = 1;
cp->next = NULL;
return cp;
}
int comp(const CP *cp, const int *data, int size) //新しく求められたCPを既出のCPと比較している。等しくないときに1を返す。
{
int i;
if (cp->size != size) return 1; //左辺と右辺、どちらが新しいCPでどちらが既出のCPなのでしょうか?またここで、既出のsize全ての種類に対して比較を行っているのでしょうか?
for (i = 0; i < size; i++)
if (cp->data[i] != data[i]) return 1;//上と同じく、どちらが既出のdataでどちらが新しく求めたCPか分かりません。また、既出の全てのdata[i]と比較を行っているのでしょうか?
return 0;
}
int hash(const int *data, int size) //hash値の計算
{
int val = 0, i;
for (i = 0; i < size; i++)
val = val * 9 + data[i];
return val % HASH_SIZE;
}
void add(CP **hash_table, int *data, int size)
{
CP **p = &hash_table[hash(data, size)]; //hash(data, size)で求めたハッシュ値を添字とするハッシュテーブルのアドレスを**pとする。
while (*p && comp(*p, data, size)) p = &(*p)->next; //ここの部分が分かりません。*pは何を示しているのでしょうか?下で(*p)->count++とあるところから、新しく求められた数列を示す為のポインタなのではないかと思いました。また、whileの中で何をしているのかよく分かりません。
if (*p) //これはどのような状態なのでしょうか?下から推測すると同じ数列が現れたかどうかという条件文ではないかと思うのですが、なぜそのような意味を持つのか分かりません。
(*p)->count++; //同じ数列が現れたのでインクリメント
else
*p = alloc_cp(data, size); //新しい種類の数列だったので、新しく追加する。
}
void print(CP **hash_table, int size)
{[code=c]
int i, j;
CP *cp;
for (i = 0; i < size; i++) {
for (cp = hash_table
; cp; cp = cp->next) //終了条件としてcpとだけ書かれていますが、これはどういう意味なのでしょうか?前後から推測すると同じエントリ内の全てのCPを辿り終わったという意味の気がするのですが、なぜそのような意味になるのでしょうか?
{
printf("hash=%3d: count=%3d:", i, cp->count);
for (j = 0; j < cp->size; j++) printf(" %d", cp->data[j]);
printf("\n");
}
}
}
int main()
{
CP *hash_table[HASH_SIZE] = { 0 };
char buf[BUF_SIZE];
int data[DATA_SIZE];
while (fgets(buf, sizeof buf, stdin)) {
char *p = buf, *q;
int i;
for (i = 0; i < DATA_SIZE; i++) {
data = (int) strtol(p, &q, 10); //ここから以下3行で何をしているのか分かりません。
if (p == q) break;
p = q;
}
add(hash_table, data, i);
}
print(hash_table, HASH_SIZE);
return 0;
}
[/code]
Re: 木構造の構築
Posted: 2012年11月02日(金) 21:12
by むつみ
投稿してから気がついたのですが、コメントがかなり見づらくなってしまっていて申し訳ありません。
Re: 木構造の構築
Posted: 2012年11月03日(土) 15:17
by かずま
コード:
#include <stdio.h>
#include <stdlib.h>
#define HASH_SIZE 997 // ハッシュのエントリ数
#define BUF_SIZE 1000 // ここで何を宣言しているのかが分かりません
// 入力行バッファのサイズ。fgets により最大998文字 + '\n' + '\0' が入る
#define DATA_SIZE 500 // 数列の最大サイズ
typedef struct CP { // 構造体CPの定義
int *data; // 数列の中身
int size; // 数列のサイズ
int count; // 同じ数列が出現した回数
struct CP *next; // 同エントリ内に複数CPを格納する際に連結させる
} CP;
CP *alloc_cp(const int *data, int size) // 上の構造体の初期設定
{
CP *cp = malloc(sizeof(CP));
cp->data = malloc(sizeof(int) * size);
memcpy(cp->data, data, sizeof(int) * size);
cp->size = size;
cp->count = 1;
cp->next = NULL;
return cp;
}
// ある一つの構造体 CP に記録されている数列と、新規入力の数列(data, size) を比較
int comp(const CP *cp, const int *data, int size)
{
int i;
if (cp->size != size) // cp->size は既出、size は新規入力
return 1; // サイズが不一致なので比較を終了
for (i = 0; i < size; i++) // 一つの数列全体を見ようとするが、
if (cp->data[i] != data[i]) return 1; // 不一致が見つかると終了
return 0; // 数列全体が一致したので差ゼロを返す。
}
int hash(const int *data, int size) // hash値の計算
{
unsigned int val = 0, i; // val が unsigned でないと、
for (i = 0; i < size; i++)
val = val * 9 + data[i]; // overflow で val が負になることがある
return val % HASH_SIZE;
}
void add(CP **hash_table, int *data, int size)
{
CP **p = &hash_table[hash(data, size)];
// hash(data, size)で求めたハッシュ値を添字とするハッシュテーブルの
// エントリのアドレスを p に入れることにより、p がエントリを指す。
// *p はエントリであり、NULL であるか、あるいは最初の構造体 CP を指す。
while (*p && comp(*p, data, size))
p = &(*p)->next;
// *p がある構造体を指していて、かつ、その構造体の持つ数列が新規入力の
// 数列(data, size) と不一致なら、*p が次の構造体を指すようにする
// *p が NULL なら、どの構造体も指していないのでループを抜ける。
if (*p) // *p がある構造体を指していれば (*p が NULL でなければ)
(*p)->count++; // 同じ数列が現れたのでインクリメント
else
*p = alloc_cp(data, size); // 新しい種類の数列だったので、新規追加
}
void print(CP **hash_table, int size)
{
int i, j;
CP *cp; // cp はある構造体を指すポインタ
for (i = 0; i < size; i++) {
for (cp = hash_table[i]; cp; cp = cp->next) {
// forループの継続条件は cp がある構造体を指していること。
// すなわち、cp が NULL でないこと
// cp を単独で書く代わりに cp != NULL または cp != 0 でも同じ
printf("hash=%3d: count=%3d:", i, cp->count);
for (j = 0; j < cp->size; j++) printf(" %d", cp->data[j]);
printf("\n");
}
}
}
int main()
{
CP *hash_table[HASH_SIZE] = { 0 }; // 全部の要素を NULL で初期化
char buf[BUF_SIZE]; // 行入力バッファ
int data[DATA_SIZE];
while (fgets(buf, sizeof buf, stdin)) {
char *p = buf, *q; // p は buf の先頭の文字を指す
int i;
for (i = 0; i < DATA_SIZE; i++) {
data[i] = (int) strtol(p, &q, 10);
// p は buf 内のある文字を指す。
// strtol は、p の指すところから始まる文字列を見て
// 空白を読み飛ばし、数字の列(10進)を数値に変換して返す。
// 数字でない文字のアドレスを q に返す。
// 数値に変換できない場合、q には、元の p の値を返す。
if (p == q) break; // 行末は数値に変換できないのでループ終了
p = q; // 次の数値を求めるために p を更新
}
add(hash_table, data, i);
}
print(hash_table, HASH_SIZE);
return 0;
}
Re: 木構造の構築
Posted: 2012年11月03日(土) 21:41
by むつみ
丁寧に説明してくださってありがとうございます。
だいぶ中身が分かってきた気がします。
昨日、私が使っているプログラムに実際にハッシュ関数のプログラムを入れてみました。
しかし、求めたCPを今度は入力としてハッシュ関数でCPの個数を求めるところでうまくいきません。
私が使っているプログラムでは以下の部分で求めたCPを出力しています。
コード:
for(i=i;i>0;i--){
printf("%ld\t",max_point -> pathlist[i]);
}
printf("%ld\n",max_point -> pathlist[0]);
printf("%ld\t",max_point -> pathlist
)で数列の最後の数以外を出力していて
printf("%ld\n",max_point -> pathlist[0])で数列の最後の数を出力しています。
例えば、
コード:
for(i=i;i>0;i--){
printf("あ%ld\t",max_point -> pathlist[i]);
}
printf("い%ld\n",max_point -> pathlist[0]);
このようにコードに文字を加えた場合、出力結果は以下のようになります。(試行回数10回)
あ26 あ4 あ5 あ10 あ11 あ18 あ19 い29 →数列1
あ26 あ4 あ5 あ10 あ11 あ18 あ19 い29 →数列2
あ26 あ4 あ5 あ10 あ11 あ20 あ21 い30 →数列3
あ27 あ6 あ7 あ12 あ13 あ22 あ23 い30 :
あ27 あ6 あ7 あ10 あ11 あ20 あ21 い30 :
あ26 あ4 あ5 あ10 あ11 あ20 あ21 い30
あ27 あ6 あ7 あ10 あ11 あ20 あ21 い30
あ26 あ4 あ5 あ10 あ11 あ20 あ21 い30
あ27 あ6 あ7 あ12 あ13 あ22 あ23 い30
あ26 あ4 あ5 あ10 あ11 あ18 あ19 い29 →数列10
入力と出力結果は以下のようになっています。
入力
$ ./last.exe c17_node.dat c17_edge.dat 5
出力
27 6 7 10 11 20 21 30
26 4 5 10 11 18 19 29
27 6 7 10 11 20 21 30
27 6 7 10 11 18 19 29
27 6 7 10 11 20 21 30
私のプログラムでは数列は手で入力するのではなく上のように結果として求められたものを使うので、
while (fgets(buf, sizeof buf, stdin)変えなければいけないのですが、
while(scanf("%ld%ld/n",max_point -> pathlist
),max_point -> pathlist[0]))とすればいいのかなと思ったのですが、
うまくいきませんでした。
また、今回の私のプログラムのように手で入力する必要がない場合、BUFを定義する必要はないのでしょうか?
Re: 木構造の構築
Posted: 2012年11月04日(日) 17:49
by かずま
むつみ さんが書きました:丁寧に説明してくださってありがとうございます。
だいぶ中身が分かってきた気がします。
昨日、私が使っているプログラムに実際にハッシュ関数のプログラムを入れてみました。
しかし、求めたCPを今度は入力としてハッシュ関数でCPの個数を求めるところでうまくいきません。
今度は入力について質問ですか?
プログラム内部で持つデータ構造についての疑問について解決したのなら、
このトピックは解決にして、また別のトピックにしませんか?
むつみ さんが書きました:私のプログラムでは数列は手で入力するのではなく上のように結果として求められたものを使うので、
while (fgets(buf, sizeof buf, stdin)変えなければいけないのですが、
なぜですか?
fgets(..., stdin) は標準入力から行を読み込むものであって、標準入力のリダイレクトにより、
キー入力でも、ファイル入力でもどちらでも使えることは示したはずですが。
むつみ さんが書きました:
while(scanf("%ld%ld/n",max_point -> pathlist),max_point -> pathlist[0]))とすればいいのかなと思ったのですが、
うまくいきませんでした。
- 括弧の対応が取れていません。
- scanf の書式の "/n" が意味不明です。"\n" の間違いだとしても、特殊な問題があります。
- "%ld" という変換指定に対する実引数は、long へのポインタでないといけません。
- scanf の返却値をそのまま while (式) の「式」に渡すことは意図したことですか?
むつみ さんが書きました:また、今回の私のプログラムのように手で入力する必要がない場合、BUFを定義する必要はないのでしょうか?
手で入力するか、ファイルから入力するかは関係ありません。
- fgets: 行単位で文字列を読み込み、それから数値に変換する。(入力行バッファが必要)
- scanf: 書式に従って、数値を読み込む
どちらを使うかはプログラマの自由です。
Re: 木構造の構築
Posted: 2012年11月05日(月) 01:15
by むつみ
ごめんなさい、かずまさんが書いてくださったファイルを入力にしている部分を見逃していました。
その通りにやってみたら出来ました。
txtファイルに保存した際にcpの結果が1つずつ改行されていなかったのが気になりましたが、そのままやってみたら正しくCPをカウントしてくれていました。
これで各パスがCPに含まれた回数を数え上げることができます。
これから研究を進めていく上でまた分からない事があったら、またこの掲示板で質問させて頂きますのでよろしくお願いいたします。
私の相談にのってくださった皆様、私の初歩的な質問に答えてくださって本当にありがとうございました。