您的位置:首页 > 其它

二叉树的创建、相关操作、递归和非递归式实现三种遍历

2018-03-03 16:40 603 查看
首先,本文默认读者已经掌握了二叉树的相关概念。本文采用二叉链表作为存储结构。

定义二叉链表的结点:

struct node{
int data;           //数据域
node*lchild;         //指向左子树根节点的指针
node*rchild;         //指向右子树根节点的指针
};


下面我们新建一个二叉树结点:

node* creatnode(int x){           //创建新的结点
node*root =new node;          //使用new分配结点空间
root->data =x;                //对数据域进行赋值
root->lchild=root->rchild=NULL;    //这一句很重要,因为是新的结点还没有对其插入子树,一定置为NULL。
return root;                  //返回新建结点的地址。
}


其实创建二叉树就是在已有的基础上按照某种规则不断的插入新的结点到合适的位置上(比如左子树的数据域<根节点的数据域<右子树的根节点)。我们下面先来看怎么插入结点。

void insert(node*&root,int x){  //往树中查找一个节点,因为要改变二叉树,因此要使用引用,不然新的结点连接不到父亲节点上面
if(root==NULL){            //递归边界,也是插入的位置。
root=creatnode(x);     //将新结点链接上去
return;
}
if(x>=root->data){        //我这里使用的时查找二叉树(BST)的规则作为条件
insert(root->rchild,x);  //在左子树中递归插入
}
else insert(root->lchild,x); //在右子树中递归查询。
}


读者可能对为什么函数参数中使用node*&root,因为root存放的是当前节点孩子的地址,如果为空,说明这是插入新节点的地方,那么将creatnode返回的新节点的地址赋值给root,就实现了父亲和孩子的连接。但是如果不引用,函数中的root的改变不能传递给原树中该节点的指针域。也就意味这该指针域没有被修改,因此新节点没有能连接到父亲结点。因此必须使用引用。

下面见二叉树的创建:

为了方便说明,我们假设所有的数据存储在了一个数组当中,对数组边遍历边插入结点,即可创建一个二叉树。

node*creatTree(int arr[],int n){     //建立树,arr数组存放所有的数据,n表示节点的个数
node*Tree=NULL;                  //首先创建根节点并置空
for(int i=0;i<n;i++){
insert(Tree,arr[i]);         //依次插入即可
}
return Tree;          //最后返回根节点的值
}


二叉树的查找和修改:

使用递归,其实就是从根节点出发,顺着子树一直往下查找,一直到NULL,递归结束。

void search(node*root,int x,int newdata){      //查找、修改操作
if(root==NULL){     //空树了,递归边界
return;
}
if(root->data==x){
root->data=newdata;     //对数据域进行修改
}
search(root->lchild,x,newdata);   //递归查询左子树
search(root->rchild,x,newdata);   //递归查询右子树
}


二叉树的先序遍历(递归式):

先序遍历按照根节点->左子树->右子树的顺序

void preorder1(node*root){     //先序遍历
if(root==NULL){           //递归边界
return;
}
printf("%d ",root->data);   //访问根结点
preorder(root->lchild);     //递归访问左子树
preorder(root->rchild);     //递归访问右子树
}


注意:对一个二叉树的先序遍历序列,第一个一定是该二叉树的根节点

二叉树的先序遍历(非递归式):

非递归需要栈的帮助。工作指针为设为now,每次它所指的结点不空,则对其进行访问,然后压入栈中,now往其左子树走。如果now所指结点为空,那么就从栈中弹出一个节点赋值给now,now往这个节点的右子树走。

void preorder2(node*root){
stack<node*> s;
node*now=root;
while(now!=NULL||!s.empty()){
if(now!=NULL){
s.push(now);
printf("%d ",now->data);
now=now->lchild;
}
else{
now=s.top();
s.pop();
now=now->rchild;
}
}
}


二叉树的中序遍历(递归式):

访问顺序为左子树->根节点->右子树。

void inorder1(node*root){    //中序遍历
if(root==NULL){
return;
}
inorder1(root->lchild);
printf("%d ",root->data);
inorder1(root->rchild);
}


二叉树的中序遍历(非递归式):

这个类别先序遍历即可。

void inorder2(node*root){
stack<node*> s;
node*now=root;
while(now!=NULL||!s.empty()){
if(now!=NULL){
s.push(now);
now=now->lchild;
}
else{
now=s.top();           //取得栈顶元素
printf("%d ",now->data);  //访问
s.pop();              //将栈顶结点弹出
now=now->rchild;     //往右子树方向遍历
}

}
}


二叉树后序遍历(递归式):

访问顺序:左子树->右子树->根节点。

void postorder(node*root){  //后序遍历
if(root==NULL){
return;
}
postorder(root->lchild);
postorder(root->rchild);
printf("%d ",root->data);
}


二叉树后序遍历(非递归式):

后续遍历的非递归式要麻烦一些,因为最后才访问根节点,因此需要对它的子树结点进行标记,防止重复进栈。

void postorder2(node*root){
stack<node*> s;                 //需要使用栈进行存储
node*now,*p;               //now为工作指针,p为标记指针,标记刚被访问过的结点
now=root;p=NULL;           //初始化
while(now!=NULL||!s.empty()){
if(now!=NULL){         //如果不是空结点,那么进行压栈,往左走。
s.push(now);
now=now->lchild;
}
else{
now=s.top();       //取得栈顶元素。
if(now->rchild!=NULL&&now->rchild!=p){   //如果该节点的右子树不空而且没有被访问过
now=now->rchild;       //走向右子树
}
else{
printf("%d ",now->data);    //左右子树为空,或者都已经被访问过了,输出该节点
s.pop();            //出栈
p=now;              //p标记刚被访问的结点
now=NULL;           //将now置空,使其能返回父亲结点走向右子树。
}
}

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