アルゴリズムとデータ構造1

アルゴリズムとデータ構造
2010年7月5日
酒居敬一([email protected])
http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2010/index.html
木構造(48ページ)
ルートとそれ以外の
ノードにちょうど1つだけ
の経路しか存在しない
ルートノード
エッジ
ノード
末端ノード
二分探索木(73ページ)
二分木を次のルールで作成
•親よりも小さい値を持つ子は左
•親よりも大きい値を持つ子は右
29
20
14
7
24
19
21
32
30
48
31
プログラムの例では、数値のほかにObjectを置けるようにする
public class MyData implements Comparable { 二分探索木のノードに置くデータを表すクラス
private MyData() {
}
public MyData(int anId, Object aData) {
this.id = anId;
public int compareTo(Object aTarget) {
this.data = aData;
int targetId = ((MyData)aTarget).getId();
}
if(this.id == targetId){
return 0;
}
if(this.id > targetId){
interface Comparableには、
return 1;
compareTo()というメソッドが存在する。
}
return -1;
public int compareTo(Object o);
}
public Object getData() {
return this.data;
thisオブジェクトを指定されたオブジェ
}
クトと比較し、
public int getId() {
小さい場合は負の整数、
return (this.id);
等しい場合はゼロ、
}
大きい場合は正の整数、
public String toString() {
をそれぞれ返す。
return "(" + this.id + "'" + this.data + ")";
}
private Object data;
// 木に保持したいデータ
private int id;
// 順序付けのための数値
}
public class MyNode {
private 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;
二分探索木へノードの挿入
(76ページ)
17 を挿入したい
29 > 17
29
だから…
20 > 17
20
だから…
14 < 17
14
24
32
30
48
だから…
19
7
19 > 17
だから…
17
21
31
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
(末尾再帰 84ページ)
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());
}
このような再帰呼び出しはループと相互に変換可能
子をもたない、または
1つだけ持つノードの削除(82ページ)
29
20
削除したい
削除
24
14
7
19
21
32
30
48
31
そのまま削除
削除したい
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());
}
}
/* 続く… */
親
親
親
子
子
親
子
親
子
子を2つ持つノードの削除
29
削除したい
削除
20
14
7
24
19
21
32
30
48
31
}
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);
1. 右部分木から最小の(最左)
}else{
のノードを取り出す
parentNode.setRight(minimumNode);
2. 削除対象と付け替え
}
親
子
子
孫
親
子
子
孫
private MyNode getMinimumNode(MyNode aLocalRootNode) {
if(null == aLocalRootNode){
return null;
}
MyNode currentNode = aLocalRootNode;
while(true){
if(null == currentNode.getLeft()){
return currentNode;
}
currentNode = currentNode.getLeft();
}
}
行きがけ順(pre-order)の走査
(52ページ)
二分木を次のルールで走査
1. 自分を訪ねる
2. 自分の左部分木をたどる
3. 自分の右部分木をたどる
29
20
14
7
24
19
21
32
30
48
31
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();
二分木を次のルールで走査
1. 自分を訪ねる
2. 自分の左部分木をたどる
3. 自分の右部分木をたどる
通りがけ順(in-order)の走査
二分木を次のルールで走査
1. 自分の左部分木をたどる
2. 自分を訪ねる
3. 自分の右部分木をたどる
29
20
二分探索木を走査すると
整列済みデータが得られる
14
7
24
19
21
21
32
30
48
31
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();
}
二分木を次のルールで走査
1. 自分の左部分木をたどる
2. 自分を訪ねる
3. 自分の右部分木をたどる
二分探索木を走査すると
整列済みデータが得られる
帰りがけ順(post-order)の走査
二分木を次のルールで走査
1. 自分の左部分木をたどる
2. 自分の右部分木をたどる
3. 自分を訪ねる
29
20
14
7
24
19
21
32
30
48
31
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();
二分木を次のルールで走査
1. 自分の左部分木をたどる
2. 自分の右部分木をたどる
3. 自分を訪ねる
public class BinarySearchTreeTest {
public static void main(String[] anyArguments) {
BinarySearchTree tree = new BinarySearchTree();
MyData data01 = new MyData(29, "リンゴ");
System.out.println("果物の名前を追加");
MyData data02 = new
MyData(20,
"ミカン");
tree.printTree();
tree.insert(data01);
MyData data03 = new MyData(14, "サクランボ");
tree.insert(data02);
MyData data04 = new MyData(32, "バナナ");
tree.insert(data03);
System.out.println("木を行きがけ順で表示");
MyData data05 = new
MyData(30, "イチゴ");
tree.insert(data04);
tree.printTreePreOrder();
MyData data06 = new
MyData(24, "ブルーベリー");
tree.insert(data05);
System.out.println("木を通りがけ順で表示");
MyData data07 = new
MyData( 7, "グレープフルーツ");
tree.insert(data06);
MyData data08 = new
MyData(21, "レモン");
tree.printTreeInOrder();
tree.insert(data07);
MyData data09 = new
MyData(48, "メロン");
System.out.println("木を帰りがけ順で表示");
tree.insert(data08);
MyData data10 = new
MyData(31, "スイカ");
tree.printTreePostOrder();
tree.insert(data09);
MyData data11 = new MyData(19, "ナシ");
tree.insert(data10);
MyData data12 = new MyData(17, "モモ");
tree.insert(data11);
tree.remove(data10);
MyData data13 = new
MyData(23, "マンゴー");
tree.insert(data12);
tree.remove(data11);
MyData data14 = new
MyData(28, "ブドウ");
tree.insert(data13);
tree.remove(data02);
tree.insert(data14);
tree.printTree();
System.out.println("木を通りがけ順で表示");
tree.printTreeInOrder();
}
}
System.out.println("30, イチゴ を探す");
System.out.println(tree.search(data05).toString());
System.out.println(tree.searchRecursive(data05).toString());
果物の名前を追加
(7'グレープフルーツ)
(14'サクランボ)
(17'モモ)
(19'ナシ)
木を行きがけ順で表示
(7'グレープフルーツ)
(20'ミカン)
{(20'ミカン),(29'リンゴ),(32'バナナ)}
(14'サクランボ)
(21'レモン)
{(14'サクランボ),(20'ミカン),(24'ブルーベリー)}
(17'モモ)
(23'マンゴー)
ミカン・スイカ・ナシを削除
{(7'グレープフルーツ),(14'サクランボ),(19'ナシ)}
(21'レモン)
(24'ブルーベリー)
{null,(7'グレープフルーツ),null}
(23'マンゴー)
(28'ブドウ)
木を通りがけ順で表示
{(17'モモ),(19'ナシ),null}
(24'ブルーベリー)
(29'リンゴ)
{null,(7'グレープフルーツ),null}
{null,(17'モモ),null}
(28'ブドウ)
(30'イチゴ)
{(7'グレープフルーツ),(14'サクランボ),(19'ナシ)}
{(21'レモン),(24'ブルーベリー),(28'ブドウ)}
(29'リンゴ)
(31'スイカ)
{null,(17'モモ),null}
{null,(21'レモン),(23'マンゴー)}
(30'イチゴ)
(32'バナナ)
{(17'モモ),(19'ナシ),null}
{null,(23'マンゴー),null}
(32'バナナ)
(48'メロン)
{(14'サクランボ),(20'ミカン),(24'ブルーベリー)}
{null,(28'ブドウ),null}
(48'メロン)
{null,(21'レモン),(23'マンゴー)}
{(30'イチゴ),(32'バナナ),(48'メロン)}
木を通りがけ順で表示
木を通りがけ順で表示
{null,(23'マンゴー),null}
{null,(30'イチゴ),(31'スイカ)}
木を帰りがけ順で表示
{null,(7'グレープフルーツ),null}
{null,(7'グレープフルーツ),null}
{(21'レモン),(24'ブルーベリー),(28'ブドウ)}
{null,(31'スイカ),null}
{null,(7'グレープフルーツ),null}
{(7'グレープフルーツ),(14'サクランボ),(19'ナシ)}
{(7'グレープフルーツ),(14'サクランボ),(17'モモ)}
{null,(28'ブドウ),null}
{null,(48'メロン),null}
{null,(17'モモ),null}
{null,(17'モモ),null}
{(20'ミカン),(29'リンゴ),(32'バナナ)}
{(17'モモ),(19'ナシ),null}
{(17'モモ),(19'ナシ),null}
{(14'サクランボ),(21'レモン),(24'ブルーベリー)}
{null,(30'イチゴ),(31'スイカ)}
{(7'グレープフルーツ),(14'サクランボ),(19'ナシ)}
{(14'サクランボ),(20'ミカン),(24'ブルーベリー)}
{null,(23'マンゴー),null}
{null,(31'スイカ),null}
{null,(23'マンゴー),null}
{null,(21'レモン),(23'マンゴー)}
{(23'マンゴー),(24'ブルーベリー),(28'ブドウ)}
{(30'イチゴ),(32'バナナ),(48'メロン)}
{null,(21'レモン),(23'マンゴー)}
{null,(23'マンゴー),null}
{null,(28'ブドウ),null}
{null,(48'メロン),null}
{null,(28'ブドウ),null}
{(21'レモン),(24'ブルーベリー),(28'ブドウ)}
{(21'レモン),(29'リンゴ),(32'バナナ)}
{(21'レモン),(24'ブルーベリー),(28'ブドウ)}
{null,(28'ブドウ),null}
{null,(30'イチゴ),null}
{(14'サクランボ),(20'ミカン),(24'ブルーベリー)}
{(20'ミカン),(29'リンゴ),(32'バナナ)}
{(30'イチゴ),(32'バナナ),(48'メロン)}
{null,(31'スイカ),null}
{null,(30'イチゴ),(31'スイカ)}
{null,(48'メロン),null}
{null,(30'イチゴ),(31'スイカ)}
{null,(31'スイカ),null}
{null,(48'メロン),null}
{(30'イチゴ),(32'バナナ),(48'メロン)}
30, イチゴ を探す
{(30'イチゴ),(32'バナナ),(48'メロン)}
{null,(48'メロン),null}
{null,(30'イチゴ),null}
{(20'ミカン),(29'リンゴ),(32'バナナ)}
{null,(30'イチゴ),null}