プログラマーは、あなたの心にはポイントツリーことではありませんか?

怒ってはいけない、私はあなたが意味軽蔑していない呼び出していませんでした、今日は単に誰もが木の知識を共有したいことですが、私はプログラマーとして言わなければならない、彼の心がない点ツリーはありません、教えてください?あなたはそれを数えることができませんか?自宅に近い、木が私たちの共通のデータ構造の一つであり、その他のバイナリツリー、バイナリ検索ツリー、平衡二分木、赤黒木、Bツリー、B +木、多くの種類があり、我々は今日、ツリー関連のバイナリツリーの話をしなければなりません。

木は何ですか?

まず第一に、私たちは木が何であるかを知っている必要がありますか?彼らは長い間、私は絵を描いた、正確に、根の成長と類似しているが、我々は通常、同様の木を参照してください、私たちと木のデータ構造を閉じたループを形成していないしている間、私たちは通常、木の枝にアップしていますマップは、あなたのツリーのより良い理解を与えます。

図1、図2は、ツリー図のツリーである。3ありません。我々は、2つのノードを接続する線で、要素はまた、ノードと呼ばれ呼び出す各赤い円は、2つのノードが子ノードを持つ親は、私たちの家族関係のように、兄弟ノードになり、親子関係を形成します、1人の父親は最大が同じ木であるIと呼ばれ、その家族の中で兄弟姉妹と呼ばれるが、私は、ない子ノードと呼ばれるノードがリーフノードと呼ばれて呼び出すことはありません。我々は、図の例を取っ​​てください。1、Aがルートノードであり、B、兄弟ノードのC、Eは、Fはリーフノードです。

ツリーは、3つの概念に関与することになります高度、、 深度のは、これらの3つの用語の定義を見てみましょう:

身長:最長パスの葉ノードにノード、ゼロカウントから始まります

深さ:ノードにノードを施したエッジの数で、0からカウントを開始します

レイヤー:距離からのルートノードは、1からカウントを開始します

あなたは3つの用語の概念を知ったら、私たちは、これらの3つの概念のより多くの画像を表現するために図を使用しています。

これらは、私たちが主にバイナリツリーを学び、木の多くの樹種の基本的な概念です。

二進木

バイナリツリーその名の通り、各要素は、ほとんどの二つのノードで、ノードが左と右ノードと呼ばれていました。もちろん、必ずしもすべての要素は、2つのノードを持っている必要があり、いくつかのノードを残してもよいし、一部は右のノードであってもよいです。第二子を開くための国と同じように、のように、誰もが二人の子供を持っている必要があります。私たちは、典型的なバイナリツリーを見てみましょう。

ツリーの異なるストレージモードに基づいて、収納スペース、完全なバイナリツリー、バイナリツリーのより有効に活用するために、非完全2分木に分割して、我々は完全なバイナリツリー、非完全2分木が何であるかを見てみましょうか?

完全なバイナリツリーは、定義された底部2層のリーフノードは、左の葉ノードの最後の層が配置され、そして最後のものを除いて、他のノードの層の数を最大化しなければなりません。

たぶん定義は、我々はいくつかの写真を見て、理解していない見て、あなたは完全にバイナリ、非完全2分木であるかを理解することができるようになります。

1、完全二叉树

完全二叉树

2、非完全二叉树

我々は、ストレージモードツリーを見に行くされていることを、異なるストレージモードに基づいて木の上に、前記非完全なバイナリツリーの完全なバイナリツリーに分かれています。

バイナリツリーのストレージモード

バイナリツリーを格納する2つのモードがあり、一方はポインタまたは参照に基づいてバイナリ連結保管方法、一方がアレイに基づいて順次記憶方法

チェーンストアバイナリ法

チェーン記憶方法は、各ノードは、三つのフィールド、フィールドメモリノードの値は、ノードに格納されている他の2つの左と右の参照フィールドを有することが理解されるべきでも非常に容易であり、比較的簡単です。私たちは一緒にバイトで簡単に文字列が一緒にチェーンの保存方法の全体のツリー構造は、おそらくそれほど長くすることができます。

メソッド順次ストレージ

我々は、測位ノードと座標が順次記憶方法は、アレイの実現に基づいている、アレイは、メモリ空間の規則的周期であるi= 1、左のノードは2 *であるi右ノード* 2 = 2 i+ 1 = 3、というように各ノードは、そう考えられ、それは木の配列に変換され、そして次に、我々は、ツリーに変換この規則配列を追従することができます。私は、あなたが多くの欠点を見ている必要があります考えてここを参照してください、これはアンバランスバイナリツリーが、それは無駄なスペースその多くの原因となりますされていないですか?あなたがポイントと非完全なバイナリツリーの完全なバイナリツリーを必要とする理由はい、これです。ストレージアレイベースのこれら2つのツリーのそれぞれを見てください。

