Transcript ppt

コンピュータ基礎演習
ー双方向リストー
岩井 儀雄
[email protected]
復習:リスト構造(1)

連結リストとは?

リストに含まれる各要素をつなぎ合わせた
もの
データ
データ
データ
DATA[0]
DATA[1]
DATA[2]
…
DATA[N]
データ
未使用
データ
…
データ
IDX[0]
IDX[1]
IDX[2]
…
IDX[N]
2
-1
N
…
EOD
復習:リスト構造(2)

構造体を用いたリストの表現
struct CELL {
struct CELL *next; /* 次のデータへのポインタ */
DATATYPE data;
/* 格納されるデータ */
};
復習:リスト構造(3)

連結リストの操作









要素の挿入 (insert)
要素の削除 (erase)
リストの連結(concatenate)
リストの分断 (split)
要素の探索 (find)
空リストの生成 (create)
要素数 (size)
先頭要素を見る (front)
最後尾要素を見る(tail)
復習:リスト構造(4)

連結リストの派生データ構造
連結リスト linked list
header
A
B
C
B
C
循環リスト circular list
header
A
後ろに移動し続ければ,先頭に戻れる
連結リストの問題点

各セルは次のセルへのポインタのみを持つ.


セル間の移動は1方向のみ
前後に自由に移動できるリスト構造

Doubly-linked list(双方向連結リスト,双方向
リスト,2重連結リスト)
双方向リスト
doubly-linked list
Doubly-linked list
header
A

C
各セルは2つのポインタを持つ



B
次のセルへのポインタ
前のセルへのポインタ
前後のセルへのポインタを持っているので前後に移動可能
双方向リスト
doubly-linked list(2)
リストの末尾も分かっていると tail() などの実装が容易
header
A
B
C
header を利用する
最後のセルが header を指すようにする
header の前のセルを指すポインタが最後のセルを指すようにする
双方向リスト
doubly-linked list(3)
空リストの表現
header
header の前後のポインタが header 自身を指す.
双方向リストの利点

利点



双方向リストでは,セルは前後のポインタを持っ
ているため,制約を受けず挿入,削除などの操作
が可能
一方,連結リストでは,一般に挿入,削除などの
操作に制約がある.
例)挿入の場合


連結リストでは,指定したセルの直後には挿入で
きるが直前には挿入できない
連結リストでは,直前のセルへのポインタを持つ
などの工夫が必要
双方向リストの欠点


連結リストと比べるとポインタを1つ余計に持
つ必要がある
取り扱うデータが小さい場合(例えば,整数),
2このポインタを持つため記憶容量に対して
オーバーヘッドが大きい(データサイズが大き
い場合は問題ではない)
トレードオフ問題
操作の自由さ⇔余分なデータサイズ
最近は,記憶容量が向上してきたので操作の自由さ(設計の自由さ)
が優先されつつある
双方向リストのセル(C言
語)
struct CELL {
struct CELL *prev; /* 前のデータへのポインタ */
struct CELL *next; /* 次のデータへのポインタ */
DATATYPE data;
/* 格納されるデータ */
};
空リスト(要素)生成の実装
(C言語)
struct CELL *create(DATATYPE data) {
struct CELL *p = (struct CELL*)malloc(sizeof(struct CELL));
p->prev = p;
p->next = p;
p->data = data;
return p;
}
prev
p
next
data
要素挿入の実装

指定された要素の次の位置に挿入


連結リストとほぼ同様
指定された要素の前の位置に挿入

