はじめに:
この記事は主に、コードの実装、バイナリ ツリーのトラバーサル、再帰と非再帰 (スタックを使用) について説明します。主に理解を容易にするために、詳細なコメントがコードに直接追加され、レビューや後での口述が容易になります。主にその基本的な考え方を理解し、後期の応用に向けた基礎を築きます。
トラバーサルの意味は、バイナリ ツリーに対してさまざまな操作を実行し、その場にある各ノードに注目し、ルート ノードに到達したときに現在のノードに対して操作を実行することです。
目次:
目次
1. 事前注文トラバース。
1.1 事前注文トラバーサル - 再帰
はじめに: 前文は次のとおりです。まずルート ノードにアクセスし、次にその左側の子にアクセスし、次に右側の子 (ルートの左右) にアクセスします。
//前序遍历,递归
void PreOrder(BTNode *node)
{
if(node==NULL)//当前结点为空时,返回上一层递归空间
{
printf("#");
return;
}
//结点非空时
visit(node);
PreOrder(node->lchild);
PreOrder(node->rchild);
}
1.2 事前注文トラバーサル - 非再帰的
概要: 非再帰的で、スタック (ツリー ノード ポインタを格納する配列とトップ マーク top を加えたもの) を使用してツリー ノードのポインタを格納します。ツリーが空でない場合、最初にツリーがスタックにプッシュされ、次にスタックが空でない場合、ポップ操作が実行されます。事前順序トラバーサルでスタックをポップする場合、スタックをポップした後、最初にノード情報にアクセスし、ノードに正しい子があるかどうかを判断し、存在する場合は、正しい子のポインタをスタックに格納します。次に、左側の子があるかどうかを判断し、存在する場合は、左側の子ポインタをスタックに格納します。
//前序遍历,非递归
void Stack_PreOrder(BTNode *node)
{
if(node==NULL)//树为空,不处理
return;
//创建一个栈,存放树结点类型的地址
BTNode* Stack[10];
int top=-1;
//工作指针,随着p指针,记录树的当前结点位置
BTNode *p=NULL;
//当树非空时,进行操作
if(node !=NULL)
{
//入栈
top++;
Stack[top]=node;
//随后进行出栈操作,只有栈非空时,才可出栈
while(top != -1)
{
//取出此时栈顶元素
p=Stack[top];
top--;
//然后进行访问当前结点的相关操作
visit(p);
//访问完根,在看该根的右孩子,入栈 ,因为是栈,先进后出,而前序为根左右,根出来后,右入栈,之后左入栈,最后出栈是栈顶出
if(p->rchild!=NULL)
{
top++;
Stack[top]=p->rchild;
}
//访问完右孩子,在看该根的左孩子,入栈
if(p->lchild!=NULL)
{
top++;
Stack[top]=p->lchild;
}
}
}
}
2. 順序どおりの走査
2.1 順序どおりの走査 - 再帰
はじめに: 左ルート右。分からなかったら絵を描けばいいし、新しい機能に入るたびに、そこは新しい空間になる。
//中序遍历-递归
void InOrder(BTNode *node)
{
if(node==NULL)
{
printf("#");
return;
}
InOrder(node->lchild);
visit(node);
InOrder(node->rchild);
}
2.2 順序通りの走査 - 非再帰的
はじめに: 実際には、スタックでも再帰でも、必要なステップは 2 つだけです。最初のステップは、新しいツリーに入る一連の操作です。操作が完了したら、第 2 ステップに入り、反対方向の子ツリーに入ります。このツリーでの操作はまだ前進の第 1 ステップであり、その後、第 2 ステップに進みます。
アイデア: 順序トラバーサルは非再帰操作であり、最も外側の円に do-while ループがあり、最初に実行されてから判断されます。スタックが空でない場合、またはノードが空でない場合は、順序どおりの走査操作が実行されます。
do-while の操作: 最初に左側のサブツリーを操作します: トラバースを続け、要素をスタックにプッシュし、ポインタ アドレスをノードの左側の子に変更します。左側の子が空になるまで停止しません。この時点で、右側の左側のルートの左側の操作が完了します。次に、スタックから要素をポップし、左のルートと右の中央でルート操作を実行し、ルート ノードにアクセスします。これが最初のステップです。次に、2 番目の部分で、方向ツリーに入ります。つまり、ノード ポインタが右側の子アドレスに変更されます。
//中序遍历-非递归
void StackInOrder(BTNode *node)
{
if(node==NULL)//树为空,则不处理
return;
printf("中序遍历-非递归:");
BTNode* p=node;
BTNode* Stack[10];
int top=-1;
do
{
//当结点不为空时,入栈,并进入左孩子。 ——访问左孩子
while(p!=NULL)
{
top++;
Stack[top]=p;
p=p->lchild;
}
//一直遍历左,遍历到空,此时,出栈
p=Stack[top];
top--;
visit(p);//访问根
p=p->rchild;//根访问完,随后,访问右孩子。随后,右孩子中,又是新的树,然后再进行左根右操作,形成循环,从上面再来一圈。
}while(top!=-1 || p!=NULL);//只要树不为空,或者栈内有元素,就一直进行操作。
}
3. ポストオーダートラバーサル
3.1 事後走査 - 再帰
はじめに: 左右の根。
// 后序遍历-递归
void PostOrder(BTNode *node)
{
if(node==NULL)
{
printf("#");
return;
}
PostOrder(node->lchild);
PostOrder(node->rchild);
visit(node);
}
3.2 事後走査 - 非再帰的
はじめに: 面倒ではありますが、ストローク法を使用します。ストローク法では、ルートノードをスタックにプッシュするときと、ポップするかどうかを判断するときの 2 回訪問します。スタックから外されると、それがどのポイントから来たかによって異なります。レイヤーがルート ノードに戻る場合は、右側の子ノードから戻る場合は、スタック操作が実行されます。現在のノードが最初に記録され、次にポップされます。それ以外の場合は、スタックから右のサブツリー ノードをポップします。
ここではシーケンスの途中とは少し異なり、プッシュ、ポップの状況を判断する必要があるため、常にスタックの先頭ポインタを比較する必要があります。
最初にルート ノードがスタックにプッシュされ、次にスタックが空でなくなると、トラバーサル操作が続行されます。前進の最初のステップは、スタック上のプッシュ操作です (上位層がトラバースするとき、つまり、最上位ポインタの左の子が右の子ではない場合、作業ポインタは左の子に更新され、次に左の子に更新されます)子がスタックにプッシュされます). 2 番目のステップ, 左の子 最後に, この時点でスタックをポップする必要があるため、現在のスタックの最上位の要素を取り出します. ツリーに左の子がない場合、または事前にが右の子と同じアドレスを持っている場合は、スタックをポップし、ポップする前にポインタ p を記録します。それ以外の場合は、右の子をスタックにプッシュします。
void StackPostOrder(BTNode *node)
{
printf("后序遍历-非递归:");
if(node==NULL)
return;
BTNode *p=node;//工作指针
BTNode *pre=NULL;//表示上层结点位置
//栈
BTNode *Stack[10];
int top=-1;
//先跟根节点入栈,为了方便第一次判断
top++;
Stack[top]=p;
do
{
//先判断上层结点是否遍历过,没有,则进行左子树都入栈,入到底
if(pre!=Stack[top]->lchild && pre!=Stack[top]->rchild)
{
p=Stack[top]->lchild;//上次没有遍历过左右孩子,那么开始栈顶元素的左孩子入栈操作。
while(p!=NULL)
{
top++;
Stack[top]=p;
p=p->lchild;
}
}
//左孩子方向弄到底后,开始判断,是否需要出栈输出。
p=Stack[top];//记录此时的栈顶元素
if(p->rchild==NULL || pre==p->rchild)//如果右孩子为空,或者上一层和当前结点的右孩子相等,则输出
{
pre=p;//记录当前结点地址
visit(p);//输出
top--;//输出了,栈内指针减少
}
else
{
top++;
Stack[top]=p->rchild;//右孩子入栈
}
}while(top!=-1);
}
5. 一般的なコード
5.1 コード
#include <stdio.h>
#include <stdlib.h>
//创建树,孩子链表
typedef struct BTNode
{
int data;
struct BTNode *rchild,*lchild;
}BTNode;
//创建树结点,并初始化
BTNode* BuyNode(int x)
{
BTNode* node=(BTNode*)malloc(sizeof(BTNode));
node->data=x;
node->lchild=NULL;
node->rchild=NULL;
return node;
}
//手动创建树
BTNode* CreatTree()
{
BTNode* node1=BuyNode(1);
BTNode* node2=BuyNode(2);
BTNode* node3=BuyNode(3);
BTNode* node4=BuyNode(4);
BTNode* node5=BuyNode(5);
node1->lchild=node2;
node1->rchild=node3;
node2->lchild=node4;
node2->rchild=node5;
return node1;
}
//访问当前结点时的操作
void visit(BTNode *node)
{
printf("%d",node->data);
}
//前序遍历,递归
void PreOrder(BTNode *node)
{
if(node==NULL)//当前结点为空时,返回上一层递归空间
{
printf("#");
return;
}
//结点非空时
visit(node);
PreOrder(node->lchild);
PreOrder(node->rchild);
}
//前序遍历,非递归
void Stack_PreOrder(BTNode *node)
{
if(node==NULL)
return;
printf("前序遍历-非递归:");
//创建一个栈,存放树结点类型的地址
BTNode* Stack[10];
int top=-1;
//工作指针,随着p指针,记录树的当前结点位置
BTNode *p=NULL;
//当树非空时,进行操作
if(node !=NULL)
{
//入栈
top++;
Stack[top]=node;
//随后进行出栈操作,只有栈非空时,才可出栈
while(top != -1)
{
//取出此时栈顶元素
p=Stack[top];
top--;
//然后进行访问当前结点的相关操作
visit(p);
//访问完根,在看该根的右孩子,入栈 ,因为是栈,先进后出,而前序为根左右,根出来后,右入栈,之后左入栈,最后出栈是栈顶出
if(p->rchild!=NULL)
{
top++;
Stack[top]=p->rchild;
}
//访问完右孩子,在看该根的左孩子,入栈
if(p->lchild!=NULL)
{
top++;
Stack[top]=p->lchild;
}
}
}
}
//中序遍历-递归
void InOrder(BTNode *node)
{
if(node==NULL)
{
printf("#");
return;
}
InOrder(node->lchild);
visit(node);
InOrder(node->rchild);
}
//中序遍历-非递归
void StackInOrder(BTNode *node)
{
if(node==NULL)
return;
printf("中序遍历-非递归:");
BTNode* p=node;
BTNode* Stack[10];
int top=-1;
do
{
//当结点不为空时,入栈,并进入左孩子。 ——访问左孩子
while(p!=NULL)
{
top++;
Stack[top]=p;
p=p->lchild;
}
//一直遍历左,遍历到空,此时,出栈
p=Stack[top];
top--;
visit(p);//访问根
p=p->rchild;//根访问完,随后,访问右孩子。随后,右孩子中,又是新的树,然后再进行左根右操作,形成循环,从上面再来一圈。
}while(top!=-1 || p!=NULL);//只要树不为空,或者栈内有元素,就一直进行操作。
}
// 后序遍历-递归
void PostOrder(BTNode *node)
{
if(node==NULL)
{
printf("#");
return;
}
PostOrder(node->lchild);
PostOrder(node->rchild);
visit(node);
}
//后序遍历-非递归
void StackPostOrder(BTNode *node)
{
printf("后序遍历-非递归:");
if(node==NULL)
return;
BTNode *p=node;//工作指针
BTNode *pre=NULL;//表示上层结点位置
//栈
BTNode *Stack[10];
int top=-1;
//先跟根节点入栈,为了方便第一次判断
top++;
Stack[top]=p;
do
{
//先判断上层结点是否遍历过,没有,则进行左子树都入栈,入到底
if(pre!=Stack[top]->lchild && pre!=Stack[top]->rchild)
{
p=Stack[top]->lchild;//上次没有遍历过左右孩子,那么开始栈顶元素的左孩子入栈操作。
while(p!=NULL)
{
top++;
Stack[top]=p;
p=p->lchild;
}
}
//左孩子方向弄到底后,开始判断,是否需要出栈输出。
p=Stack[top];//记录此时的栈顶元素
if(p->rchild==NULL || pre==p->rchild)//如果右孩子为空,或者上一层和当前结点的右孩子相等,则输出
{
pre=p;//记录当前结点地址
visit(p);//输出
top--;//输出了,栈内指针减少
}
else
{
top++;
Stack[top]=p->rchild;//右孩子入栈
}
}while(top!=-1);
}
int main()
{
BTNode* root=CreatTree();
//前序遍历打印
printf("前序遍历-递归:");
PreOrder(root);//递归
printf("\n");
Stack_PreOrder(root);//非递归,栈来做
printf("\n");
printf("中序遍历-递归:");
InOrder(root);
printf("\n");
StackInOrder(root);
printf("\n");
printf("后续遍历-递归:");
PostOrder(root);
printf("\n");
StackPostOrder(root);
return 0;
}