メソッド順次ストレージ完全なバイナリツリー

非順次ストレージ完全2分木の方法

ツリー内の配列に変換した後、図から分かる。ごみ貯蔵0に対するインデックスのみを格納する、完全なバイナリ配列、二つの非完全二分木は多くのスペースを無駄。ストレージノードのストレージアレイに関する情報は必要がないので、ツリーは、チェーン・ストレージ・スペースの節約よりもストレージアレイとの完全なバイナリツリーの場合、

私たちは、バイナリツリー、タイプ、保存方法、のは、バイナリツリーを走査するために一緒に見てみましょう、バイナリツリートラバーサル問題の上記の定義は、多くの場合、インタビューの中で遭遇していることを理解しています。

トラバースバイナリツリー

バイナリツリートラバーサルを理解するために、我々は最初のバイナリツリーの外にインスタンス化する必要があり、私たちは木を定義するチェーン店の方法を使用し、木は、私たちが使用しているため、ノードの情報を格納するために使用されるノード情報ツリーをインスタンス化する必要がありますチェーン店なので、下記の当社ノード情報。

/**
 * 定义一棵树
 */
public class TreeNode {
    // 存储值
    public int data;
    // 存储左节点
    public TreeNode left;
    // 存储右节点
    public TreeNode right;

    public TreeNode(int data) {
        this.data = data;
    }
}

复制代码

ノード情報を定義した後、私たちは木の友人を初期化することができ、ここではツリーの初期化処理は以下のとおりです。

public static TreeNode buildTree() {
    // 创建测试用的二叉树
    TreeNode t1 = new TreeNode(1);
    TreeNode t2 = new TreeNode(2);
    TreeNode t3 = new TreeNode(3);
    TreeNode t4 = new TreeNode(4);
    TreeNode t5 = new TreeNode(5);
    TreeNode t6 = new TreeNode(6);
    TreeNode t7 = new TreeNode(7);
    TreeNode t8 = new TreeNode(8);

    t1.left = t2;
    t1.right = t3;
    t2.left = t4;
    t4.right = t7;
    t3.left = t5;
    t3.right = t6;
    t6.left = t8;

    return t1;
}
复制代码

上記の手順の後、我々は、図に示すツリー状にノードのデジタル値の代表を成長します。

木とした後、私たちは木にそれを通過することができ、三つの方法バイナリツリートラバーサル、先行順走査、トラバーサルの順序トラバーサルでは、3種類のその後のトラバースがあり、関係シーケンシャル出力ノードトラバーサルの3種類があります。のは、これら三つのトラバーサルのそれぞれを見てみましょう。

先行順走査

先行順走査:その後、ツリー内の任意のノードに対して、このノードの最初の印刷、およびそれを印刷するには、サブツリーを左に、そして最終的にはその右のサブツリーを印刷します。

理解を容易にするために、私たちの定義上記の私のバイナリツリーに基づいて、3つのトラバーサルメソッドの実装プロセスは、ダイナミックマップを作成し、あなたの読書を助けたい、我々は、動的な実行フロー・チャートの外観の先行順走査を取ります。

前とどのようにあなたのコード内のツリーの前順走査を達成するためにどのように、順トラバーサル、あなたは間違いなく知ってほしい心を読む前に動的実行フローチャートを通過した後の概念を理解しますか?バイナリツリートラバーサルは、一般のは、のコードの先行順走査を見てみましょう、トラバース再帰的に使用されている、非常に簡単です:

// 先序遍历,递归实现 先打印本身,再打印左节点,在打印右节点
public static void preOrder(TreeNode root) {

    if (root == null) {
        return;
    }
    // 输出本身
    System.out.print(root.data + " ");
    // 遍历左节点
    preOrder(root.left);
    // 遍历右节点
    preOrder(root.right);
}
复制代码

予約限定!

予約限定:ツリー内の任意のノードについて、最初の印刷その左部分木、そしてそれ自体を印刷し、そして最終的にはその右のサブツリーを印刷します。

フロント順トラバーサルとして、我々は、動的実行フローチャートを横断空想に見えます。

トラバーサルコードINORDER:

