目次
3.7 バイナリ ツリーが完全なバイナリ ツリーかどうかを判断する
1. バイナリツリーチェーンの構造と実装
1. 二分木の構造
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
}BTNode;
バイナリ ツリー チェーン構造タイプ。これは、左右の子ノードとノードのデータ値を含むノード タイプです。
2. バイナリツリートラバーサル
バイナリ ツリー構造を学習する最も簡単な方法は、バイナリ ツリー構造をたどることです。いわゆるバイナリ ツリー トラバーサル (Traversal) は、ある特定のルールに従ってバイナリ ツリー内のノードに対して対応する操作を順番に実行することであり、各ノードは 1 回だけ操作されます。アクセス ノードによって実行される操作は、特定のアプリケーションの問題によって異なります。トラバーサルはバイナリ ツリー上で最も重要な操作の 1 つであり、バイナリ ツリー上の他の操作の基礎でもあります。4 つの走査方法を以下に示します。
2.1 事前注文トラバーサル
最初にルート ノードにアクセスし、次に左側のサブツリーにアクセスし、最後に右側のサブツリーにアクセスします。
//二叉树前序遍历
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
printf("%d ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
2.2 インオーダートラバーサル
最初に左側のサブツリーにアクセスし、次にルート ノードにアクセスし、最後に右側のサブツリーにアクセスします。
//二叉树中序遍历
void InOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
2.3 ポストオーダートラバーサル
最初に左側のサブツリーにアクセスし、次に右側のサブツリーにアクセスし、最後にルート ノードにアクセスします。
//二叉树后序遍历
void PostOrder(BTNode* root)
{
if (root == NULL)
{
return;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%d ", root->data);
}
2.4 レイヤー順序のトラバーサル
層順序走査は、二分木の各層のノードに従って訪問することです。
アイデア:キューを作成する必要があります。最初にルート ノードをキューに入れ、キューが空でない場合はヘッド ノードが出力されます。ヘッド ノードの左側のサブツリーが空でない場合は、その左側のサブツリー ノードが出力されます。がキューに入ります。キュー ヘッド ノードの右側のサブツリーが空でない場合、その右側のサブツリー ノードがキューに入り、キューにノードがなくなるまで順番に循環します。この時点で、階層トラバーサルは終了します。
//二叉树层序遍历
void LevelOrder(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
printf("%d ", front->data);
QueuePop(&q);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestroy(&q);
}
3. バイナリツリーチェーン構造の実装
3.1 ノードの作成
バイナリ ツリー チェーン構造を実装するには、まずノードを作成し、ノードの作成後に初期化する必要があります。
//创建一个节点
BTNode* BuyBTNode(BTDataType x)
{
BTNode* node = (BTNode*)malloc(sizeof(BTNode));
if (node == NULL)
{
perror("malloc fail");
exit(-1);
}
node->data = x;
node->left = node->right = NULL;
return node;
}
3.2 二分木のノード数
ここでは、グローバル変数を使用する方法と、各ノードの合計数が ++ である方法の 2 つを示します。2 番目の方法は、単純化して、左側のサブツリー ノードの数 + 右側のサブツリー ノードの数 + ルート ノードです。
//二叉树节点个数
int size = 0;
int TreeSize1(BTNode* root)
{
if (root == NULL)
return 0;
//前序遍历
size++;
TreeSize1(root->left);
TreeSize1(root->right);
return size;
}
//二叉树节点个数
int TreeSize2(BTNode* root)
{
return root == NULL ? 0 : TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
3.3 二分木の葉ノードの数
葉ノード数の計算は状況に応じて検討する必要があるが、ルートノードが空の場合は0、ルートノードの左右のサブツリーが空の場合は葉ノード数は1、残りはルート ノードの左右の子です。ツリーが空でなくなったら、左のサブツリーと右のサブツリーを走査して、葉ノードの数を計算します。
//二叉树叶子节点个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
3.4 二分木の高さ
バイナリ ツリーの高さは、左右のサブツリーの最大高さです。最後のレイヤーまで再帰してから、現在のノードの高さを加算する必要があります。
//二叉树的高度或深度(后序遍历)
int TreeHeight(BTNode* root)
{
if (root == NULL)
return 0;
int leftHeight = TreeHeight(root->left);
int rightHeight = TreeHeight(root->right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
3.5 二分木のk番目の層のノードの数
また、ルート ノードの左右のサブツリーを再帰して、各層の左右のサブツリーにノードがいくつあるかを確認し、それらを合計する必要もあります。
//二叉树第k层的节点个数 k >= 1
int TreeKLevelSize(BTNode* root, int k)
{
if (root == NULL)
return 0;
if (k == 1)
return 1;
//k > 1 子树的k-1
return TreeKLevelSize(root->left, k - 1) + TreeKLevelSize(root->right, k - 1);
}
3.6 二分木で値が x であるノードを見つける
二分木のノードを見つける方法も再帰を利用しており、まず二分木が空かどうかを判断し、空であればxは存在せず、ルートノードの値がxと等しい場合に見つけます。最初に左側のサブツリーをトラバースし、次に、x が見つかるまで右側のサブツリーをトラバースします。
//二叉树查找值为x的节点
BTNode* TreeFind(BTNode* root, BTDataType x)
{
if (root == NULL)
return NULL;
if (root->data == x)
return root;
BTNode* ret1 = TreeFind(root->left, x);
if (ret1)
return ret1;
BTNode* ret2 = TreeFind(root->right, x);
if (ret2)
return ret2;
return NULL;
}
3.7 バイナリ ツリーが完全なバイナリ ツリーかどうかを判断する
アイデア: 上記のレイヤー シーケンス トラバーサルと同様に、キューを使用する場合でも、レイヤーごとにノードをキューに追加し、キューから出ます。キューが空のノードに遭遇したら、キューへの入力を停止します。次に、キュー内に空のノードがすべてあるかどうかを判断し、すべて空のノードであれば完全な二分木であり、そうでなければ完全な二分木ではありません。
//判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
Queue q;
QueueInit(&q);
if (root)
QueuePush(&q, root);
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)
{
break;
}
else
{
QueuePush(&q, front->left);
QueuePush(&q, front->right);
}
}
//出到空以后,如果后面全是空,则是完全二叉树
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front != NULL)
{
QueueDestroy(&q);
return false;
}
}
QueueDestroy(&q);
return true;
}
2. バイナリツリーの基本的な OJ 演習
1. 単一値二分木
1.1 トピックの説明
トピックリンク:単一値二分木
ツリーのすべてのノードが同じ値を持つ場合、バイナリ ツリーは単一値のバイナリ ツリーです。指定されたツリーが単一値のバイナリ ツリーである場合のみを返し true
、それ以外の場合は 返しますfalse
。
1.2 トピック分析
アイデア:ツリーが空の場合、それは単一値のバイナリ ツリーであることを意味します。その左のサブツリーが空ではなく、左のサブツリーのノード値がルート ノードの値と等しくない場合は、false を返します。右のサブツリーが空ではなく、右のサブツリーのノード値がルート ノードの値と等しくない場合は、false を返します。
bool isUnivalTree(struct TreeNode* root){
if(root == NULL)
return true;
if(root->left && root->left->val != root->val)
return false;
if(root->right && root->right->val != root->val)
return false;
return isUnivalTree(root->left) && isUnivalTree(root->right);
}
2. 2 つのツリーが同じかどうかを確認する
2.1 トピックの説明
トピックへのリンク:同じツリー
2 つのバイナリ ツリーのルート ノード p と q を指定して、2 つのツリーが同じかどうかを確認する関数を作成します。2 つのツリーは、構造的に同一であり、ノードの値が同じであれば、同一とみなされます。
2.2 トピック分析
アイデア:ルート、左側のサブツリー、および右側のサブツリーが同じかどうかを判断します。—— 1. 構造が同じかどうかを判断します。 2. val が同じかどうかを判断します。
まず、特殊なケースが 2 つあります。つまり、ルート ノード p とルート ノード q が両方とも空の場合は、2 つのツリーが同じツリーであることを意味し、ルート ノード p とルート ノード q のいずれかが空の場合は、が空の場合、2 つのツリーが同じではないことを意味します。
ノード p の値がノード q の値と等しくない場合、2 つのツリーが同じではないことは明らかであり、最終的に p と q の左右のサブツリーをトラバースします。
bool isSameTree(struct TreeNode* p, struct TreeNode* q){
if(p == NULL && q == NULL)
return true;
if(p == NULL || q == NULL)
return false;
if(p->val != q->val)
return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
3. 対称二分木
3.1 トピックの説明
トピックリンク:対称二分木
バイナリ ツリーのルート ノード ルートが与えられた場合、それが軸対称であるかどうかを確認します。
3.2 トピック分析
アイデア:ツリーが軸対称であるかどうかを簡単に判断するために、最初にその軸対称を判断する関数を作成します。
軸対称関数は実際には、2 つの木が同じかどうかを判断する上記の判断と同じです。つまり、最後の再帰では、root1 の左側の部分木と root2 の右側の部分木が同じかどうかを判断する必要があります。 root1 の右側の部分木と root2 の右側の部分木 左側の部分木が同じかどうかを判定し、同じであれば対称二分木であることを意味します。
bool _isSymmetric(struct TreeNode* root1,struct TreeNode* root2)
{
if(root1 == NULL && root2 == NULL)
return true;
if(root1 == NULL || root2 == NULL)
return false;
if(root1->val != root2->val)
return false;
return _isSymmetric(root1->left,root2->right) && _isSymmetric(root1->right,root2->left);
}
bool isSymmetric(struct TreeNode* root){
return !root || _isSymmetric(root->left,root->right);
}
4. バイナリツリーの事前順序走査
4.1 トピックの説明
トピックリンク:バイナリツリーの事前順序走査
バイナリ ツリーのルート ノード ルートを指定すると、そのノード値の 事前順序 走査を返します。
4.2 トピック分析
アイデア 1:このメソッドは再帰的です。まず、事前順序トラバーサルのアルゴリズムを作成します。事前順序トラバーサルのアクセス順序は、ルート ノード、次に左側のサブツリー、最後に右側のサブツリーです。
class Solution {
public:
void preorde(TreeNode* root,vector<int>& ret)
{
if(root == nullptr)
return;
ret.push_back(root->val);
preorde(root->left,ret);
preorde(root->right,ret);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> ret;
preorde(root,ret);
return ret;
}
};
アイデア 2:このメソッドは非。このアイデアは、ツリーを 2 つの部分 (1) 左側のノード、(2) 左側のノードの右側のサブツリーに分割することです。ここで使用されるスタックは、左側のノードの右側のサブツリーにアクセスするために使用されます。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> v;
TreeNode* cur = root;
while(cur || !s.empty())
{
while(cur)
{
v.push_back(cur->val);
s.push(cur);
cur = cur->left;
}
TreeNode* top = s.top();
s.pop();
cur = top->right;
}
return v;
}
};
5. 二分木のインオーダートラバース
5.1 トピックの説明
トピックリンク:バイナリツリーの順序トラバーサル
バイナリ ツリーのルート ノード ルートを指定すると、そのノード値の順序 。
5.2 トピック分析
アイデア 1:再帰を使用して、最初に順序トラバーサルのアルゴリズムを作成します。順序トラバーサルのアクセス順序は、左側のサブツリー、次にルート ノード、最後に右側のサブツリーです。
class Solution {
public:
void inorder(TreeNode* root, vector<int>& ret) {
if (root == NULL)
return;
inorder(root->left, ret);
ret.push_back(root->val);
inorder(root->right, ret);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> ret;
inorder(root, ret);
return ret;
}
};
アイデア 2:非再帰的な方法を使用して、バイナリ ツリーを 2 つの部分 (1) 左側のノード、(2) 左側のノードの右側のサブツリーに分割します。上記の事前順序走査の非再帰的方法と同じですが、v を入力する順序が異なる点が異なります。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> v;
TreeNode* cur = root;
while(cur || !s.empty())
{
while(cur)
{
s.push(cur);
cur=cur->left;
}
TreeNode* top = s.top();
s.pop();
v.push_back(top->val);
cur = top->right;
}
return v;
}
};
6. 二分木の事後走査
6.1 トピックの説明
トピックリンク:バイナリ ツリーの事後走査
バイナリ ツリーのルート ノード ルートを指定すると、そのノード値の事後 。
6.2 トピック分析
アイデア 1:再帰的な方法で、最初に事後トラバーサルのアルゴリズムを作成します。事後トラバーサルのアクセス順序は、左のサブツリー、次に右のサブツリー、最後にルート ノードです。
class Solution {
public:
void postorder(TreeNode *root, vector<int> &ret)
{
if (root == nullptr)
{
return;
}
postorder(root->left, ret);
postorder(root->right, ret);
ret.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode *root) {
vector<int> ret;
postorder(root, ret);
return ret;
}
};
アイデア 2:非再帰的な方法を使用して、バイナリ ツリーを 2 つの部分 (1) 左側のノード、(2) 左側のノードの右側のサブツリーに分割します。注: 初めて 6 がフェッチされたとき、最後に訪問したノードは左側のサブツリーのルート 4 であり、2 回目に 6 がフェッチされたとき、最後に訪問したノードは右側のサブツリーのルート 7 であると仮定します。したがって、別の制約 top->right == prev があります。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> v;
TreeNode* cur = root;
TreeNode* prev = nullptr;
while(cur || !s.empty())
{
while(cur)
{
s.push(cur);
cur = cur->left;
}
TreeNode* top = s.top();
//1.右为空 或者 右子树已经访问过了(上一个访问的节点是右子树的根),可以访问根节点
if(top->right == nullptr || top->right == prev)
{
v.push_back(top->val);
s.pop();
prev = top;
}
else
{
//访问左路节点右子树 ---- 子问题
cur = top->right;
}
}
return v;
}
};
7. 別のツリーのサブツリー
7.1 トピックの説明
トピックリンク:別のツリーのサブツリー
2 つのバイナリ ツリーのルートとサブルートが与えられます。ルートに subRoot と同じ構造とノード値を持つサブツリーが含まれていることをテストします。存在する場合は true を返し、存在しない場合は false を返します。
二分木ツリーのサブツリーには、ツリーの特定のノードと、このノードのすべての子孫ノードが含まれます。ツリーはそれ自体のサブツリーとして見ることもできます。
7.2 トピック分析
アイデア: 3 つのステップ: 1. 同じツリーであるかどうかを判断します。 2. subRoot がルートの左側のサブツリーであるかどうかを判断します。 3. subRoot がルートの右側のサブツリーであるかどうかを判断します。
bool isSameTree(struct TreeNode* p, struct TreeNode* q) {
if (p == NULL && q == NULL)
return true;
//其中一个为空
if (p == NULL || q == NULL)
return false;
if (p->val != q->val)
return false;
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
if(root == NULL)
return false;
if(isSameTree(root,subRoot))
return true;
return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}
この記事に不備がある場合は、以下にコメントしてください。できるだけ早く修正します。