JS を使用してバイナリ ツリー トラバーサル アルゴリズムのサンプル コードを実装する

JS を使用してバイナリ ツリー トラバーサル アルゴリズムのサンプル コードを実装する

序文

コンピュータ サイエンスでは、ツリーは広く使用されている抽象データ型 (ADT) であり、非線形データ構造の一種です。ツリー、特にバイナリツリーはコンピューター分野で広く使用されています。

ツリー関連の概念:

  • ノード: 各要素はノードと呼ばれる
  • ツリールート: ルートノード
  • 次数: ノードに含まれる子ノードの数をノードの次数と呼びます。
  • リーフノード: 次数0のノード

1. バイナリツリー

概念: 各ノードに最大 2 つのサブツリーが含まれるツリーは、バイナリ ツリーと呼ばれます。

1.1. 二分木の走査

バイナリ ツリーのトラバーサルには、深さトラバーサルと幅トラバーサルの 2 種類があります。深さトラバーサルには、事前順序、インオーダー、事後順序の 3 つのトラバーサル方法があります。 幅方向のトラバーサルは、レイヤーごとに順番にトラバーサルするレベル方向のトラバーサルです。

4 つのトラバーサルの主なアイデアは次のとおりです。

  • 事前順序: 最初にルートにアクセスし、次に左のサブツリーにアクセスし、最後に右のサブツリーである DLR にアクセスします。
  • 順序どおり: 最初に左のサブツリーにアクセスし、次にルートにアクセスし、最後に右のサブツリー (LDR) にアクセスします。
  • 後順序: 最初に左のサブツリーにアクセスし、次に右のサブツリーにアクセスし、最後にルート LRD にアクセスします。
  • 幅: レイヤーごとに順番にトラバースします。

たとえば、a+b*(cd)-e/f という式はバイナリ ツリーで表されます。

それらを個別にトラバースします。

  • 前文: -+a*b-cd/ef
  • 順番:a+b*cde/f
  • 追記:abcd-*+ef/-
  • 幅: -+/a*efb-cd

1.2. jsを使用してバイナリツリーを表現する

バイナリ ツリーを表すには、js オブジェクトを使用します。オブジェクトには、left、value、right の 3 つのプロパティがあり、それぞれ左サブツリー、値、右サブツリーを表します。上記の例 a+b*(cd)-e/f は、js を使用して次のように表すことができます。

var ツリー = {
    価値: '-'、
    左: {
        値: '+',
        左: {
            値: 'a'
        },
        右:
            価値: '*'、
            左: {
                値: 'b'
            },
            右:
                価値: '-'、
                左: {
                    値: 'c'
                },
                右:
                    値: 'd'
                }
            }
        }
    },
    右:
        価値: '/'、
        左: {
            値: 'e'
        },
        右:
            値: 'd'
        }
    }
}

1.3 事前順序探索アルゴリズム

前書き: 方法は 2 つあります。最初の方法は非常に簡単で、再帰を直接使用する方法です。

関数 preOrder(treeNode) {
  if(ツリーノード) {
    console.log(treeNode.value); // 出力は、このノードにアクセスすることを意味します preOrder(treeNode.left);
    ツリーノードを右にプリオーダーします。
  }
}

アルゴリズムは非常にシンプルです。まずルート ノードをトラバースし、次に左のサブツリーを再帰的にトラバースします。左のサブツリーをトラバースした後、右のサブツリーを再帰的にトラバースします。

2番目の非再帰的走査

関数 preOrder(treeNode) {
  if(ツリーノード) {
    var stack = [treeNode]; // バイナリツリーをスタックにプッシュします while (stack.length !== 0) {
      treeNode = stack.pop(); // スタックの先頭を取得します。 document.getElementById('text').appendChild(document.createTextNode(treeNode.value)); // ノードにアクセスします。 if(treeNode.right) stack.push(treeNode.right); // 右のサブツリーをスタックにプッシュします。 if(treeNode.left) stack.push(treeNode.left); // 左のサブツリーをスタックにプッシュします。 }
  }
}