// 中序遍历 先打印左节点,再输出本身,最后输出右节点
public static void inOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    inOrder(root.left);
    System.out.print(root.data + " ");
    inOrder(root.right);
}
复制代码

後順

後順:ツリー内の任意のノードについて、最初の印刷その左部分木、その後、それを右のサブツリーを印刷し、そして最終的には、ノード自体を印刷します。

同じを横断する2種類の前には、概念を理解した後、我々は最初の数字を見てください。

後順の実装コード:

// 后序遍历 先打印左节点,再输出右节点,最后才输出本身
public static void postOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    postOrder(root.left);
    postOrder(root.right);
    System.out.print(root.data + " ");
}
复制代码

そこに通過する3つの方法がありますが、すべて同じですが、出力のためだけでなく、同じには、上記の研究のように多くの後、私はあなたがバイナリツリーの多くの知識を持っている必要があります信じているが、バイナリツリートラバーサルはその後、非常に簡単ですのが一般的で比較的ユニークなバイナリを理解してみましょう:バイナリ検索ツリー

バイナリ検索ツリー

私たちは、この木は見て異常な利点の面で持っていなければならないことを知ることができる名前からも二分探索木として知られているバイナリ検索ツリーは、確かにそうである、二分探索木は確かに生まれて見て木ですが、それは、データをすばやく見つけることが、またすぐに挿入するためにサポートし、データを削除するだけでなく、サポートしています。それはどのようにこのにそれを行うのですか?私たちは理解し始める二分探索木のコンセプトで始まります。

バイナリ検索ツリー:ツリー内の任意のノード、このノードのその左部分木の値の各ノードが値未満である必要があり、このノードの右部分木の値は、ノードの値よりも大きくなります。

理解することは困難?覚えていますか?それはここで私たちはゆっくり理解するための木に直面している、バイナリ検索ツリーを定義し、重要ではありません。

バイナリ検索ツリーの定義によると、各ツリーノードの値は、親ノードよりも小さいままである、ノードが正しい親ノードの値よりも大きいです。62個のノード のすべての左ノードの値が62未満であるべきである、すべての右ノード 値が62より大きくなければなりません。風雲がこの条件を満たしている必要があり、ツリーの各ノードについて、私たちは別のノード35への私たちの木を取り35は、左の部分木47であるので、その右部分木の最大のノード値は、47を超えることはできません。バイナリ検索ツリーの規則に従って、左サブツリーの値は、ノードの値未満です。

単語と名前を見つけるためのバイナリ検索ツリーので、我々はそれを学習を開始するために二分探索木から二分探索木を見つけます。

バイナリ検索ツリーを操作して下さい

ノードは左のサブツリーのルートノードよりも少ないがいる限り、ここでは避けられない場合、返されるルートノードと同じである場合はそのため二分探索木の特性のため、我々は、ノードと話すべきデータ比較を見つける必要がある左の子再帰検索などライン上の木、再帰的に右部分木に、ここでは右のサブツリー以上、この場合。見た目の半分がやや似てデータを見つけるたびに、半分にカットされているので、これは、すべての実装この機能に住んで削除し、すぐに挿入し、すばやく見つける達成することができます。

ここでは、我々は上記風雲二分探索木37個のノードに等しい値を見つける必要があり、バイナリ検索ツリーを見つけるために、プロセスの理解を高めるために、ダイナミックマップを使用してフローチャートを達成する方法である時、私たちは見てみましょう。

検索操作のバイナリ検索ツリー

  • 37 <62と比較して、62と37との第1、左サブツリー内検索を続けます
  • 図2に示すように、左サブツリーのノード58,37は<58であり、左の部分木に見えます
  • 3、左部分木のノード47,37は<47であり、左部分木に見えます
  • 図4に示すように、左サブツリー右サブツリー内のルックアップ、ノード35、37> 35であります
  • 図5に示すように、ノードの右の部分木は、37 = 37、37ノードリターンします

完成した概念を見た後、私たちは、二分探索木の実装のコードのルックアップ操作を見てみましょう

/**
 * 根据值查找树
 * @param data&emsp;值
 * @return
 */
public TreeNode find(int data) {
    TreeNode p = tree;
    while (p != null) {
        if (data < p.data) p = p.left;
        else if (data > p.data) p = p.right;
        else return p;
    }
    return null;
}
复制代码

バイナリ検索ツリー挿入