前のセルに移動して,次の要素に挿入
B
header
A
C
要素挿入指定された要素の次
の位置に挿入
void insert( struct CELL *pos, struct CELL *val ) {
val->next = pos->next;
val->next->prev = val;
pos->next = val;
val->prev = pos;
}
val
pos
B
header
A
C
要素挿入指定された要素の次
の位置に挿入
void insert( struct CELL *pos, struct CELL *val ) {
val->next = pos->next;
val->next->prev = val;
pos->next = val;
val->prev = pos;
}
val
pos
B
header
A
C
要素挿入指定された要素の次
の位置に挿入
void insert( struct CELL *pos, struct CELL *val ) {
val->next = pos->next;
val->next->prev = val;
pos->next = val;
val->prev = pos;
}
val
pos
B
header
A
C
要素挿入指定された要素の次
の位置に挿入
void insert( struct CELL *pos, struct CELL *val ) {
val->next = pos->next;
val->next->prev = val;
pos->next = val;
val->prev = pos;
}
val
pos
B
header
A
C
要素挿入指定された要素の次
の位置に挿入
void insert( struct CELL *pos, struct CELL *val ) {
val->next = pos->next;
val->next->prev = val;
pos->next = val;
val->prev = pos;
}
val
pos
B
header
A
C
指定された要素の前の位置に
挿入

前のセルに移動して,次の要素に挿入
void insert_before( struct CELL *pos, struct CELL *val ) {
insert( pos->prev, val );
}
挿入ポイントを前のセルに移動
要素削除の実装

指定された要素の次を削除


連結リストとほぼ同様
指定された要素を削除する
指定された要素を削除する
struct CELL *erase( struct CELL *pos
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
pos->prev = pos->next = pos;
return pos;
}
) {
pos
B
header
A
C
指定された要素を削除する
struct CELL *erase( struct CELL *pos
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
pos->prev = pos->next = pos;
return pos;
}
) {
pos
B
header
A
C
指定された要素を削除する
struct CELL *erase( struct CELL *pos
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
pos->prev = pos->next = pos;
return pos;
}
) {
pos
B
header
A
C
指定された要素を削除する
struct CELL *erase( struct CELL *pos
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
pos->prev = pos->next = pos;
return pos;
}
) {
pos
B
header
A
C
指定された要素を削除する
struct CELL *erase( struct CELL *pos
pos->prev->next = pos->next;
pos->next->prev = pos->prev;
pos->prev = pos->next = pos;
return pos;
}
) {
B
header
A
C
tail の実装

リストの最後尾を返す


循環しているので header を見分ける必要
がある.
Header が分かれば,その前のセルへのポ
インタでよい.
struct CELL *tail( struct CELL *header ) {
return header->prev; /* 事前条件として header は
リストのヘッダであること */
}
多重リスト構造
multi link structure


セルに2つ以上のポインタを持たせ,
同時に複数のリストに所属できるよう
にしたデータ構造
例)大学での学生の成績を管理する表

2次元配列でも表現可能
short seiseki[MAX_GAKUSEI][MAX_KAMOKU];
seiseki[x][y]
学生番号 x の学生の課目 y の成績
未受講 -1
2次元配列を用いた場合
0
0
90
1
72
2
3
学 4
籍
番
号
1
2
3
4
83
90
52
53
25
23
44
55
課目数
….
例)成績管理表

学生総数2,000人,授業課目数400,平
均受講課目数 10
平均履修者数 → 2000x10/400 = 50名
配列の大きさ → 2000x400xsizeof(short) = 約1.6MB
データ量
→ 2000x 10xsizeof(short) = 約40KB
配列の使用効率 → 2.5%(無駄が多い)
疎な配列を効率良く表現できる → 多重リスト構造
多重リスト構造

基本アイデア

データのある要素をセルで表現し,それら
をポインタでつなぐ
上のセル
左のセル
DATA
下のセル
右のセル
コンピュータ基礎演習
ー木構造,二分木ー
岩井 儀雄
[email protected]
木構造

階層構造を表すためのデータ構造

一般世界


会社組織,家系図(祖父母,父母,兄弟),
本の構成(章,節,段落)
計算機の世界


階層化ディレクトリ(UNIX)
コンパイラによる構文解析
木 (tree)
レベル 0
レベル 1
木の深さ
根 root … 最上位ノード
節 node
レベル 2
レベル 3
レベル 4
枝 branch
葉 leaf…下位ノードがない
部分木 (subtree)
ある節を根とする木
部分木 (subtree)
ある節を根とする木
木に関する用語