2 つ目は、スタックの考え方を使うことです。ご存知のとおり、スタックは先入れ後出しのデータ構造です。最初にルート ノードをスタックにプッシュし、次にスタックの先頭を取り、ルート ノードにアクセスして、右と左のサブツリーをそれぞれスタックにプッシュします。最初に左側からアクセスする必要があるため、右側を最初にスタックにプッシュする必要があります。そのため、最初に右側のサブツリーをスタックにプッシュし、次にスタックが空になるまでスタックを取り出すループを実行します。

1.4. 順序探索アルゴリズム

順序再帰アルゴリズム:

関数InOrder(treeNode) {
    if(ツリーノード) {
        ツリーノードの左端に順序を付けます。
        document.getElementById('text').appendChild(document.createTextNode(treeNode.value));
        ツリーノードの右端に順序を付けます。
    }
}

事前順序再帰アルゴリズムと同じ考え方だが、訪問したノードの位置が異なる

2番目のタイプ:

関数InOrder(ノード) {
  if(ノード) {
    var stack = []; // 空のスタックを作成する // スタックが空でないかノードが空でない場合は、while (stack.length !== 0 || node) { をループします。 
      if (node) { //ノードが空でない場合 stack.push(node); //ノードをスタックにプッシュします node = node.left; //左のサブツリーを現在のノードとして使用します } else { //左のサブツリーが空です。つまり、左のサブツリーはありません node = stack.pop(); //ノードを取り出します document.getElementById('text').appendChild(document.createTextNode(node.value));
          node = node.right; // 右ノードを現在のノードとして使用します}
    }
  }
}

非再帰的順序アルゴリズムの考え方は、現在のノードをスタックにプッシュし、次に左のサブツリーをトラバースし、左のサブツリーが存在する場合は、左のサブツリーが空になるまでスタックにプッシュし続け、前のノードにアクセスし、右のサブツリーをスタックにプッシュすることです。

1.5. 後順序探索アルゴリズム

1つ目: 再帰的トラバーサルアルゴリズム

関数 postOrder(ノード) {
    if (node) { // バイナリツリーが空かどうかを判断します postOrder(node.left); // 左のサブツリーを再帰的にトラバースします postOrder(node.right); // 右のサブツリーを再帰的にトラバースします document.getElementById('text').appendChild(document.createTextNode(node.value));
    }
}

2番目: 非再帰的トラバーサルアルゴリズム

関数 postOrder(ノード) {
    if (node) { //バイナリツリーが空かどうかを判断var stack = [node]; //バイナリツリーをスタックにプッシュvar tmp = null; //キャッシュ変数を定義while (stack.length !== 0) { //スタックが空でない場合はループしますtmp = stack[stack.length - 1]; //スタックの最上位の値をtmpに保存if (tmp.left && node !== tmp.left && node !== tmp.right) { //左のサブツリーがある場合、node !== tmp.left && node !== tmp.right は、左と右のサブツリーを繰り返しスタックにプッシュすることを回避するためですstack.push(tmp.left); //左のサブツリーノードをスタックにプッシュします} else if (tmp.right && node !== tmp.right) { //ノードに右のサブツリーがある場合stack.push(tmp.right); //右のサブツリーをスタックにプッシュします} else {
                document.getElementById('text').appendChild(document.createTextNode(stack.pop().value));
                ノード = tmp;
            }
        }
    }
}

ここでは、最後にポップおよびプッシュされたノードを記録するために tmp 変数が使用されます。アイデアとしては、まずルート ノードと左のツリーをスタックにプッシュし、次に左のツリーを取り出し、右のツリーをプッシュして取り出し、最後にルート ノードを取り出すというものです。

以下は、このアルゴリズムを使用して前のバイナリツリーをトラバースするプロセスです。

