序章
二分木は非常に重要なデータ構造であり、他の多くのデータ構造は二分木に基づいて進化しています。バイナリ ツリーには、深さトラバーサルと幅トラバーサルがあります. 深さトラバーサルには、プリオーダー、インオーダー、ポストオーダーの 3 つのトラバーサル方法があります. 幅トラバーサルは、通常、階層トラバーサルと呼ばれるものです. ツリーの定義自体が再帰的な定義であるため、再帰的な方法を使用してツリーの 3 つのトラバーサルを実装することは理解しやすく、コードが非常に単純であるだけでなく、幅トラバーサルについては他のサポートが必要です。ヒープなどのデータ構造。
ここでは、主に二分木の 3 つの走査方法、つまり、順序順走査、順序順走査、および後順走査を紹介します。その中で、順序走査が最も重要です。
トラバーサルの主なアイデア:
トラバーサルの事前順序付け: ルート ノード -> 左のサブツリー -> 右のサブツリー
順序通りのトラバーサル: 左のサブツリー -> ルート ノード -> 右のサブツリー
ポストオーダー トラバーサル: 左のサブツリー —> 右のサブツリー —> ルート ノード
次の図を例にとります:
A: ルート ノード、B: 左ノード、C: 右ノード、プレオーダー オーダーは ABC、インオーダー オーダーは BAC、ポストオーダー オーダーは BCA です。
より複雑なツリーを見てください:
事前順序トラバーサル: ABCDEFGHK; 中間順序トラバーサル: BDCAEHGKF; 後順序トラバーサル: DCBHKGFEA
再帰的な方法は、前後の順トラバーサルを実現します
まず、二分木の構造を定義しましょう。
typedef struct BinaryTreeNode
{
TelemType data;
struct BinaryTreeNode *Left;
struct BinaryTreeNode *Right;
}Node;
ルート ノード -> 左サブツリー -> 右サブツリーの順序でバイナリ ツリーを作成します。
Node* createBinaryTree()
{
Node *p;
TelemType ch;
cin>>ch;
if(ch == 0) //如果到了叶子节点,接下来的左、右子树分别赋值为0
{
p = NULL;
}
else
{
p = (Node*)malloc(sizeof(Node));
p->data = ch;
p->Left = createBinaryTree(); //递归创建左子树
p->Right = createBinaryTree(); //递归创建右子树
}
return p;
}
事前注文トラバーサル:
void preOrderTraverse(Node* root)
{
if( root )
{
cout<<root->data<<' ';
preOrderTraverse(root->Left);
preOrderTraverse(root->Right);
}
}
順序通りのトラバーサル:
void inOrderTraverse(Node* root)
{
if( root )
{
inOrderTraverse(root->Left);
cout<<root->data<<' ';
inOrderTraverse(root->Right);
}
}
ポストオーダー トラバーサル:
void lastOrderTraverse(Node* root)
{
if( root )
{
lastOrderTraverse(root->Left);
lastOrderTraverse(root->Right);
cout<<root->data<<' ';
}
}
以下は、二分木のノードの総数、深さ、および葉ノードの数です。
//二叉树节点总数目
int Nodenum(Node* root)
{
if(root == NULL)
{
return 0;
}
else
{
return 1+Nodenum(root->Left)+Nodenum(root->Right);
}
}
//二叉树的深度
int DepthOfTree(Node* root)
{
if(root)
{
return DepthOfTree(root->Left)>DepthOfTree(root->Right)?DepthOfTree(root->Left)+1:DepthOfTree(root->Right)+1;
}
if( root == NULL )
{
return 0;
}
}
//二叉树叶子节点数
int Leafnum(Node* root)
{
if(!root)
{
return 0;
}
else if( (root->Left == NULL) && (root->Right == NULL) )
{
return 1;
}
else
{
return (Leafnum(root->Left) + Leafnum(root->Right)) ;
}
}
完全なコード:
#include <iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
typedef int TelemType;
typedef struct BinaryTreeNode
{
TelemType data;
struct BinaryTreeNode *Left;
struct BinaryTreeNode *Right;
}Node;
//创建二叉树,顺序依次为中间节点->左子树->右子树
Node* createBinaryTree()
{
Node *p;
TelemType ch;
cin>>ch;
if(ch == 0) //如果到了叶子节点,接下来的左、右子树分别赋值为0
{
p = NULL;
}
else
{
p = (Node*)malloc(sizeof(Node));
p->data = ch;
p->Left = createBinaryTree(); //递归创建左子树
p->Right = createBinaryTree(); //递归创建右子树
}
return p;
}
//先序遍历
void preOrderTraverse(Node* root)
{
if( root )
{
cout<<root->data<<' ';
preOrderTraverse(root->Left);
preOrderTraverse(root->Right);
}
}
//中序遍历
void inOrderTraverse(Node* root)
{
if( root )
{
inOrderTraverse(root->Left);
cout<<root->data<<' ';
inOrderTraverse(root->Right);
}
}
//后序遍历
void lastOrderTraverse(Node* root)
{
if( root )
{
lastOrderTraverse(root->Left);
lastOrderTraverse(root->Right);
cout<<root->data<<' ';
}
}
//二叉树节点总数目
int Nodenum(Node* root)
{
if(root == NULL)
{
return 0;
}
else
{
return 1+Nodenum(root->Left)+Nodenum(root->Right);
}
}
//二叉树的深度
int DepthOfTree(Node* root)
{
if(root)
{
return DepthOfTree(root->Left)>DepthOfTree(root->Right)?DepthOfTree(root->Left)+1:DepthOfTree(root->Right)+1;
}
if( root == NULL )
{
return 0;
}
}
//二叉树叶子节点数
int Leafnum(Node* root)
{
if(!root)
{
return 0;
}
else if( (root->Left == NULL) && (root->Right == NULL) )
{
return 1;
}
else
{
return (Leafnum(root->Left) + Leafnum(root->Right)) ;
}
}
int main()
{
Node *root = NULL;
root = createBinaryTree();
printf("二叉树建立成功");
cout<<endl;
cout<<"二叉树总节点数为:"<<Nodenum(root)<<endl;
cout<<"二叉树深度为:"<<DepthOfTree(root)<<endl;
cout<<"二叉树叶子节点数为:"<<Leafnum(root)<<endl;
cout<<"前序遍历结果:"<<endl;
preOrderTraverse(root);
cout<<endl;
cout<<"中序遍历结果:"<<endl;
inOrderTraverse(root);
cout<<endl;
cout<<"后序遍历结果:"<<endl;
lastOrderTraverse(root);
cout<<endl;
return 0;
}
達成するために非再帰的
非再帰トラバーサルの主なアイデアは、ループやスタックなどのデータ構造を介してバイナリ ツリーをトラバースする問題を解決することです。
二分木の構造を定義します。
struct TreeNode {
TreeNode(int val_) :val(val_), left(NULL), right(NULL) {
}
TreeNode* left;
TreeNode* right;
int val;
};
事前注文トラバーサル:
最初にルート ノードにアクセスし、次に左右の子ノードにそれぞれアクセスします。非再帰的な事前注文トラバーサルでは、スタックを使用してトラバーサル プロセスをシミュレートします。
- 最初にルートノードをスタックに置きます
- ループを開始し、ルート ノードにアクセスして、ルート ノードをスタックからポップします。
- ルート ノードの左の子ノードがスタックにプッシュされ、最後に右の子ノードがスタックにプッシュされ、サイクルが完了します。
- 次に、スタックが空になるまで、前の 3 つのステップの処理がループで実行され、トラバーサルが完了します。
void PreOrderNonRecursive(TreeNode* root) {
stack<TreeNode*> sta;
if (root == NULL)
return;
sta.push(root);
while (!sta.empty()) {
TreeNode* pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL)
sta.push(pNode->right);
if (pNode->left != NULL)
sta.push(pNode->left);
}
}
順序通りのトラバーサル:
最初に左の子ノードにアクセスし、次にルート ノードにアクセスし、最後に右の子ノードにアクセスします。また、スタックを使用してトラバースします。
- 左の子ノードが最初にアクセスされるため、最初に左端のノードがアクセスされる必要があり、最初にルート ノードがスタックにプッシュされ、スタックにプッシュされた各ノードを記録するために中間変数が使用されます。左の子ノード
- 真ん中にプッシュされたノードに左の子ノードがある場合は、左の子ノードがなくなるまで左の子ノードをプッシュし続けます。
- スタックの最上位要素がポップアウトされます。つまり、ルート ノードにアクセスされます。
- スタックの最上位要素に右の子ノードがあるかどうかを判断し、ある場合はその右の子ノードをスタックにプッシュします
- スタックが空になるまでステップ 2 ~ 4 を繰り返し、走査を完了します。
void InOrderNonRecursive(TreeNode* root) {
stack<TreeNode*> sta;
if (root == NULL)
return;
sta.push(root);
TreeNode* pNode = root;
while (!sta.empty()) {
while (pNode != NULL&&pNode->left != NULL) {
pNode = pNode->left;
sta.push(pNode);
}
pNode = sta.top();
cout << pNode->val << endl;
sta.pop();
if (pNode->right != NULL) {
sta.push(pNode->right);
pNode = pNode->right;
}
else
pNode = NULL;
}
}
ポストオーダー トラバーサル:
最初に左右の子ノードをトラバースし、最後にルート ノードをトラバースします。非再帰的なポストオーダー トラバーサルは、3 つのトラバーサル メソッドの中で最も複雑です。最も重要なことは、各ノードの左右の子ノードが NULL であるかどうか、またはその左右の子ノードが訪問されたかどうかを判断し、そうであればそのノードを訪問することです。
- 境界条件チェック、スタック ルート ノード
- 現在のスタック トップ要素を記録します。スタック トップ要素の左右の子ノードが NULL であるか、アクセスされている場合は、現在のノードにアクセスします。
- それ以外の場合は、右の子ノードと左の子ノードを順番にスタックにプッシュします
- スタックが空になるまでステップ 2 とステップ 3 をループします。
void PostOrderNonRecursive(TreeNode* root) {
if (root == NULL)
return;
stack<TreeNode*> sta;
TreeNode* curNode = root;
TreeNode* preNode = NULL;
sta.push(root);
while (!sta.empty()) {
curNode = sta.top();
if ((curNode->left == NULL&&curNode->right == NULL) ||
(preNode != NULL && (preNode == curNode->left || preNode == curNode->right))) {
cout << curNode->val << endl;
sta.pop();
preNode = curNode;
}
else {
if (curNode->right != NULL)
sta.push(curNode->right);
if (curNode->left != NULL)
sta.push(curNode->left);
}
}
}