親(parent)
子(child)
兄弟(sibling)
先祖(ancestor)
子孫(descendant)
このノードに注目
木に関する用語





親(parent)
子(child)
兄弟(sibling)
先祖(ancestor)
子孫(descendant)
このノードに注目
木に関する用語





親(parent)
子(child)
兄弟(sibling)
先祖(ancestor)
子孫(descendant)
自分自身は先祖であり
子孫である.
木の再帰的定義


単一の節はそれ自体が木である.この節はこの木の根でも
ある.
k個の木T1~Tkがあり,それぞれの根を節n1~nkとする.節n
を節n1~nkの親にすると節nを根とする新しい木Tが得られ
る.このとき,木T1~Tkは,木Tの部分木(subtree)であると
いう.部分木の根n1~nkは節nの子であるという.
n
単一の節からなる木
n1
n2
T1
T2
nk
…
Tk
部分木から新しい木を構成
節の順序

節の子には左から右に順序をつける
a
a
b
c
兄
弟
構造は同じだが
異なる木
c
b
兄
弟
ADT Tree





節の親を返す(parent)
節の最も左の子を返す(leftmost_child)
節の次の弟を返す(right_sibling)
根を返す(root)
節を作成する(create)
2分木(binary tree)

定義


空の木は2分木である
次のいずれかを満たす節のみからなる木は,
2分木である.




子を持たない
左の子を1つだけもつ
右の子を1つだけもつ
左の子と右の子を1つずつもつ
2分木
2分木では右の子と左の子を区別する
例)下の木は互いに異なる

a
b
d
a
c
b
a
b
c
d
右の子
d
左の子
普通の木(左右の子を区別しない) 2分木(左右の子を区別する)
c
左部分木と右部分木

左部分木


左の子を根とする木
右部分木

右の子を根とする木
左部分木
a
b
c
d
右部分木
配列を用いた2分木の実装
(C言語)

N番目の節の左の子のデータは,2N+1番目,
右の子のデータは2N+2番目に格納
#define MAXNODES 7
struct NODE { DATATYPE data; };
struct NODE[MAXNODES];
a
b
c
d
[0]
[1]
[2]
a
b
c
[3]
[4]
[5]
[6]
d
木の最大深さLとすると節の最大数は2L+1-1
配列を用いた2分木の実装
(C言語)(2)

左の子,右の子の添え字(インデックス)を格納
#define MAXNODES 7
struct NODE {
DATATYPE data;
int leftchild, rightchild; };
struct NODE[MAXNODES];
a
b
c
d
[0]
[1]
[2]
[3]
[4]
[5]
[6]
data
a
b
c
d
leftchild
1
0
0
0
0
0
0
rightchild
2
3
0
0
0
0
0
木の最大深さLとすると節の最大数は2L+1-1
配列による2分木の
実装の利点,欠点

利点


最大深さが分かっている場合,実装が簡単
欠点