スタック tmp ノード プリント初期値: - null -
ラウンド1: -+ - -
第2ラウンド: -+a + -
ラウンド3: -+ a a a
第4ラウンド: -+* + a
第5ラウンド: -+*b * a
第6ラウンド: -+* b b b
第7ラウンド: -+*- * b
第8ラウンド: -+*-c - b
第9ラウンド: -+*- c c c
第10ラウンド: -+*-d - c
第11ラウンド: -+*- d d d
第12ラウンド: -+* - - -
第13ラウンド: -+ * * *
第14ラウンド: - + + +
ラウンド15: -/-+
ラウンド16: -/e / +
第17ラウンド: -/ e e e
第18ラウンド: -/f / e
第19ラウンド: -/ f f f
第20ラウンド: - / / /
第21ラウンド: - - -

結果: abcd-*+ef/-

1.6. レイヤーごとのトラバーサルアルゴリズム

関数breadthTraversal(ノード) {
    if (node) { //バイナリツリーが空かどうかを判断var que = [node]; //バイナリツリーをキューに入れますwhile (que.length !== 0) { //キューが空かどうかを判断node = que.shift(); //キューからノードを取得しますdocument.getElementById('text').appendChild(document.createTextNode(node.value)); //取得したノードの値を配列に保存しますif (node.left) que.push(node.left); //左のサブツリーが存在する場合は、左のサブツリーをキューに入れますif (node.right) que.push(node.right); //右のサブツリーが存在する場合は、右のサブツリーをキューに入れます}
    }
}

配列を使用してキューをシミュレートし、最初にルート ノードをキューに配置します。キューが空でない場合は、ループを実行します。キューからノードを取り出し、ノードに左サブツリーがある場合は、ノードの左サブツリーをキューに格納します。ノードに右サブツリーがある場合は、ノードの右サブツリーをキューに格納します。

2. アルゴリズムに関する質問

1.1. 二分木の最大深さ

バイナリツリーが与えられた場合、その最大深さを見つけます。

バイナリ ツリーの深さは、ルート ノードから最も遠いリーフ ノードまでの最長パス上のノードの数です。

たとえば、次のバイナリ ツリーは深さ 3 を返します。

3
/ \
9 20
/ \
15 7

定数ツリー = {
値: 3,
左: {
値: 9
},
右:
値: 20,
左: { 値: 15 },
右: { 値: 9 }
}
}

再帰アルゴリズム:再帰アルゴリズムの考え方は非常にシンプルです。まず、左のサブツリーの最も深い層を取得し、次に右のサブツリーの最も深い層を取得し、それらの最大値がツリーの深さになります。

var maxDepth = function(ルート) {
  ルートの場合
    0を返します。
  }
  定数 leftDeep = maxDepth(root.left) + 1;
  定数 rightDeep = maxDepth(root.right) + 1;
  Math.max(leftDeep, rightDeep) を返します。
};
/*
最大深度(ルート) = 最大深度(ルート.左) + 1 = 2
最大深度(ルート.左) = 最大深度(ルート.左.左) + 1 = 1
最大深度(ルートの左端) = 0;

最大深度(ルート) = 最大深度(ルート.右) + 1 = 3
最大深度(ルート.右) = 最大深度(ルート.右.右) + 1 = 2
最大深度(ルート.右.右) = 最大深度(ルート.右.右.右) + 1 = 1
最大深度(ルート右右右) = 0
*/

1.2. 二分木のすべてのパス

バイナリ ツリーが与えられた場合、ルート ノードからリーフ ノードまでのすべてのパスを返します。

例えば:

3
/ \
9 20
/ \
15 7

戻り値: ['3->9', '3->20->15', '3->20->7']

再帰メソッドの使用:

var binaryTreePaths = function(ルート) {
  if (!root) は [] を返します。
  定数res = [];
  関数 dfs(curNode, curPath) {
    if(!curNode.left && !curNode.right) {
      res.push(curPath);
    }
    左のノードの場合
      dfs(curNode.left, `${curPath}->${curNode.left.value}`)
    }
    右のノードの場合
      dfs(curNode.right、`${curPath}->${curNode.right.value}`)
    }
  }
  dfs(ルート、`${root.value}`);
  res を返します。
};

