二叉树及哈夫曼树

二叉树是一种常用的数据结构,熟练掌握二叉树的各种算法,是必须的。本科时学过数据结构课程,但因当时课程繁多,且对很多概念理论不甚熟悉,数据结构课程也是得过且过,虽言考试顺利通过,但有许多盲点,还有很多知识点,虽然知道其基本思路,但一旦说要动手实现,抓耳挠腮半响,还是一堆的error。痛定思痛,与其忍受这种痛苦,还不若痛下决心将其一一击破。

问题描述一:
假设二叉树采用二叉链存储结构,设计一个算法,输出从每个叶子结点到根结点的路径。

有一个好的想法是简单,但要实现它可不是容易的事,这在编程中经常体现。我使用的李春葆李老师的《数据结构》一书中使用了队列实现,设计的队列为非循环队列,将所有已扫描过的结点指针进队列,并在队列中保存双亲结点的位置。当找到一个叶子结点时,在队列中通过双亲结点的位置输出该叶子结点到根结点的路径。

这种方法是可行的。但因为我先前用堆实现过二叉树遍历的非递归算法,这一次我便想尝试看用堆是否能够实现。实现思想是先将根结点的所有左子结点入栈,并置所有栈内结点的标记为0,代表是其右节点还未访问,而后弹出栈顶结点,输出其到根结点路径,再取栈顶结点(它应该是刚弹出栈顶结点之父结点),将其标记置1, 代表其右结点已经访问,然后将其右子节点与其右子结点的所有左子结点入栈,并置入栈结点标记为0, 弹出栈顶结点,输出其到根结点路径,并不断弹出标记为1的栈顶结点,再取栈顶结点,将其标记置1,代表其右结点已经访问,如上循环,直至栈空。

简要思想是:将所有左结点入栈,输出,弹出左右结点都已访问的父结点,再访问右结点。

算法实现如下:
void leaf2root(BiNode* _node)
{
    struct stack
    {
        BiNode* data;
        int flag;
    }st[MAX];
    int top=-1;
    BiNode* p, *s;

    while(_node!=NULL)
    {
        top++;
        st[top].data=_node;
        st[top].flag=0;
        _node=_node->lchild;
    }     //压入根结点及其所有左子结点,并设标记为0

    while(top>-1)
    {
        p=st[top].data;
        if(p->lchild==NULL&&p->rchild==NULL)
        {
            for(int i=top;i>=0;i--)
                cout<<st[i].data->data<<"->";
            cout<<endl;
            top--;
        }     //输出叶子结点到根结点的路径

        while(top>-1&&st[top].flag==1)
        {
            top--;
        }      //如果栈顶结点是父节点,且其左右子结点均已访问过,就弹出之

        if(top>-1)
        {
            st[top].flag=1;
            p=st[top].data;
            s=p->rchild;
            while(s!=NULL)
            {
                top++;
                st[top].data=s;
                st[top].flag=0;
                s=s->lchild;
            }     //将栈顶结点的右子结点及其右子结点的所有左子结点压栈,并设标记为0
        }
    }
}

李老师的算法实现如下:
void leaf2root_1(BiNode* _node)
{
    struct snode
    {
        BiNode* node;
        int parent;
    }q[MAX];
    int front, rear, p;
    front=rear=-1;
    rear++;
    q[rear].node=_node;
    q[rear].parent=-1;
    while(front<rear)
    {
        front++;
        _node=q[front].node;
        if(_node->lchild==NULL&&_node->rchild==NULL)
        {
            p=front;
            while(q[p].parent!=-1)
            {
                cout<<q[p].node->data<<"->";
                p=q[p].parent;
            }
            cout<<q[p].node->data<<endl;
        }
        if(_node->lchild!=NULL)
        {
            rear++;
            q[rear].node=_node->lchild;
            q[rear].parent=front;
        }
        if(_node->rchild!=NULL)
        {
            rear++;
            q[rear].node=_node->rchild;
            q[rear].parent=front;
        }
    }
}

问题描述二:如何创建二叉树?

二叉树的创建既可用递归算法实现,也可用非递归实现。

递归实现:
输入采用先序输入,先构建根结点,而后左结点,右结点
void create_bitree(BiNode*& _node)
{
    char c;
    cin>>c;
    if(c=='*')
    {
        _node=NULL;
        return;
    }
    else
    {
        _node=(BiNode*)malloc(sizeof(BiNode));
        _node->data=c;
        _node->lchild=NULL;
        _node->rchild=NULL;
        create_bitree(_node->lchild);
        create_bitree(_node->rchild);
    }
    return;
}