最大深さが分からない場合,最大見積もり
深さを利用して設計するので,記憶装置の
使用効率が低下することがある
挿入削除を繰り返すと,空き領域の探索,
データの移動が伴うため,実行効率が悪い
構造体へのポインタを利用し
た2分木の実装(C言語)
struct NODE {
DATATYPE data;
struct NODE *leftchild; /* 左の子へのポインタ */
struct NODE *rightchild; /* 右の子へのポインタ */
struct NODE *parent;
/* 親へのポインタ
*/
};
struct NODE *parent
struct NODE
struct NODE *leftchild
struct NODE
struct NODE *rightchild
struct NODE
ADT Tree の実装(C言語)
struct NODE *parent( struct NODE *node ) {
return node->parent; /* ノードの親を返す */
}
struct NODE *leftmost_child(struct NODE *node) {
return node->leftchild; /* 左の子を返す */
}
struct NODE *right_sibling(struct NODE *node) {
if (node->parent != 0) { /* 親がいれば */
if (node->parent->leftchild != node) return NULL;
else return node->parent->rightchild; /* 右の子を返す */
} else return NULL;
}
struct NODE *root(struct NODE *node) {
struct NODE *p = node;
while (p->parent != NULL) p = p->parent; /* 親がいなくなるまで */
return p;
}
ADT Tree の実装(2)(C言
語)
/* 節を作成する */
struct NODE *create( DATATYPE val, struct NODE *par,
struct NODE *left, struct NODE *right ) {
struct NODE *p = (struct NODE *)malloc(sizeof(struct NODE));
p->data = val;
/* DATA の設定 */
p->parent = par; /* 親の登録 */
p->leftchild = left; left->parent = p;
/* 左の子の登録 */
p->rightchild = right; right->parent = p; /* 右の子の登録 */
}
/* 単一の節からなる木 T1, T2 の作成コード */
struct NODE *T1 = create( val, NULL, NULL, NULL );
struct NODE *T2 = create( val, NULL, NULL, NULL );
/* T1, T2 を部分木に持つ木 T の作成コード */
struct NODE *T = create( val, NULL, T1, T2 );
木のなぞり(traverse)

a


木の節を1つ残らず訪問
立ち寄った順に順序づけ可能
3種類のなぞりの方法

b
c


d
行きがけ順 pre-order
通りがけ順 in-order
帰りがけ順 post-order
行きがけ順

a
b
c
まず最初に節に立ち寄ってか
ら部分木をなぞる
1. 根に立ち寄る
2. 左部分木をなぞる
3. 右部分木をなぞる
a→b→d→c
d
行きがけ順

a
b
節
c
d
左部分木 右部分木
節aに立ち寄る
行きがけ順

a
節
b
節

c
d 右部分木
左部分木 右部分木
節aに立ち寄る
左部分木をなぞる
行きがけ順

a
節

節aに立ち寄る
左部分木をなぞる


節
b
c
節
d 右部分木
左部分木 右部分木

節bに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる
行きがけ順

a
節

節aに立ち寄る
左部分木をなぞる


節
b
c 節

節bに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる


節
d 右部分木


左部分木 右部分木
節dに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる(なし)
右部分木をなぞる
行きがけ順

a
節

節aに立ち寄る
左部分木をなぞる



節
b
節bに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる

c 節


節

d 右部分木
右部分木をなぞる


左部分木 右部分木
節dに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる(なし)

節cに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる(なし)
通りがけ順
a
b
c
1. 左部分木をなぞる
2. 根に立ち寄る
3. 右部分木をなぞる
b→d→a→c
d
通りがけ順

a
b
c
d
左部分木
右部分木
左部分木をなぞる
通りがけ順

左部分木をなぞる

a


b
c
d
右部分木
左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる
通りがけ順

左部分木をなぞる

a


左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる


b
c
d

左部分木をなぞる(なし)
節dに立ち寄る
右部分木をなぞる(なし)
通りがけ順

左部分木をなぞる

a


左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる


b
c


d
左部分木をなぞる(なし)
節dに立ち寄る
右部分木をなぞる(なし)
節aに立ち寄る
通りがけ順

左部分木をなぞる

a


左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる


b
c



d
左部分木をなぞる(なし)
節dに立ち寄る
右部分木をなぞる(なし)
節aに立ち寄る
右部分木をなぞる
通りがけ順

左部分木をなぞる

a


左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる


b
c



d
左部分木をなぞる(なし)
節dに立ち寄る
右部分木をなぞる(なし)
節aに立ち寄る
右部分木をなぞる



左部分木をなぞる(なし)
節cに立ち寄る
右部分木をなぞる(なし)
通りがけ順

左部分木をなぞる

a


左部分木をなぞる(なし)
節bに立ち寄る
右部分木をなぞる


b
c



d
節aに立ち寄る
右部分木をなぞる


b→d→a→c
左部分木をなぞる(なし)
節dに立ち寄る
右部分木をなぞる(なし)

