【データ構造】二分木(2)

目次

1. バイナリツリーチェーンの構造と実装

 1. 二分木の構造

 2. バイナリツリートラバーサル

   2.1 事前注文トラバーサル

   2.2 インオーダートラバーサル

   2.3 ポストオーダートラバーサル

   2.4 レイヤー順序のトラバーサル

 3. バイナリツリーチェーン構造の実装

   3.1 ノードの作成

   3.2 二分木のノード数 

   3.3 二分木の葉ノードの数

   3.4 二分木の高さ

   3.5 二分木のk番目の層のノードの数

   3.6 二分木で値が x であるノードを見つける

   3.7 バイナリ ツリーが完全なバイナリ ツリーかどうかを判断する

2. バイナリツリーの基本的な OJ 演習

 1. 単一値二分木

   1.1 トピックの説明 

   1.2 トピック分析

 2. 2 つのツリーが同じかどうかを確認する

   2.1 トピックの説明

   2.2 トピック分析

 3. 対称二分木

   3.1 トピックの説明

   3.2 トピック分析

 4. バイナリツリーの事前順序走査

   4.1 トピックの説明

   4.2 トピック分析

 5. 二分木のインオーダートラバース

   5.1 トピックの説明

   5.2 トピック分析

 6. 二分木の事後走査

   6.1 トピックの説明

   6.2 トピック分析

 7. 別のツリーのサブツリー

   7.1 トピックの説明

   7.2 トピック分析

 


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);
}

   

   


この記事に不備がある場合は、以下にコメントしてください。できるだけ早く修正します。 

古いアイアン、好きになることと注目することを忘れないでください!  

おすすめ

転載: blog.csdn.net/m0_63198468/article/details/131097443