非递归实现:
非递归实现使用一个string类型的字符串,将二叉树序列用字符串表示起来,用逗号将左右结点隔开,用()将左右结点包括,表示一个结点的子结点。遍历输入字符串,若为(,说明下一个输入应该是左结点,若为,,说明下一个结点应该是右结点,若为),说明有一个结点输入完毕,可以弹栈;若为字母,压栈。。。
void create_bitree_1(BiNode*& root, string str)
{
    BiNode* st[MAX], *p;
    int top=-1,flag=0;
    string::size_type i=0;
    p=root;
    while(i<str.size())
    {
        switch(str[i])
        {
            case '(':
                flag=1;
                break;
            case ',':
                if(str[i-1]!='(')
                    top--;
                flag=2;
                break;
            case ')':
                if(str[i-1]!=',')
                    top--;
                break;
            default :
                p=(BiNode*)malloc(sizeof(BiNode));
                p->data=str[i];
                p->lchild=NULL;
                p->rchild=NULL;

                if(flag==1)
                {
                    st[top]->lchild=p;
                }

                if(flag==2)
                {
                    st[top]->rchild=p;
                }
                top++;
                st[top]=p;
                if(i==0)
                    root=p;
                p=NULL;
        }
        i++;
    }
}

问题描述3:先序,中序,后序实现二叉树遍历

遍历也有递归及非递归实现,递归实现起来较为简单,非递归实现用栈保存信息,较为麻烦,但常是考点。

递归算法:
void pre_order(BiNode* _node)
{
    if(_node==NULL)
        return;
    cout<<_node->data<<" ";
    pre_order(_node->lchild);
    pre_order(_node->rchild);
}

void in_order(BiNode* _node)
{
    if(_node==NULL)
        return;
    in_order(_node->lchild);
    cout<<_node->data<<" ";
    in_order(_node->rchild);
}

void post_order(BiNode* _node)
{
    if(_node==NULL)
        return;
    post_order(_node->lchild);
    post_order(_node->rchild);
    cout<<_node->data<<" ";
}

非递归算法
先序算法1:
先将(根结点指针,1)进栈;
while(栈不空)
{
    if(栈顶元素未访问过)
    {
        p=st[top].pt; 退栈;
        将*p结点的右孩子结点指针进栈,tag=1;
        将*p结点的左孩子结点指针进栈,tag=1;
        将*p结点指针进栈,tag=0;
    }
    else if(栈顶元素可直接访问)
        访问栈顶元素并退栈;
}
其中心思想也就是先将右孩子结点,再左孩子结点一一进栈,最后父亲结点进栈,始终保证父亲结点在孩子结点之前
void pre_order_1(BiNode* _node)
{
    struct stack_node
    {
        bool flag;
        BiNode* pt;
    }stack[MAX];
    int top=-1;

    top++;
    stack[top].flag=false;
    stack[top].pt=_node;

    while(top>-1)
    {
        if(!stack[top].flag)
        {
            BiNode* p=stack[top].pt;
            top--;
            if(p!=NULL)
            {
                top++;
                stack[top].flag=false;
                stack[top].pt=p->rchild;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->lchild;
                top++;
                stack[top].flag=true;
                stack[top].pt=p;
            }
        }

        if(stack[top].flag&&top>=0)
        {
            cout<<(stack[top].pt)->data<<" ";
            top--;
        }
    }
}

中序算法1
中序算法1和先序算法1的思想相似,不过进栈顺序有些变化,它先将右孩子结点进栈,再父亲,再左孩子结点进栈
void in_order_1(BiNode* _node)
{
    struct stack_node
    {
        bool flag;
        BiNode* pt;
    }stack[MAX];
    int top=-1;

    top++;
    stack[top].flag=false;
    stack[top].pt=_node;

    while(top>-1)
    {
        if(!stack[top].flag)
        {
            BiNode* p=stack[top].pt;
            top--;

            if(p!=NULL)
            {
                top++;
                stack[top].flag=false;
                stack[top].pt=p->rchild;
                top++;
                stack[top].flag=true;
                stack[top].pt=p;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->lchild;
            }
        }

        if(stack[top].flag&&top>-1)
        {
            cout<<stack[top].pt->data<<" ";
            top--;
        }
    }
}

后序算法1
后序算法1和先序算法1的思想亦类似,不过进栈顺序不一样,它先将父亲结点进栈,再右孩子,再左孩子进栈
void post_order_1(BiNode* _node)
{
    struct stack_node
    {
        bool flag;
        BiNode* pt;
    }stack[MAX];
    int top=-1;

    top++;
    stack[top].flag=false;
    stack[top].pt=_node;

    while(top>-1)
    {
        if(stack[top].flag==false)
        {
            BiNode* p=stack[top].pt;
            top--;

            if(p!=NULL)
            {   
                top++;
                stack[top].flag=true;
                stack[top].pt=p;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->rchild;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->lchild;
            }
        }

        if(stack[top].flag&&top>-1)
        {
            cout<<stack[top].pt->data<<" ";
            top--;
        }
    }
}

