データ構造には、配列構造とリスト構造があります。
配列は、データもコンパクトでアクセスも高速ですが、編集時にデータをずらさなければならない欠点があります。
これに対して、リストは、編集が高速にできるという利点があります。
そこで、リスト構造を中心に、データ構造とアルゴリズムについて解説します。
配列は、同じ属性のデータの繰り返しで表します。 データへのアクセスは、インデックスと呼ばれる番号で、何番目の要素かを指定してアクセスします。 配列データは、以下のような構造となります。
[データ1] 配列の0番目 [データ2] 配列の1番目 [データ3] 配列の2番目 |
言語によって異なりますが、配列は、以下のようにアクセスします。
データ名[インデックス番号] $データ名[インデックス番号] |
リストは、データの場所を示すセルで連結して表します。
セル間の関係を示すために、次のデータへのポインタを持っています。
データ1、データ2、データ3があった場合、データ1はデータ2を示し、データ2はデータ3を示し、データ3はデータ1を示すことで、3つのデータを1つのデータとして管理しています。
リストには、片方向のポインタのみ持つ単方向リストと、逆方向のポインタも持つ双方向リストがあります。
単方向リストでは、ポインタを片方向しか持たないため、データはコンパクトになりますが、編集時にサーチ処理が必要となるため、遅くなります。
単方向リストと双方向リストは以下のような構造となります。
単方向リストの構造 +--> [データ1] ---> [データ2] ---> [データ3] ---+ | | +-----------------------------------------------+ 双方向リストの構造 #==> [データ1] <==> [データ2] <==> [データ3] <==# # # #===============================================# |
一般的に、リスト構造のセルは、以下のポインタで表します。
単方向リストのセル next: 次データへのポインタ(next) 双方向リストのセル next: 次データへのポインタ(next) prev: 前データへのポインタ(previous) |
言語によって異なりますが、リストは、以下のようにアクセスします。
データ名->next $next[データ識別子] |
子となるノードが1つしかない場合、次データと前データは、それぞれ自分を指します。 データの最後をNULLとする方法もありますが、ループさせた方が最後のデータを簡単に検索することができるため、ループさせた方が便利です。
双方向リストの単独ループ #==> [データ1] <==# # # #=================# |
階層構造を表すためには、セルに親子関係の情報を持ちます。
親は、先頭の子のみ示し、子はそれぞれの親を示します。
これらのセルによって連結される、各データのことを、ノード(節)と呼びます。
一般的に、階層構造を持つ双方向リストのセルは、以下の4つのポインタで表します。
prnt: 親ノードへのポインタ(parent node) chid: 子ノードへのポインタ(child node) next: 次ノードへのポインタ(next node) prev: 前ノードへのポインタ(previous node) |
データには必ずルート(根)ノードが必要です。 階層構造では、このルートノードからセルのポインタによって関連付けられています。
階層構造リストの構造 [ルートノード] ↓↑↑↑ ↓↑↑+−−−−−−−−−−−−+ ↓↑+−−−−−+ ↑ ↓↑ ↑ ↑ #==> [データ1] <==> [データ2] <==> [データ3] <==# # # #===============================================# ↓↑↑ ↓↑+−−−−−+ ↓↑ ↑ #==> [データ4] <==> [データ5] <==# # # #================================# |
このサンプルプログラムでは、階層構造を持った双方向リストを採用しています。 階層構造を持つデータを、最新日時順に表示したり、階層構造で表示したりすることができます。
サンプルプログラムは、以下のファイルから構成されています。
list.pl | 双方向リストを使った最新日時順表示+階層表示プログラム |
---|---|
data.txt | サンプルデータファイル |
list.plでは、data.txtから読み込み、配列を逆方向から表示することで、最新日時順の表示を実現しています。
また、ルートノードから検索することで、階層表示を実現しています。
階層構造を表すプリフィックス記号は、自分が最後のデータかどうかによって、切り替えています。
lst_cre関数で、各種双方向リストのポインタを設定し、lst_dsp関数で、階層表示を行っています。
lst_dsp関数は再帰的に呼びだされます。
以下に、サンプルソースを示します。
list.pl |
---|
#!/usr/local/bin/perl # # list edit: 双方向リスト編集処理 # prnt: 親ノード (parent node) # chid: 子ノード (child node) # next: 次ノード (next node) # prev: 前ノード (previous node) # data format: データファイルの形式 # date: 日時 (MM/DD hh:mm) # prnt: 親データ行番号 # user: ユーザ名 # data: データ内容 # { my ($file, $buf); my ($line, $num); # initialize $pre = ''; $pre1a = '├'; $pre1b = '└'; $pre2a = '│'; $pre2b = ' '; # data file read $file = "data.txt"; if (!open(FILE_IN, "<$file")) { printf("File %s open error.\n",$file); exit(1); } $prnt[0] = 0; $chid[0] = 0; for ($line = 1; $buf = <FILE_IN>; $line++) { chomp($buf); ($date[$line], $prnt[$line], $user[$line], $data[$line]) = split(',', $buf); } $num = $line; close(FILE_IN); # 最新日付順表示 for ($line = $num - 1; $line > 0; $line--) { printf("%-30s %-11s %-10s\n", $data[$line], $date[$line], $user[$line]); } # create tree index for ($line = 0; $line < $num; $line++) { &lst_cre($line, $prnt[$line]); } # # tree disp # for ($line = 0; $line < $num; $line++) { # printf("%5d,%5d,%5d,%5d,%5d,%5s,%10s,%20s\n", $line, # $prnt[$line], $chid[$line], $next[$line], $prev[$line], # $date[$line], $user[$line], $data[$line]); # } # 階層表示 &lst_dsp($chid[0], $pre); } sub lst_cre { # create list pointer my ($idx, $pos) = @_; my ($insnext, $insprev); if ($chid[$pos] == 0) { # 子がない場合 $chid[$pos] = $idx; $next[$idx] = $idx; $prev[$idx] = $idx; } else { # 子がある場合 $insnext = $chid[$pos]; $insprev = $prev[$insnext]; $next[$idx] = $insnext; $prev[$idx] = $insprev; $next[$insprev] = $idx; $prev[$insnext] = $idx; } $chid[$idx] = 0; } sub lst_dsp { # list display my ($idx, $pre) = @_; my ($i); #printf("lst_dsp(%d)\n", $idx); $i = $idx; do { if ($next[$i] == $chid[$prnt[$i]]) { # 最後のノード $pre1 = $pre.$pre1b; $pre2 = $pre.$pre2b; } else { # 最後以外のノード $pre1 = $pre.$pre1a; $pre2 = $pre.$pre2a; } printf("%-40s %-11s %-10s\n", $pre1.$data[$i], $date[$i], $user[$i]); if ($chid[$i] != 0) { if ($next[$i] == $chid[$prnt[$i]]) { # 最後のノード } else { # 最後以外のノード } &lst_dsp($chid[$i], $pre2); } $i = $next[$i]; } until ($i == $idx); } |
list.plには、以下のデータを使用しています。
$data[0]: ルートノード @data: データ格納用配列(データは1オリジン) $pre*: 階層表示用のプリフィックス記号 $prnt: 親ノードへのポインタ(parent node) $chid: 子ノードへのポインタ(child node) $next: 次ノードへのポインタ(next node) $prev: 前ノードへのポインタ(previous node) |
data.txtの形式は以下の項目があります。 データは、CSV形式で格納してあります。
date: 日時 (MM/DD hh:mm) prnt: 親データ行番号 user: ユーザ名 data: データ内容 |
実際のデータ内容は、以下のような内容となります。
data.txt |
---|
09/01 10:10,000,joe,text001 09/02 10:10,001,sys,text002 09/03 10:10,002,joe,text003 09/04 10:10,001,sys,text004 09/05 10:10,004,joe,text005 09/06 12:34,005,sys,text006 09/07 12:34,000,joe,text007 09/08 12:34,007,sys,text008 09/09 12:34,001,joe,text009 09/10 12:34,003,sys,text010 |