1篇の文章は二叉木と二叉木をマスターして木を探します
8882 ワード
ツリーはコンピュータ科学でよく使われるデータ構造です.ツリーは、データを階層的に格納する非線形のデータ構造です.
ツリーは、ファイルシステム内のファイルなどの階層関係を持つデータを格納するために使用されます.
ツリーは、シーケンステーブルを格納するためにも使用できます.
ツリーは、エッジで接続されたノードのセットで構成されます.会社の組織構造図はツリーの例です.
組織構造図は、木の一番上のノードがルートノードになることです.1つのノードの下に複数のノードが接続されている場合、そのノードは親ノードと呼ばれ、その下のノードは子ノードと呼ばれます.1つのノードは、0個、1個または複数のサブノードを有することができる.サブノードがないノードをリーフノードと呼びます.次の図は、いくつかのツリーの用語を示しています.
特定のエッジのセットに沿って、1つのノードから直接接続されていない別のノードに移動できます.1つのノードから別のノードへのエッジのセットをパスと呼びます.ツリー内のすべてのノードに特定の順序でアクセスすることをツリーのループと呼びます.ツリーは、ルートノードが0番目のレイヤで、サブノードが1番目のレイヤで、サブノードのサブノードが2番目のレイヤである階層に分けられます.ツリー内の任意のレイヤのノードは、ルートノードのサブノード、サブノードのサブノードなどを含むサブツリーのルートと見なすことができます.ツリーのレイヤ数がツリーの深さであることを定義します.ノードの高さ:ノードからリーフノードへの最長パス(エッジツリー).ノードの深さ:ルートノードからこのノードまでのエッジの数.ノードのレイヤ数:ノードの深さ+1.ノードの高さ:ルートノードの高さ.次に例を挙げると、各ノードにはキーと呼ばれる場合がある関連する値があります.二叉木は特殊な木です.サブノードは2つを超えません.二叉木はいくつかの特殊な計算特性を有し、それ以上のいくつかの操作が異常に効率的である.
1つの親ノードの2つのサブノードは、それぞれ左ノードと右ノードと呼ばれます.左ノードには特定の値のセットが含まれ、右ノードには特定の値のセットが含まれます.
次の図は、ツリーを検索するなどの特殊なツリーを考慮すると、サブノードを決定することが重要であるツリーを示しています.ツリーは特殊なツリーで、比較的小さな値は左ノードに保存され、大きな値は右ノードに保存されます.この特性により、単語や文字列などの数値や非数値のデータに対しても、検索の効率が向上します.下の図の木を見てみましょう.番号2の二叉木の中で、葉ノードはすべて最下層にあり、葉ノード以外の各ノードには左右の2つのサブノードがあり、この二叉木を満二叉木と言います.番号3の二叉木では、葉ノードが一番下の二層にあり、最後の層の葉ノードが左に並び、最後の層を除いて他の層のノード数が最大に達し、この二叉木を完全二叉木と呼ぶ.
ノードオブジェクトを定義します.
二叉ルックアップツリーを表すBSTクラスを作成できるようになりました.クラスには、ツリーのルートノードを検索する2つのフォークを表すノードを表すノードオブジェクトだけが含まれます.クラスのコンストラクション関数は、ルートノードをnullに初期化し、空のノードを作成します.
BSTには、まず、ツリーに新しいノードを挿入するためのinsert()メソッドが必要です.まず、ノードオブジェクトを作成し、そのオブジェクトにデータを転送して保存します.次に、BSTにルートノードがあるかどうかをチェックし、なければ、これは新しい木で、そのノードがルートノードであり、この方法はこれで終わります.そうでなければ、次のステップに進みます.
挿入されるノードがルートノードでない場合、BSTを巡回して挿入の適切な位置を見つける準備をします.このプロシージャはチェーンテーブルを巡回するのと似ています.現在のノードを1つのノードで保存し、BSTを1つずつ巡回します.
BSTに入ると、次にノードをどこに置くかを決定する必要があります.正しい挿入点が見つかった場合、ループが飛び出します.
正しい挿入点を検索するアルゴリズムは次のとおりです.ルートノードを現在のノードとする. 挿入されるノードが現在のノードより小さい場合、新しい現在のノードを元のノードの左ノードとする.逆に、第4ステップを実行します. 現在のノードの左ノードがnullである場合、新しいノードをこの位置に挿入し、ループを終了する.逆に、次のループを実行し続けます. 新しい現在のノードを元のノードの右ノードとする. 現在のノードの右ノードがnullである場合、新しいノードをその位置に挿入し、ループを終了する.逆に、次のループを実行し続けます.
もう一つの書き方は、実は考え方は同じですが、上のほうは少し簡潔です.(コード構想は王争先生の『データ構造とアルゴリズムの美』)
二叉木を巡る方法は3つあります.中序、先序、後序です.
中間シーケンスは、ノード上のキー値に従ってBST上のすべてのノードに昇順にアクセスする.まず、ルートノードにアクセスし、左サブツリーと右サブツリーにも同じ方法でアクセスします.後順は、まずリーフノードにアクセスし、左サブツリーから右サブツリー、さらにルートノードに移動します.
シーケンスループとは、ツリー内の任意のノードに対して、このノードを先に印刷し、左サブツリーを印刷し、最後に右サブツリーを印刷することです.中順遍歴とは、ツリー内の任意のノードに対して、左サブツリーを印刷してから、それ自体を印刷し、最後に右サブツリーを印刷することです.後順ループとは、ツリー内の任意のノードに対して、左サブツリーを印刷し、右サブツリーを印刷し、最後に自分自身を印刷することです.
中間パスのコードは次のとおりです.
次の図は、中間パスを示しています.
先に巡回するコードは次のとおりです.
次の図は、先行するパスを示しています.
後に続くコード:
次の図は、後続のパスを示しています.
BSTには、通常、次の3つのタイプの検索があります.所与の値を検索 最大値 を検索最小値 を検索
小さい値は常に左ノードにあるので、BSTで最小値を検索するには、左サブツリーを巡って、最後のノードを見つけることを知るだけです.
BST上で最大値を検索するには,右サブツリーを巡回し,最後のノードを見つけることを知るだけでよい.
BSTで指定された値を検索するには、その値と現在のノード値のサイズを比較する必要があります.比較することにより、指定された値が現在のノードにない場合、左にループするか右にループするかを決定できます.
指定された値が見つかった場合、メソッドは値を保存するメソッドを返します.見つからない場合はnullを返します.
BSTからノードを削除する操作は最も複雑であり,その複雑さはどのノードを削除するかに依存する.サブノードのないノードを削除すると、非常に簡単です.ノードにサブノードが1つしかないと、少し複雑になります.ノードにサブノードが2つある場合は、最も複雑です.
3つの状況に分けて処理します.削除するノードにサブノードがない場合は、削除するノードへのポインタをnullに設定するだけです.例えば、図中のノード55を削除する. 削除するノードが1つのサブノード(左サブノードまたは右サブノード)しかない場合は、親ノードを更新し、削除するノードを指すポインタを、削除するノードを指すサブノードに向けるだけでよい.例えば、図中のノード13を削除する. 削除するノードに2つのサブノードがある場合は、このノードの右サブツリーの最小ノードを見つけて、削除するノードに置き換えてから、この最小ノードを削除する必要があります.最小ノードには左サブノードがないに違いありません.この2つのルールを使用して、図のノード18の削除などの削除操作を行うことができます.
実際,ツリーの削除操作については,単純に削除したノードを「削除済み」とマークするが,真の削除ではない非常に簡単で巧みな方法がある.これにより、削除する要素がメモリに格納され、メモリスペースが浪費されますが、削除操作は簡単で、挿入や検索のコード実装の難しさも増しません.
二叉ルックアップツリーにはもう一つの重要な特性があり、中序が二叉ルックアップツリーを遍歴し、秩序あるデータシーケンスを入力することができ、時間複雑度はO(n)であり、非常に効率的である.したがって,二叉ルックアップツリーは二叉ソートツリーとも呼ばれる.
ツリーは、ファイルシステム内のファイルなどの階層関係を持つデータを格納するために使用されます.
ツリーは、シーケンステーブルを格納するためにも使用できます.
ツリーの定義
ツリーは、エッジで接続されたノードのセットで構成されます.会社の組織構造図はツリーの例です.
組織構造図は、木の一番上のノードがルートノードになることです.1つのノードの下に複数のノードが接続されている場合、そのノードは親ノードと呼ばれ、その下のノードは子ノードと呼ばれます.1つのノードは、0個、1個または複数のサブノードを有することができる.サブノードがないノードをリーフノードと呼びます.次の図は、いくつかのツリーの用語を示しています.
特定のエッジのセットに沿って、1つのノードから直接接続されていない別のノードに移動できます.1つのノードから別のノードへのエッジのセットをパスと呼びます.ツリー内のすべてのノードに特定の順序でアクセスすることをツリーのループと呼びます.ツリーは、ルートノードが0番目のレイヤで、サブノードが1番目のレイヤで、サブノードのサブノードが2番目のレイヤである階層に分けられます.ツリー内の任意のレイヤのノードは、ルートノードのサブノード、サブノードのサブノードなどを含むサブツリーのルートと見なすことができます.ツリーのレイヤ数がツリーの深さであることを定義します.ノードの高さ:ノードからリーフノードへの最長パス(エッジツリー).ノードの深さ:ルートノードからこのノードまでのエッジの数.ノードのレイヤ数:ノードの深さ+1.ノードの高さ:ルートノードの高さ.次に例を挙げると、各ノードにはキーと呼ばれる場合がある関連する値があります.二叉木は特殊な木です.サブノードは2つを超えません.二叉木はいくつかの特殊な計算特性を有し、それ以上のいくつかの操作が異常に効率的である.
ツリーとツリーの検索
1つの親ノードの2つのサブノードは、それぞれ左ノードと右ノードと呼ばれます.左ノードには特定の値のセットが含まれ、右ノードには特定の値のセットが含まれます.
次の図は、ツリーを検索するなどの特殊なツリーを考慮すると、サブノードを決定することが重要であるツリーを示しています.ツリーは特殊なツリーで、比較的小さな値は左ノードに保存され、大きな値は右ノードに保存されます.この特性により、単語や文字列などの数値や非数値のデータに対しても、検索の効率が向上します.下の図の木を見てみましょう.番号2の二叉木の中で、葉ノードはすべて最下層にあり、葉ノード以外の各ノードには左右の2つのサブノードがあり、この二叉木を満二叉木と言います.番号3の二叉木では、葉ノードが一番下の二層にあり、最後の層の葉ノードが左に並び、最後の層を除いて他の層のノード数が最大に達し、この二叉木を完全二叉木と呼ぶ.
二叉ルックアップツリーの実装(BST)
ノードオブジェクトを定義します.
function node(data, left, right) {
this.data = data;
this.left = left;
this.right = right;
this.show = show;
function show() {
return this.data;
}
}
二叉ルックアップツリーを表すBSTクラスを作成できるようになりました.クラスには、ツリーのルートノードを検索する2つのフォークを表すノードを表すノードオブジェクトだけが含まれます.クラスのコンストラクション関数は、ルートノードをnullに初期化し、空のノードを作成します.
BSTには、まず、ツリーに新しいノードを挿入するためのinsert()メソッドが必要です.まず、ノードオブジェクトを作成し、そのオブジェクトにデータを転送して保存します.次に、BSTにルートノードがあるかどうかをチェックし、なければ、これは新しい木で、そのノードがルートノードであり、この方法はこれで終わります.そうでなければ、次のステップに進みます.
挿入されるノードがルートノードでない場合、BSTを巡回して挿入の適切な位置を見つける準備をします.このプロシージャはチェーンテーブルを巡回するのと似ています.現在のノードを1つのノードで保存し、BSTを1つずつ巡回します.
BSTに入ると、次にノードをどこに置くかを決定する必要があります.正しい挿入点が見つかった場合、ループが飛び出します.
正しい挿入点を検索するアルゴリズムは次のとおりです.
function BST() {
this.root = null;
this.insert = insert;
function insert(data) {
var n = new Node(data, null, null);
if(this.root == null) {
this.root = n;
}else {
var currentNode = this.root;
var parent;
while(true) {
parent = currentNode;
if(data < currentNode.data) {
currentNode = currentNode.left;
if(currentNode == null) {
parent.left = n;
break;
}
}else {
currentNode = currentNode.right;
if(currentNode.data == null) {
parent.right = n;
break;
}
}
}
}
}
}
もう一つの書き方は、実は考え方は同じですが、上のほうは少し簡潔です.(コード構想は王争先生の『データ構造とアルゴリズムの美』)
function insert(data) {
var node = new Node(data, null, null);
if(this.root == null) {
this.root = node;
}else {
p = this.root;
while(p !== null) {
if(data < p.data) {
if(p.left == null) {
p.left = node;
return;
}
p = p.left;
}else {
if(p.right == null) {
p.right = node;
return;
}
p = p.right;
}
}
}
}
ツリーを検索するには
二叉木を巡る方法は3つあります.中序、先序、後序です.
中間シーケンスは、ノード上のキー値に従ってBST上のすべてのノードに昇順にアクセスする.まず、ルートノードにアクセスし、左サブツリーと右サブツリーにも同じ方法でアクセスします.後順は、まずリーフノードにアクセスし、左サブツリーから右サブツリー、さらにルートノードに移動します.
シーケンスループとは、ツリー内の任意のノードに対して、このノードを先に印刷し、左サブツリーを印刷し、最後に右サブツリーを印刷することです.中順遍歴とは、ツリー内の任意のノードに対して、左サブツリーを印刷してから、それ自体を印刷し、最後に右サブツリーを印刷することです.後順ループとは、ツリー内の任意のノードに対して、左サブツリーを印刷し、右サブツリーを印刷し、最後に自分自身を印刷することです.
中間パスのコードは次のとおりです.
function inOrder(node) {
if(!(node == null)) {
this.inOrder(node.left);
console.log(node.show());
this.inOrder(node.right);
}
}
var bst = new BST();
bst.insert(17)
bst.insert(19)
bst.insert(16)
bst.insert(34)
bst.insert(35)
bst.insert(36)
bst.inOrder(bst.root); // 16 17 19 34 35 36
次の図は、中間パスを示しています.
先に巡回するコードは次のとおりです.
function perOrder(node) {
if(!(node == null)) {
console.log(node.show());
this.perOrder(node.left);
this.perOrder(node.right);
}
}
var bst = new BST();
bst.insert(17)
bst.insert(19)
bst.insert(16)
bst.insert(34)
bst.insert(35)
bst.insert(36)
console.log(' ');
bst.perOrder(bst.root); // 17 16 19 34 35 36
次の図は、先行するパスを示しています.
後に続くコード:
var bst = new BST();
bst.insert(17)
bst.insert(19)
bst.insert(16)
bst.insert(34)
bst.insert(35)
bst.insert(36)
console.log(' ');
bst.postOrder(bst.root); // 16 36 35 34 19 17
次の図は、後続のパスを示しています.
ツリーで検索
BSTには、通常、次の3つのタイプの検索があります.
最小値と最大値の検索
小さい値は常に左ノードにあるので、BSTで最小値を検索するには、左サブツリーを巡って、最後のノードを見つけることを知るだけです.
function getMin() {
let currentNode = this.root;
while(currentNode.left != null) {
currentNode = currentNode.left;
}
return currentNode.data;
}
BST上で最大値を検索するには,右サブツリーを巡回し,最後のノードを見つけることを知るだけでよい.
function getMax() {
let currentNode = this.root;
while(currentNode.right != null) {
currentNode = currentNode.right;
}
return currentNode.data;
}
指定した値の検索
BSTで指定された値を検索するには、その値と現在のノード値のサイズを比較する必要があります.比較することにより、指定された値が現在のノードにない場合、左にループするか右にループするかを決定できます.
function find(data) {
var currentNode = this.root;
while(currentNode != null) {
if(currentNode.data == data) {
return currentNode;
}else if(currentNode.data < data) {
currentNode = currentNode.right;
}else {
currentNode = currentNode.left;
}
}
return null;
}
指定された値が見つかった場合、メソッドは値を保存するメソッドを返します.見つからない場合はnullを返します.
ツリーからノードを削除
BSTからノードを削除する操作は最も複雑であり,その複雑さはどのノードを削除するかに依存する.サブノードのないノードを削除すると、非常に簡単です.ノードにサブノードが1つしかないと、少し複雑になります.ノードにサブノードが2つある場合は、最も複雑です.
3つの状況に分けて処理します.
function deleteNode(data) {
var p = this.root; // p ,
var pp = null; // pp P
while(p != null && p.data != data) {
pp = p;
if(data > p.data) {
p = p.right;
}else {
p = p.left;
}
}
if(p == null) return; //
if(p.left != null && p.right != null) { //
//
var minP = p.right;
var minPP = p; // minPP minP
while(minP.left != null) {
pnode = minP;
minP = p.left;
}
p.data = minP.data; // minP p
p = minP; // minP
pp = minP;
}
//
var childNode = null;
if(p.left != null) {
childNode = p.left;
}else if(p.right != null) {
childNode = p.right;
}else {
chiildNode = null;
}
if(pp == null) {
p = chiildNode; //
}else if(pp.left == p) {
pp.left = childNode;
}else {
pp.right = childNode;
}
}
実際,ツリーの削除操作については,単純に削除したノードを「削除済み」とマークするが,真の削除ではない非常に簡単で巧みな方法がある.これにより、削除する要素がメモリに格納され、メモリスペースが浪費されますが、削除操作は簡単で、挿入や検索のコード実装の難しさも増しません.
その他
二叉ルックアップツリーにはもう一つの重要な特性があり、中序が二叉ルックアップツリーを遍歴し、秩序あるデータシーケンスを入力することができ、時間複雑度はO(n)であり、非常に効率的である.したがって,二叉ルックアップツリーは二叉ソートツリーとも呼ばれる.