先序算法2:
由先序遍历过程可知,先访问根结点,再访问左子树,最后访问右子树,因此,先将根结点进栈,在栈不空时循环,出栈p, 访问*p结点,将其右孩子结点进栈,再将左孩子结点进栈
void post_order_1(BiNode* _node)
{
    struct stack_node
    {
        bool flag;
        BiNode* pt;
    }stack[MAX];
    int top=-1;

    top++;
    stack[top].flag=false;
    stack[top].pt=_node;

    while(top>-1)
    {
        if(stack[top].flag==false)
        {
            BiNode* p=stack[top].pt;
            top--;

            if(p!=NULL)
            {   
                top++;
                stack[top].flag=true;
                stack[top].pt=p;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->rchild;
                top++;
                stack[top].flag=false;
                stack[top].pt=p->lchild;
            }
        }

        if(stack[top].flag&&top>-1)
        {
            cout<<stack[top].pt->data<<" ";
            top--;
        }
    }
}

中序遍历算法2:
由中序遍历过程可知,采用一个栈保存需要返回的结点指针,先扫描根结点的所有左结点将它们一一进栈,然后出栈一个结点,显然该结点没有左孩子结点或者左孩子结点已访问过,访问它,然后将该结点的右孩子结点,进栈,再将该右孩子结点的所有左孩子结点一一进栈,如此这样,直到栈空为止
void in_order_2(BiNode* _node)
{
    BiNode* st[MAX],*p;
    int top=-1;

    if(_node!=NULL)
    {
        p=_node;

        while(top>-1||p!=NULL)
        {
            while(p!=NULL)
            {   
                top++;
                st[top]=p;
                p=p->lchild;
            }

            if(top>-1)
            {
                p=st[top];
                top--;
                cout<<p->data<<" ";
                p=p->rchild;
            }
        }
    }
}

后序遍历算法2:
后序遍历采用一个栈保存需要返回的结点指针,先扫描根结点的所有左结点一一进栈,出栈一个结点,将该结点的右孩子结点入栈,再扫描该右孩子结点的所有左结点入栈。当一个结点的左右孩子结点均访问后再访问该结点,如此这样,直到栈空。显然,难点在于如何判断左右结点俱已访问。
void post_order_2(BiNode* _node)
{
    BiNode* st[MAX],*p;
    int flag,top=-1;
    if(_node!=NULL)
    {
        do
        {
            while(_node!=NULL)
            {
                top++;
                st[top]=_node;
                _node=_node->lchild;
            }
           
            p=NULL;
            flag=1;

            while(top!=-1&&flag)
            {
                _node=st[top];
                if(_node->rchild==p)
                {
                    cout<<_node->data<<" ";
                    top--;
                    p=_node;
                }
                else
                {
                    _node=_node->rchild;
                    flag=0;
                }
            }
        }while(top!=-1);
    }
}

问题描述3:如何销毁二叉树?
在二叉树创建时,手动申请了内存,因此也要手动进行释放,释放过程中先保存父亲结点指针,将父亲结点占用空间删除后,再将左孩子及右孩子结点占用的空间删除
void del_tree(BiNode* _node)
{
    BiNode* left;
    BiNode* right;
    if(_node!=NULL)
    {
        left=_node->lchild;
        right=_node->rchild;
        delete _node;

        del_tree(left);
        del_tree(right);
    }
}

以下是一些简单的算法实现,实现诸如二叉树的高度,宽度等等
BiNode* find_node(BiNode* _node, char c)
{
    if(_node==NULL)
        return NULL;
    else if(_node->data==c)
        return _node;
    else
    {
        if(find_node(_node->lchild,c)!=NULL)
            return _node->lchild;
        else
            return find_node(_node->rchild,c);
    }
}

BiNode* lchild(BiNode* _node)
{
    return _node->lchild;
}

BiNode* rchild(BiNode* _node)
{
    return _node->rchild;
}

int tree_height(BiNode* _node)
{
    if(_node==NULL)
        return 0;
    int left=tree_height(_node->lchild);
    int right=tree_height(_node->rchild);
    return left>right?left+1:right+1;;
}