左部分木をなぞる(なし)
節cに立ち寄る
右部分木をなぞる(なし)
帰りがけ順
a
b
c
1. 左部分木をなぞる
2. 右部分木をなぞる
3. 根に立ち寄る
d→b→c→a
d
帰りがけ順

a
b
c
d
左部分木
右部分木
左部分木をなぞる
帰りがけ順

左部分木をなぞる

a

b
c
d
左部分木をなぞる(なし)
右部分木をなぞる
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる


b
c
d

左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる


b
c


d
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る
節bに立ち寄る
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる


b
c



d
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る
節bに立ち寄る
右部分木をなぞる
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる



b
c


節bに立ち寄る
右部分木をなぞる

d
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る


左部分木をなぞる(なし)
右部分木をなぞる(なし)
節cに立ち寄る
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる



b
c





節bに立ち寄る
右部分木をなぞる

d
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節cに立ち寄る
節aに立ち寄る
帰りがけ順

左部分木をなぞる

a

左部分木をなぞる(なし)
右部分木をなぞる



b
c



d→b→c→a


節bに立ち寄る
右部分木をなぞる

d
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節dに立ち寄る
左部分木をなぞる(なし)
右部分木をなぞる(なし)
節cに立ち寄る
節aに立ち寄る
木のなぞりの実装

再帰呼び出しのできる言語なら,手順
をそのままコーディングするだけで良
い.
2分木のなぞりの実装
(C言語)
void preorder(struct NODE *p) { /* 行きがけ順 */
if (p == NULL) return;
/* 立ち寄ったときの処理をいれる */
preorder( p->leftchild );
preorder( p->rightchild );
}
void inorder(struct NODE *p) { /* 通りがけ順 */
if (p == NULL) return;
inorder( p->leftchild );
/* 立ち寄ったときの処理をいれる */
inorder( p->rightchild );
}
void postorder(struct NODE *p) { /* 帰りがけ順 */
if (p == NULL) return;
postorder( p->leftchild );
postorder( p->rightchild );
/* 立ち寄ったときの処理をいれる */
}
コンピュータ基礎演習
ー二分探索木ー
岩井 儀雄
[email protected]
2分探索木
(binary search tree)


任意の節xで,左部分木TL(x)に含
まれる要素Nは節Xの要素N(x)よ
り小さい.
右部分木TR(x)に含まれる要素N
は節xの要素N(x)より大きい.
例) a < b < c <d
c
右部分木
左部分木
a
d
b

 x, l, r l  T L ( x), r  T R ( x)  N (l) < N ( x)  N ( x) < N (r)
2分探索木の利点


順序 < が定義された集合を表現可
集合への追加,削除,探索などの操作
が O(log n) で可能
Strict Partial Order ‘<’

非反射律: irreflexivity

推移律:transivity

 x x < x must be false

 x,y,z x < y  y < z  x < z
上の2つの条件を満たす2項関係を
strict partial order (狭義半順序)という
同値 equivalence‘~’

反射律: reflexivity

対称律: symmetry

推移律: transitivity
 x x ~ x

x x ~ y  y ~ x

x x ~ y  y ~ z  x ~ z
2分探索木の操作

探索 (search)


挿入 (insert)


指定された要素と等価な要素の位置を返す
指定された要素と等価な要素を2分探索木
の条件を満たすように挿入する
削除 (erase)

指定された要素と等価な要素を2分探索木
の条件を満たすように削除する
2分探索木の実装(C言語)
struct NODE {
DATATYPE data;
struct NODE *leftchild; /* 左の子へのポインタ */
struct NODE *rightchild; /* 右の子へのポインタ */
};
2分探索木で必要な演算
データ要素を順序づける演算 ‘<’ (strict partial order)
データが等価かどうかの演算 ‘~’ (equivalence)
int lessthan(DATATYPE a, DATATYPE b); /* 偽なら0,真なら!0 */
int equal(DATATYPE a, DATATYPE b);
/* 偽なら0,真なら!0 */
探索(search)の実装




