導入:
ツリーは非線形構造であり、 1 つずつノードで構成されます。
ツリーのいくつかの基本概念:
ノードの次数: ノードに含まれるサブツリーの数はノードの次数と呼ばれます。上の図に示すように、A の次数は 6 です。
リーフ ノードまたはターミナル ノード: 次数 0 のノードはリーフ ノードと呼ばれます。
非終端ノードまたは分岐ノード: 次数が 0 ではないノード。
親ノードまたは親ノード: ノードに子ノードが含まれる場合、このノードはその子ノードの親ノードと呼ばれます。
子ノードまたは子ノード: たとえば、B は A の子ノードです。
兄弟ノード: 同じ親ノードを持つノードは兄弟ノードと呼ばれます。
ツリーの次数: ツリーにおいて、最大のノードの次数をツリーの次数と呼びます。
ノード階層: ルートから定義され、ルートが第 1 レベル、ルートの子ノードが第 2 レベルなどとなります。
ツリーの高さまたは深さ: ツリー内のノードの最大レベル (上に示すように): ツリーの高さは 4 です (カウントは 1 から始まることに注意してください。これは、空のツリーが 0 であることも意味します)。
ツリー表現
左子右兄弟表記:
二分木の概念と構造
コンセプト:
- 各ノードには最大 2 つのサブツリーがあります。つまり、バイナリ ツリーには 2 より大きい次数を持つノードはありません。
- 二分木の部分木は左右に分かれており、部分木の順序を逆にすることはできません。、
二分木の定義
子は最大 2 つであるため、左の子と右の子を直接定義できます。
二分木の前次数、中間次数、後次数
まず第一に、バイナリ ツリーは 3 つの部分に分割されることを明確にする必要があります。
- ルートノード
- 左のサブツリー
- 右サブツリー
左のサブツリーと右のサブツリーは全体であり、この全体からルート ノード、左のサブツリー、右のサブツリーが細分化されることに注意してください。
予約注文 (ルートが最初): ルートの左のサブツリー、右のサブツリー
順序: 左サブツリーのルート 右サブツリー
後順: 左サブツリー右サブツリー ルート
根元の位置により前・中・後ろの順番が決まります
以下は、前部、中間部、後部シーケンスの元のコードです(再帰を使用して解決します)
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%c ", root->val);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%c ", root->val);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->val);
}
特別な二分木
- フルバイナリツリー:バイナリツリー、各層のノードツリーが最大値に達すると、フルバイナリツリーと呼ばれます
- 完全な二分木: 完全な二分木に基づいて、最初の h-1 層はいっぱいですが、最後の層はいっぱいではありませんが、最後の層は左から右に連続しています。
二分木の性質
- ルート ノードの層の数が 1 として指定された場合、空ではない二分木の i 番目の層には最大 2^(i-1) 個のノードが存在します。
- ルート ノードの層数が 1 に指定された場合、深さ h のバイナリ ツリーの最大ノード数 (つまり、完全なバイナリ ツリーの数) は 2^h-1 になります。
- 任意の二分木について、次数 0 の葉ノードの数が n0、次数 2 の枝ノードの数が n2 の場合、n0 = n2 +1 となります。
- ルート ノードの層の数が 1 として指定された場合、 n 個のノードを持つ完全なバイナリ ツリーの深さ h = LogN になります。
二分木の特性に従って問題を解決する
例 1:
二分木の性質 3 から得られる葉ノードの数は、次数 2+1 の枝ノードの数なので、直接 199+1 = 200 となります。
例 2:
多肢選択の簡単な方法:
n = 2 を直接仮定して計算を実行し、各選択肢と比較して正しい答えを求めます。
従来のソリューション:
未知の数を仮定すると、次数 0 のノードの数は X0、次数 1 のノードの数は X1、次数 2 のノードの数は X2 です。
X0+X1+X2 = 2n が得られます。
X0 と X2 の関係により、X2+1 = X0 となります。
また、完全な二分木であるため、次数 1 のノードの数は 1 または 0 のいずれかになります。したがって、2 つのケースで、X1 = 1 の場合、計算結果は小数となり、条件を満たさないことがわかります。質問の意味なので、X1 = 0 で答えが得られます。
例 3:
答え:
最後の層に X が欠けていると仮定して、木の高さを h と仮定します。
この問題も、選択式問題の答えを質問に持ち込んで比較し、正解を得るというものです。
二分木のノードの数を見つけるにはどうすればよいですか?
解決策 1:
ループトラバーサルを使用するというアイデア
このツリーの各ノードを走査します。空でない場合は size++、それ以外の場合は ++ を実行しません。
このメソッドを使用する場合、複数のツリーのノードを計算する必要がある場合があるため、パラメータが渡されるたびに新しいサイズ アドレスを渡す必要があり、サイズの値がメイン関数で直接出力されることに注意してください。
元のコード:
void TreeSize(BTNode* root,int* psize)//直接传地址
{
if (root == NULL)
return;
else
{
(*psize)++;
TreeSize(root->left, psize);
TreeSize(root->right, psize);
}
}
解決策 2:
分割統治の考え方を採用します。
分割統治の考え方とは、より複雑な問題を、単純なレベルに分解されるまで層ごとに分解することです。大きな問題を小さな問題に分割します。
たとえば、校長が学校全体の人数を数えたい場合、校長はこのタスクを学部長に割り当て、学部長はそれを各クラスの校長に割り当て、各校長は各寮の寮長に割り当てます。
この質問の考え方も同じで、木全体のノードの数を数え、木を左の部分木と右の部分木に分割し、次に左の部分木を左の小さな部分木と右の小さな部分木に再帰的に分割します。最後のレイヤーは 1 ずつ増加します。
元のコード:
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
/*if (root == NULL)
return 0;
else
{
return TreeSize(root->left) + TreeSize(root->right) + 1;
}*/
}
二分木の葉ノードの数を見つけるにはどうすればよいですか?
上記分岐の考え方に従って、推論してみましょう〜
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
else
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
ヒント:
完全なバイナリ ツリーを復元するには、inorder + 任意の preorder または postorder を指定するだけで済みます。
理由:
preorder と inorder はどちらもルート ノードがどれであるかを知っていますが、左のサブツリーと右のサブツリーは知りません。
順序があり、ルート ノードがわかっている場合、順序配置では、左のサブツリーがルート ノードの左側にあり、右のサブツリーがルート ノードの右側にあり、ツリーを簡単に復元できます。 。
バイナリ ツリーに関するいくつかの古典的な例
例 1: バイナリ ツリーの事前順序走査
144. バイナリツリーのプレオーダートラバーサル - LeetCode
タイトル説明:
予防:
再帰のアイデアを使用して、事前順序トラバーサルを実行します。再帰する場合、通常、カウンターはライフサイクルを終了するときにローカル変数が自動的に破棄されるのを防ぐためにアドレスを渡す必要があることに注意してください。
カウンタがグローバル変数を使用すると仮定することは受け入れられません。
関数インターフェイスは複数回呼び出されるため、グローバル変数は常に蓄積されます。
元のコード:
//提前计算好树的结点的个数,方便创建动态数组的个数
int TreeSize(struct TreeNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
void _PrevOrder(struct TreeNode* root,int* arr,int* pcnt)
{
//前序递归遍历,将遍历的结点存储到动态数组中
if(root == NULL)
return;
arr[*pcnt] = root->val;
(*pcnt)++;
_PrevOrder(root->left,arr,pcnt);
_PrevOrder(root->right,arr,pcnt);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize){
int size = TreeSize(root);
int* arr = (int*)malloc(sizeof(int) * size);
int cnt = 0;
//注意传地址,递归调用一般需要传址操作
_PrevOrder(root,arr,&cnt);
*returnSize = cnt;
return arr;
}
例 2: バイナリ ツリーの最大の深さ
タイトル説明:
問題解決のアイデア:
また、分割統治の考え方を採用し、バイナリ ツリーの最大の深さを見つけるには、左側のサブツリーと右側のサブツリーの最大の深さを見つけてから、分割統治を続けて、最大の深さを見つけます。左のサブツリー内の小さな左のサブツリーと小さな右のサブツリーの深さ、分解できなくなるまで +1 することができます
元のコード:
int maxDepth(struct TreeNode* root){
if(root == NULL)
return 0;
int leftmax = maxDepth(root->left) + 1;
int rightmax = maxDepth(root->right) + 1;
return leftmax > rightmax ? leftmax : rightmax;
}
例 3: バランスの取れた二分木の判定
タイトル説明:
問題解決のアイデア:
この問題では、二分木の各ノードが左右のサブツリーの高さの差の絶対値が 1 を超えないことを満たしている必要があるため、まず、ヘッド ノードの左右のサブツリーが満たされているかどうかを判断し、ヘッド ノードの高さの差が 1 を超えていないかどうかを判断します。ノードが満たされていない場合は、直接 false を返します。
ヘッド ノードが満たされている場合は、左右のサブツリーのノードが満たされているかどうかを下方向に再帰し続けます。
元のコード:
int MaxDepth(struct TreeNode* root)
{
if(root == NULL)
return 0;
int leftDepth = MaxDepth(root->left);
int rightDepth = MaxDepth(root->right);
return leftDepth > rightDepth ? leftDepth+1 : rightDepth+1;
}
bool isBalanced(struct TreeNode* root){
if(root == NULL)
return true;
//先判断头结点,接着再判断子节点
int leftDepth = MaxDepth(root->left);
int rightDepth = MaxDepth(root->right);
return abs(leftDepth-rightDepth) < 2 && isBalanced(root->left) && isBalanced(root->right);
}