200 likes | 372 Views
アルゴリズムとデータ構造. 2012 年 6 月 14 日 酒居敬一 ( sakai.keiichi@kochi-tech.ac.jp ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2012/index.html. ルートノード. エッジ. ノード. 末端ノード. 木構造. ルートとそれ以外の ノードにちょうど1つだけ の経路しか存在しない. 201 2 年6月14日. データ. データ. データ. データ. データ. データ. データ. 左. 左. 左. 左. 左. 左.
E N D
アルゴリズムとデータ構造 2012年6月14日 酒居敬一(sakai.keiichi@kochi-tech.ac.jp) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2012/index.html
ルートノード エッジ ノード 末端ノード 木構造 ルートとそれ以外の ノードにちょうど1つだけ の経路しか存在しない
2012年6月14日 データ データ データ データ データ データ データ 左 左 左 左 左 左 左 右 右 右 右 右 右 右 二分木 • 各ノードが持てる子の数が高々2である木 • 二分探索木として、順序木を使う
二分探索木 • 二分木を次のルールで作成 • 親よりも小さい値を持つ子は左 • 親よりも大きい値を持つ子は右 29 20 32 30 14 24 48 7 19 21 31
29 20 32 14 24 30 48 7 19 21 31 ノードの探索 ノード数をNとすると O(log N) の計算量で探索できる 木が偏っているときは 最悪O(N)になるが…
最悪の形(二分探索木) 48 32 31 ノード数をNとすると O(log N) の計算量で探索できる 木が偏っているときは 最悪O(N)になるが… 30 29 24 21 20 木の深さをバランスよく したものを平衡木という 平衡木については次回述べる 19 14 7
public class MyData implements Comparable { public MyData(int anId, Object aData) { this.id = anId; this.data = aData; } public int compareTo(Object aTarget) { int targetId = ((MyData)aTarget).getId(); if(this.id == targetId){ return 0; } if(this.id > targetId){ return 1; } return -1; } public Object getData() { return this.data; } public int getId() { return (this.id); } public String toString() { return "(" + this.id + "'" + this.data + ")"; } private Object data; // 木に保持したいデータ private int id; // 順序付けのための数値 } 二分探索木のノードに置くデータを表すクラス interface Comparableには、compareTo()というメソッドが存在する。 public int compareTo(Object o); thisオブジェクトを指定されたオブジェクトと比較し、 小さい場合は負の整数、 等しい場合はゼロ、 大きい場合は正の整数、 をそれぞれ返す。
二分探索木のノードを表すクラス ノードに関する操作 public class MyNode { public MyNode(MyData aData) { this.data = aData; } public MyData getData() { return this.data; } public MyNode getLeft() { return this.left; } public MyNode getRight() { return this.right; } public void setLeft(MyNode aNode) { this.left = aNode; } public void setRight(MyNode aNode) { this.right = aNode; } public String toString() { MyData leftdata = null; MyData rightdata = null; if(null != this.left){ leftdata = this.left.getData(); } if(null != this.right){ rightdata = this.right.getData(); } return ("{"+leftdata+","+this.data+","+rightdata+"}"); } private MyData data; private MyNode left; private MyNode right; }
17 を挿入したい 20 29 > > 17 17 29 だから… だから… 20 32 14 < 17 14 24 30 48 だから… 7 19 21 31 19 > 17 だから… 二分探索木へノードの挿入(73ページ以降で詳しく) 29 20 14 19 17
public class BinarySearchTree { public BinarySearchTree() { this.root = null; } private MyNode root; } 二分探索木へ挿入するメソッド public void insert(MyData aData) { MyNode newNode = new MyNode(aData); if(null == this.root){ this.root = newNode; return; } MyNode currentNode = this.root; while(true){ if(0 < currentNode.getData().compareTo(aData)){ if(null == currentNode.getLeft()){ currentNode.setLeft(newNode); return; } currentNode = currentNode.getLeft(); }else{ if(null == currentNode.getRight()){ currentNode.setRight(newNode); return; } currentNode = currentNode.getRight(); } } } 二分探索木を表すクラス
public MyNode search(MyData aData) { if(null == this.root){ return null; } MyNode currentNode = this.root; while(true){ if(0 == currentNode.getData().compareTo(aData)){ return currentNode; } if(0 < currentNode.getData().compareTo(aData)){ if(null == currentNode.getLeft()){ return null; } currentNode = currentNode.getLeft(); }else{ if(null == currentNode.getRight()){ return null; } currentNode = currentNode.getRight(); } } } • ループの終了条件 • 末端に達した • 一致するデータを発見 ループによる二分探索
Tail Recursion(末尾再帰) public MyNode searchRecursive(MyData aData) { return searchR(aData, this.root); } private MyNode searchR(MyData aData, MyNode aRoot) { if(null == aRoot){ // 再帰を終了できるかどうか(末端に到達?)の判定 return null; } int result = aRoot.getData().compareTo(aData); if(0 == result){ // 一致するデータを見つけたかどうかの判定 return aRoot; } return searchR(aData, (0 < result)? aRoot.getLeft(): aRoot.getRight()); } このような再帰呼び出しはループと相互に変換可能
29 20 32 削除したい 24 14 24 30 48 そのまま削除 削除したい 31 7 19 21 31 削除 子をもたない、または1つだけ持つノードの削除
public boolean remove(MyData aData) { if(null == this.root){ return false; } MyNode parentNode = this.root; MyNode currentNode = this.root; boolean inLeftSubTree = false; while(0 != currentNode.getData().compareTo(aData)){ parentNode = currentNode; if(0 < currentNode.getData().compareTo(aData)){ currentNode = currentNode.getLeft(); inLeftSubTree = true; }else{ currentNode = currentNode.getRight(); inLeftSubTree = false; } if(null == currentNode){ return false; } } /* 削除処理本体は次以降のページで */ currentNode.setLeft(null); currentNode.setRight(null); return true; } 削除対象を探す
if((null == currentNode.getLeft()) && (null == currentNode.getRight())){ if(currentNode == this.root){ this.root = null; }else if(inLeftSubTree){ parentNode.setLeft(null); }else{ parentNode.setRight(null); } }else if(null == currentNode.getRight()){ if(currentNode == this.root){ this.root = currentNode.getLeft(); }else if(inLeftSubTree){ parentNode.setLeft(currentNode.getLeft()); }else{ parentNode.setRight(currentNode.getLeft()); } }else if(null == currentNode.getLeft()){ if(currentNode == this.root){ this.root = currentNode.getRight(); }else if(inLeftSubTree){ parentNode.setLeft(currentNode.getRight()); }else{ parentNode.setRight(currentNode.getRight()); } } /* 続く… */ 親 親 親 子 子 親 親 子 子
29 削除したい 削除 20 20 32 14 24 30 48 7 19 21 31 子を2つ持つノードの削除
else{ MyNode minimumNode = this.getMinimumNode(currentNode.getRight()); this.remove(minimumNode.getData()); minimumNode.setLeft(currentNode.getLeft()); minimumNode.setRight(currentNode.getRight()); if(currentNode == this.root){ this.root = minimumNode; }else if(inLeftSubTree){ parentNode.setLeft(minimumNode); }else{ parentNode.setRight(minimumNode); } } • 右部分木から最小の(最左)のノードを取り出す • 削除対象と付け替え 親 親 private MyNode getMinimumNode(MyNode aLocalRootNode){ if(null == aLocalRootNode){ return null; } MyNode currentNode = aLocalRootNode; while(true){ if(null == currentNode.getLeft()){ return currentNode; } currentNode = currentNode.getLeft(); } } 子 子 子 子 孫 孫
29 20 32 14 24 30 48 7 19 21 31 行きがけ順(pre-order)の走査 • 二分木を次のルールで走査 • 自分を訪ねる • 自分の左部分木をたどる • 自分の右部分木をたどる 29 20 32 public void printTreePreOrder() { System.out.println(this.traversePreOrder(this.root)); } private String traversePreOrder(MyNode aLocalRootNode) { if(null == aLocalRootNode){ return ""; } StringBuffer treeRepresentation = new StringBuffer(); treeRepresentation.append(aLocalRootNode + System.getProperty("line.separator")); treeRepresentation.append(this.traversePreOrder(aLocalRootNode.getLeft())); treeRepresentation.append(this.traversePreOrder(aLocalRootNode.getRight())); return treeRepresentation.toString(); } 14 24 30 48 7 19 21 31
29 20 32 14 24 30 48 7 19 21 31 通りがけ順(in-order)の走査 • 二分木を次のルールで走査 • 自分の左部分木をたどる • 自分を訪ねる • 自分の右部分木をたどる • 二分探索木を走査すると • 整列済みデータが得られる 29 20 32 public void printTreeInOrder() { System.out.println(this.traverseInOrder(this.root)); } private String traverseInOrder(MyNode aLocalRootNode) { if(null == aLocalRootNode){ return ""; } StringBuffer treeRepresentation = new StringBuffer(); treeRepresentation.append(this.traverseInOrder(aLocalRootNode.getLeft())); treeRepresentation.append(aLocalRootNode +System.getProperty("line.separator")); treeRepresentation.append(this.traverseInOrder(aLocalRootNode.getRight())); return treeRepresentation.toString(); } 14 24 30 48 7 19 31 21
29 20 32 14 24 30 48 7 19 21 31 帰りがけ順(post-order)の走査 • 二分木を次のルールで走査 • 自分の左部分木をたどる • 自分の右部分木をたどる • 自分を訪ねる 29 20 32 public void printTreePostOrder() { System.out.println(this.traversePostOrder(this.root)); } private String traversePostOrder(MyNode aLocalRootNode) { if(null == aLocalRootNode){ return ""; } StringBuffer treeRepresentation = new StringBuffer(); treeRepresentation.append(this.traversePostOrder(aLocalRootNode.getLeft())); treeRepresentation.append(this.traversePostOrder(aLocalRootNode.getRight())); treeRepresentation.append(aLocalRootNode + System.getProperty("line.separator")); return treeRepresentation.toString(); } 14 24 30 48 7 19 21 31