ほぼ同様にあなたが大規模なデータノードよりデータを挿入すると、ツリーの右の子ノードが空の場合、それは直接右の子に新しいデータを配置し、ルートを見始めると挿入して下さい。空でない場合は、その後、再帰的に挿入位置を見つけ、右のサブツリーを横断します。同様に、データを挿入する場合には、ノードの値よりも小さい場合、ノードの左サブツリーが空の場合、新しいデータは、左の子ノードの位置に挿入され、空でない場合、次に、再帰的挿入位置を見つけるために、左の部分木をトラバースされます。

我々は、挿入プロセスを確認するために、ダイナミックマップを使用して、我々は63を挿入するとします。

  • > 62 1,63は、右の子の木を見つけるために続けています。
  • 左側のサブツリー内の2,63 <88、探し続けて
  • 3,63 <73、73はリーフノードであるので、63は左サブツリー73となるからです。

バイナリ検索ツリー挿入実装コードを見てみましょう

/**
 * 插入树
 * @param data
 */
public void insert(int data) {
    if (tree == null) {
        tree = new TreeNode(data);
        return;
    }

    TreeNode p = tree;

    while (p != null) {
        // 如果值大于节点的值,则新树为节点的右子树
        if (data > p.data) {
            if (p.right == null) {
                p.right = new TreeNode(data);
                return;
            }
            p = p.right;
        } else { // data < p.data
            if (p.left == null) {
                p.left = new TreeNode(data);
                return;
            }
            p = p.left;
        }
    }
}
复制代码

バイナリ検索ツリーの削除

見つけて、より複雑な挿入、3例についてのポイントを削除するよりも、論理削除:

最初のケース:あなたはノードが子ノードを持たない削除したい場合は、我々はあなたがノードのポインタを削除したいポイントがnullに設定されている、唯一の直接の親ノードを必要とします。例えば、図中のノード51を削除します。

後者の場合:あなたが唯一の子ノード(のみ左の子か右の子ノード)を削除したいノードは、我々は唯一の親ノードを更新する必要がある場合は、あなたが上のノードを削除するには、子ノードを指すようにノードポインタを削除したいポイントそれは。例えば、図中のノード35を削除します。

第三の場合:ノードを削除したい場合は、より複雑である、2人の子供を持っています。私たちは、削除するノード上で、それを置き換えるために、ツリーの中で最小のノードの右の子ノードを見つける必要があります。そして、最小値は確かに子ノードを残していないので(左の子ノードが存在する場合、それは最小のノードではない)、最小のノードを削除するので、私たちは最小のノードを削除するには、上記2つのルールを適用することができます。例えば、図中のノード88を削除します。

最初の2例は少し単純、第三のケースは、私はあなたを助けることを望んで、ダイナミックマップを作成しました。

バイナリ検索ツリーの削除操作の実装コードを見てみましょう

public void delete(int data) {
    TreeNode p = tree; // p指向要删除的节点,初始化指向根节点
    TreeNode 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) { // 查找右子树中最小节点
        TreeNode minP = p.right;
        TreeNode minPP = p; // minPP表示minP的父节点
        while (minP.left != null) {
            minPP = minP;
            minP = minP.left;
        }
        p.data = minP.data; // 将minP的数据替换到p中
        p = minP; // 下面就变成了删除minP了
        pp = minPP;
    }

    // 删除节点是叶子节点或者仅有一个子节点
    TreeNode child; // p的子节点
    if (p.left != null) child = p.left;
    else if (p.right != null) child = p.right;
    else child = null;

    if (pp == null) tree = child; // 删除的是根节点
    else if (pp.left == p) pp.left = child;
    else pp.right = child;
}
复制代码

極端な場合には、バイナリ検索ツリーは、リンクされたリストに退化するので、我々は、知識の上記二分探索木の一部を理解し、例えば、各ノードは1つのしか残っノードがあり、これは時間の複雑さはO(N)となりますこのような状況を避けるため、新しいツリーの出現と呼ばれるためにバランスの取れた、バイナリ検索ツリーが関連、原因少し長くここに利用可能なスペースに、私はあなたがここで見る信じることは平衡二分探索木の上に、少し疲れジュニアパートナーとなっています私は知識がここに示されません。

あなたがバグの記事を見つけた場合、あなたはまた、あなたが、私はよく書くと思うなら、一緒ジュニア相手に読まれるあなたの記事を共有するために歓迎し、指摘、そして私はまた、公開番号を作成し、興味のある方は公衆に従うことができます番号、交換を学習。

個人公開番号

参考資料

  • (オタク時間)の米国のデータ構造とアルゴリズム

おすすめ

転載: juejin.im/post/5d42d962f265da03c502ef7a