要約する

JS を使用してバイナリ ツリー トラバーサル アルゴリズムを実装する方法に関するこの記事はこれで終わりです。JS バイナリ ツリー トラバーサル アルゴリズムに関する関連コンテンツをさらにご覧になりたい場合は、123WORDPRESS.COM で以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • JavaScript を使用してソートアルゴリズムを実装する方法
  • Matlab による JavaScript プログラミング、重心アルゴリズムによる位置決め学習
  • JavaScript 初心者のための二分探索木アルゴリズムのチュートリアル
  • JavaScript で実装された 7 つのソート アルゴリズムの概要 (推奨!)
  • JavaScript でツリー構造を構築するための効率的なアルゴリズムについての簡単な説明
  • JavaScript でアルゴリズムの複雑さを学ぶ方法
  • jsは赤い封筒の順序と量を指定するアルゴリズムを実装します
  • JavaScript を使用して簡単なアルゴリズムを実行する方法

<<:  Webフォーム作成スキル

>>:  MySQL マスタースレーブレプリケーションと読み取り書き込み分離の詳細な説明

推薦する

MySQL 8.0.15 バージョンのインストールチュートリアル Navicat.list への接続

落とし穴1. ネット上の多くのチュートリアルでは環境変数を設定するファイル名はmy.iniと書いてあ...

Docker の NFS-Ganesha イメージを使用して NFS サーバーを構築する詳細なプロセス

目次1. NFS-Ganeshaの紹介2. NFS-Ganeshaの設定3. NFS-Ganesha...

HTML テーブル マウス ドラッグ ソート機能

効果画像: 1. ファイルをインポートする<script src="js/jquer...

MySQLにおけるビューの作成(CREATE VIEW)と使用制限の詳しい説明

この記事では、例を使用して、MySQL ビューの作成 (CREATE VIEW) と使用上の制限につ...

MySQL 5.7.17 winx64 のインストールと設定のチュートリアル

今日、MySQL データベースをコンピューターに再度インストールしました。システムを再インストールす...

CentOS サーバーのセキュリティ構成戦略

最近、ブルートフォース攻撃によるサーバのクラッキングが頻発しています。侵入行為を大まかに分析し、よく...

Docker実践: Pythonアプリケーションのコンテナ化

1. はじめにコンテナはサンドボックス メカニズムを使用して相互に分離します。コンテナ内にデプロイさ...

MySQLで時間別データと最後の時間別データの差をクエリするアイデアの詳細な説明

1. はじめに要件は、特定の時間範囲内で、1 時間ごとのデータと前の 1 時間ごとのデータの差と比率...

MySQL UPDATE ステートメントの「典型的な」落とし穴

目次1. 問題のあるSQL文たとえば、次の図のような質問をした人がいました。 問題は次のように要約で...

MySQL マスタースレーブ同期、トランザクションロールバックの実装原理

ビンログBinLog は、データベース テーブル構造の変更 (テーブルの作成、変更など) とテーブル...

MySQLのグループカウントと範囲集計を実装する2つの方法

1つ目:通常動作 選択 SUM(ddd) AS count_days、 場合 aa.days >...

HTML での位置の使用に関する簡単な紹介

昨日 HTML を少し学んだばかりで、JD.com の検索バーを作るのが待ちきれませんでした。 作っ...

シンプルな時計を実装するJavaScript

この記事では、JavaScriptでシンプルな時計を実装するための具体的なコードを参考までに紹介しま...

カスタム変数を使用した MySQL クエリの最適化

目次並べ替えクエリの最適化変更されたばかりのデータ行を繰り返し取得しないようにする遅延ロードされた結...

データベースのデフォルトパスを変更した後にmysqlが起動できない問題の解決策

序文mysql がデフォルトのデータベース パスを変更したため、サービスを開始できませんでした。ログ...