int tree_width(BiNode* _node)
{
    if(_node==NULL)
        return 0;
    if(_node->lchild==NULL&&_node->rchild==NULL)
        return 1;
    int left=tree_width(_node->lchild);
    int right=tree_width(_node->rchild);
    return left+right;
}

int node_total(BiNode* _node)
{
    if(_node==NULL)
        return 0;
    int left=node_total(_node->lchild);
    int right=node_total(_node->rchild);
    return left+right+1;
}

问题描述4:如何层次遍历二叉树?
层次遍历有两种方法
第一种方法,我们可以使用两个栈a,和b
开始将根结点压入栈a
while(栈a及栈b都不空)
{
    while(栈a不空)
    {
        将a中所有元素弹栈,输出
        将a中所有元素的孩子结点压入栈b
    }
    while(栈b不空)
    {
        将b中所有元素弹栈,输出
        将b中所有元素的孩子结点压入栈a
    }
}
算法实现如下:
void layer_order(BiNode* _node)
{
    BiNode* st_1[MAX], *st_2[MAX],*p;
    int top_1=-1,top_2=-1;

    if(_node==NULL)
        return;
    top_1++;
    st_1[top_1]=_node;

    while(top_1>-1||top_2>-1)
    {
        while(top_1>-1)
        {
            p=st_1[top_1];
            top_1--;
            cout<<p->data<<" ";
            if(p->rchild!=NULL)
            {
                top_2++;
                st_2[top_2]=p->rchild;
            }
            if(p->lchild!=NULL)
            {
                top_2++;
                st_2[top_2]=p->lchild;
            }
        }
        cout<<endl;
        while(top_2>-1)
        {
            p=st_2[top_2];
            top_2--;
            cout<<p->data<<" ";
            if(p->lchild!=NULL)
            {
                top_1++;
                st_1[top_1]=p->lchild;
            }
            if(p->rchild!=NULL)
            {
                top_1++;
                st_1[top_1]=p->rchild;
            }
        }
        cout<<endl;
    }
}

第二种方法稍难理解些,采用递归算法,设h返回p所指结点的高度,其初值为0.找到指定结点时,返回其层次,否则返回0.lh作为一个中间变量在计算搜索层次时使用,其初值为1.
void level(BiNode* _node,char data,int &h, int lh)
{
    if(_node==NULL)
        h=0;
    else if(_node->data==data)
        h=lh;
    else
    {
        level(_node->lchild,data,h,lh+1);
        if(h==0)
            level(_node->rchild,data,h,lh+1);
    }
}

问题描述5:输出二叉树
其实以上的遍历算法也对二叉树进行了输出,但是少了一种输出方法,在创建的时候,我们曾用过A(B,C)这种类似的格式来创建二叉树,而今我们也想以这种方式进行输出,那该怎么办呢?
void disp_tree(BiNode* _node)
{
    if(_node!=NULL)
    {
        cout<<_node->data;
        if(_node->lchild==NULL&&_node->rchild==NULL)
            return;
        cout<<"(";
        if(_node->lchild!=NULL)
            disp_tree(_node->lchild);
        cout<<",";
        if(_node->rchild!=NULL)
            disp_tree(_node->rchild);
        cout<<")";
    }
}

问题描述6:如何构建哈夫曼树
构建哈夫曼树稍为简单
首先创建一个vector,用来保存哈夫曼树结点的指针。
while(vector的大小!=1)
{
    找出vector中最小的两个元素,用left right表示;
    将最小的两个元素从vector中删除;
    取left right的权重和,新创建一个结点,插入vector;
}
HTNode* create_HTTree(vector<HTNode*>& list_htnode)
{
    int smallest1,smallest2;
    vector<HTNode*>::const_iterator beg;
    HTNode* left,*right;
    while(list_htnode.size()>1)
    {
        smallest1=htnode_smallest(list_htnode);
        left=*(list_htnode.begin()+smallest1);
        list_htnode.erase(list_htnode.begin()+smallest1);
        smallest2=htnode_smallest(list_htnode);
        right=*(list_htnode.begin()+smallest2);
        list_htnode.erase(list_htnode.begin()+smallest2);

        HTNode* temp_root=(HTNode*)malloc(sizeof(HTNode));
               temp_root->weight=left->weight+right->weight;
        temp_root->lchild=left;
        temp_root->rchild=right;
        list_htnode.push_back(temp_root);
    }
    return list_htnode[0];
}

以上算法我都一一实现过,对于二叉树的知识点涵盖较全,除了没有线索二叉树以及中后序创建二叉树外。一一实现大约花费了我一个星期的工夫,花了这么长时间,也真是值了,终于把盲点清扫一空,把二叉树弄完,下一步开始图算法的清扫了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值