struct
if
if
if
現在の節の要素と等しい→探索終わり
現在の節の要素より小さい→左部分木を探索
現在の節の要素より大きい→右部分木を探索
節がない→要素なし(NULLを返す)
NODE *search(DATATYPE data,struct NODE *node) {
(node == NULL) return NULL; /* データなし */
(equal(data,node->data)) return node; /* データ発見 */
(lessthan(data,node->data))
return search(data,node->leftchild); /* 左部分木を探索 */
else return search(data,node->rightchild); /* 右部分木を探索 */
}
挿入(insert)の実装(C言語)
struct NODE *create(DATATYPE data) { /* 新規ノード作成 */
struct NODE *p = (struct NODE *)malloc(sizeof(struct NODE));
node->data = data; node->leftchild = node->rightchild = NULL;
return p;
}
struct
if
if
if
NODE *insert(DATATYPE data,struct NODE *node) {
(node == NULL) return create(data);
/* 新規データ */
(equal(data,node->data)) return node; /* 既にデータあり */
(lessthan(data,node->data))
node->leftchild = insert( data, node->leftchild );
else node->rightchild = insert( data, node->rightchild );
return node;
}
削除(erase)の実装


削除すべき要素が葉ならば削除
削除すべき要素が内部節ならば問題


右部分木の最小値をその削除すべき要素と置き換える.
右部分木の最小値があった位置は1つ以下の子なので削除可
d
この節を単純に削除
すると木が壊れる
b
a
e
c
この節は葉であるから
単純に削除OK
削除(erase)の実装


削除すべき要素が葉ならば削除
削除すべき要素が内部節ならば問題


右部分木の最小値をその削除すべき要素と置き換える.
右部分木の最小値があった位置は1つ以下の子なので削除可
d
右部分木の最小値と入替
a
c
e
d
この節は葉であるから
単純に削除OK
右部分木の最小値をその削除すべき
要素と置き換える

2分探索木の条件

 x, l, r l  T L ( x), r  T R ( x)  N (l) < N ( x)  N ( x) < N (r)
削除対象の節を x,右部分木TRの最小値を m とすると,
N( x) < m < N (r),  r  T R ( x)

 l, r l  T L ( x), r  T R ( x)  N (l) < m  m < N (r)
2分探索木の条件を壊さないことが分かる
木の最小値を削除しその値を
返す


右部分木の根を渡せば,右部分木の最小値を削除できる.
返り値で削除対象節の値を書き換える
data
min
最小値の左の子はいない
data
木の最小値を削除しその値を
返す


右部分木の根を渡せば,右部分木の最小値を削除できる.
返り値で削除対象節の値を書き換える
data
min
最小値の左の子はいない
data
木の最小値を削除しその値を
返す(C言語)
struct NODE *erasemin(struct NODE **node) {
if ((*node)->leftchild == NULL) {
struct NODE *ret = *node;
/* 返り値 */
*node = (*node)->rightchild; /* ポインタ変更 */
ret->rightchild = NULL;
return ret; /* 削除した節を返す */
} else
return erasemin( &((*node)->leftchild) );
}
要素の削除(C言語)
struct NODE *erase( DATATYPE data, struct NODE **node ) {
struct NODE *x = *node;
if (x == NULL) return NULL;
if (equal(data,x->data)) { /* 等価なデータ発見,削除 */
/* 子の有無を調査,1つだけならそのままつなぐ */
if (x->leftchild == NULL) *node = x->rightchild;
else if (x->rightchild == NULL) *node = x->leftchild;
else { /* 両側に子がある場合は erasemin を呼ぶ */
x = erasemin( &x->rightchild );
(*node)->data = x->data;
}
x->leftchild = x->rightchild = NULL;
return x;
} else if (lessthan(data,x->data))
return erase( data, &x->leftchild) ); /* 左部分木を探索 */
else
return erase( data, &x->rightchild) ); /* 右部分木を探索 */
}