链表、栈或队列都是线性结构,包含一个数据元素序列。而二叉树是一种层次结构。一颗二叉树要么为空,要么由一个数据元素(称为跟)和两颗独立的二叉树(称为左子树和右子树)。某个节点的左(右)子树的根节点称为该节点的左(右)孩子节点。两颗子树均为空的节点称为叶子节点。
搜索二叉树:其左子树任意节点的值都小于此节点的值,其右子树中任意节点的值都大于此节点的值。
完全二叉树:如果一颗二叉树除最后一层外都保证是满的,且若最后一层不满,所有节点均位于最左边,则称为完全二叉树。
平衡二叉树:它是一颗空树,或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
树的遍历:就是按某种特定次序不遗漏、不重复地访问树的每个节点的过程。访问次序有很多种,主要有中序遍历、前序遍历、后序遍历、深度优先遍历和广度优先遍历几种方式。中序遍历:先访问当前节点的左子树,然后访问当前节点,最后访问当前节点的右子树。后序遍历:先访问当前节点的左子树,再访问当前节点的右子树,最后访问当前节点。前序遍历:先访问当前节点,然后访问当前节点的左子树,最后访问当前节点的右子树。对于二叉树而言,深度优先遍历和先序遍历是相同的。广度优先遍历:首先访问根节点,然后由左至右访问根节点的孩子节点,再由左至右访问跟节点的孙子节点,依次类推。
1)先序遍历
递归方式实现:按照“根节点-左孩子-右孩子”顺序遍历,代码如下:
void preorder(TreeNode *root) //前序递归实现 { if (root==NULL) { return; } cout<<root->val<<"\t"; preorder(root->left); preorder(root->right); }
非递归方式实现:利用栈的特性来实现。实现过程:对于任一节点P,如果P不为空,将P压入栈中。取出栈顶元素赋值给P,输出P节点对应的值。如果P的右孩子节点不为空,则将右孩子节点压入栈;如果P的左孩子节点不为空,则将左孩子节点压入栈。如此循环,直至栈中元素为空。代码如下:
void preorder_stack(TreeNode *root) //前序非递归实现 { if (root==NULL) { return; } stack<TreeNode *> nodes; TreeNode *temp; nodes.push(root); while(!nodes.empty()) { temp=nodes.top(); nodes.pop(); cout<<temp->val<<"\t"; if(temp->right!=NULL) { nodes.push(temp->right); } if(temp->left!=NULL) { nodes.push(temp->left); } } }
2)中序遍历
递归实现:按照“左孩子-根节点-右孩子”顺序遍历,代码如下:
void inorder(TreeNode *root) //中序递归实现 { if (root==NULL) { return; } inorder(root->left); cout<<root->val<<"\t"; inorder(root->right); }
非递归实现:利用栈的特性来实现。实现过程:对于任一节点P,若其左孩子不为空,则将P压入栈并把P的左孩子置为P,然后对当前节点P再进行相同的处理。若其左孩子不为空,则取栈顶元素并进行出栈操作,然后将P置为栈顶元素的右孩子节点。如此循环,直至P为NULL且栈为空为止。代码如下:
void inorder_stack(TreeNode *root) //中序非递归实现 { if (root==NULL) { return; } stack<TreeNode *> nodes; TreeNode *temp=root; while(temp!=NULL || !nodes.empty()) { while(temp!=NULL) { nodes.push(temp); temp=temp->left; } if(!nodes.empty()) { temp=nodes.top(); nodes.pop(); cout<<temp->val<<"\t"; temp=temp->right; } } }
3)后序遍历
递归实现:按照“左孩子-右孩子-根节点”顺序遍历,代码如下:
void postorder(TreeNode *root) //后序递归实现 { if(root==NULL) { return; } postorder(root->left); postorder(root->right); cout<<root->val<<"\t"; }
非递归实现:后序遍历的非递归实现是三种遍历方式中最难的一种。因为在后序遍历中,要保证左孩子和右孩子都已被访问并且左孩子在右孩子前访问才能访问根结点,这就为流程的控制带来了难题。下面介绍一种网上看到比较容易理解的思路。实现过程如下:要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。如果P不存在左孩子和右孩子,则可以直接访问它;或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。代码如下:
void postorder_stack(TreeNode *root) //后序非递归实现 { if(root==NULL) { return; } stack<TreeNode *> nodes; TreeNode *curNode; TreeNode *preNode=NULL; nodes.push(root); while(!nodes.empty()) { curNode=nodes.top(); if((curNode->left==NULL && curNode->right==NULL) || (preNode!=NULL &&(preNode==curNode->left || preNode==curNode->right))) { cout<<curNode->val<<"\t"; nodes.pop(); preNode=curNode; } else { if(curNode->right!=NULL) { nodes.push(curNode->right); } if(curNode->left!=NULL) { nodes.push(curNode->left); } } } }
4)深度优先遍历
对于二叉树而言,深度优先遍历与先序遍历是相同的,不再赘述。
5)广度优先遍历
利用队列的特性来实现。实现过程:对于任一节点P,如果P不为空,将P压入队列中。取出队首元素赋值给P,输出P节点对应的值。如果P的左孩子节点不为空,则将左孩子节点压入队列;如果P的右孩子节点不为空,则将右孩子节点压入队列。如此循环,直至队列中元素为空。代码如下:
void width(TreeNode *root) //广度优先遍历 { if(root==NULL) { return; } queue<TreeNode *> nodes; nodes.push(root); TreeNode *temp; while(!nodes.empty()) { temp=nodes.front(); nodes.pop(); cout<<temp->val<<"\t"; if(temp->left!=NULL) { nodes.push(temp->left); } if(temp->right!=NULL) { nodes.push(temp->right); } } }
版权声明:本文为博主原创文章,未经博主允许不得转载。