1 树的基本概念

【树】---- 二叉树_二叉树

(1)树是由若干结点组成的具有层次关系的集合,非空树有且只有一个根结点(/)。

(2)某个结点及其下面所有的结点并称为以该结点为根的子树(usr及其下的所有结点就是/的一颗子树,usr是该子树的根)。

(3)结点拥有的子树的个数称为结点的度(/的度为7,home的度为3)。

(4)度为0的节点称为叶子结点(lib就是叶子结点)。

(5)树的深度就是结点的最大层数(上图中的树深度为4)。


注意:
(1)在一颗非空树上,一定会有根结点和叶子结点。当只存在一个结点时,它既是根结点也是叶子结点。

(2)在树上,父结点可以有多个孩子结点,但是除根结点外的孩子结点仅有一个父结点。





2 二叉树的基本概念

【树】---- 二叉树_二叉树_02

2.1 概念

每个结点最多只有两个孩子结点的树被称为二叉树。


2.2 性质

(1)二叉树的第i层最多只有2^(i-1)个结点。

(2)深度为k的二叉树最多有2^k - 1个结点。

          总结点数等于每层的结点数加起来:  total  = 2^0 + 2^1 + 2^2 + ... + 2^(k-1)                                             (1)

                                                   将(1)式乘2:2total  =           2^1 + 2^2 + ... + 2^(k-1) + 2^k                                   (2)

                                                 式(2) - 式(1):   total  = 2^k - 1

(3)对于任意的非空二叉树,其叶子结点数n0比度为2的结点数n2多1

          证明过程如下:

                 假设总结点数为n,度为1的结点数为n1,度为2的结点数为n2,叶子结点数为n0。

                 可以得出:n = n0 + n1 + n2                                                                                                                        (1)

                 通过分析可以发现除了根结点外,每个结点都有唯一的一条边跟其父结点相连。对于有n个结点的二叉树来说,其共有n-1条边。再进一步分析,可以发现每个度为1的节点引出的边为1,度为2的结点引出的边为2,叶子结点引出的边为0。所以有如下公式:

                 n -1 = n1 + 2 * n2                                                                                                                                           (2)

                 联立(1)、(2)可得到:n0 - n2 = 1


2.3 特殊的二叉树

满二叉树:如果一棵二叉树的深度为k,其结点数为2^k - 1则称此树为满二叉树。

完全二叉树:如果一颗二叉树的深度为k,从第1层到第k-1层为满二叉树,并且第k层的结点全部集中在左边,那么该二叉树被称为完全二叉树。


                       【树】---- 二叉树_数据结构_03

                     注意:这里的全部集中在左边的意思是第k层的结点从左边往右边依次排布,不会有间隔。如上图中如果结点11不存在,则不是完全二叉树。另外,满二叉树也是一种特殊的完全二叉树。




3 二叉树的广义表表示形式

3.1 广义表的定义

可以用广义表的形式表示二叉树,形如a(b,c)。表示根结点a的左孩子结点为b,右孩子结点为c,中间用一个逗号隔开。如果某个节点为空则不填任何字符。

以下是几种常见的格式:

        a       : 表示根结点为a,左右孩子结点均为空。

       a(b)   :表示根结点为a,左孩子结点为b,右孩子结点为空。

       a(,c)  :表示根结点为a,左孩子结点为空,右孩子结点为c。

       a(b,c):表示根结点为a,左孩子结点为b,右孩子结点为c。

【树】---- 二叉树_二叉树_04

上图中的二叉树用广义表的形式表示为:L = (A(B(C,D),E(,F)))。


3.2 判断输入的广义表是否合法

假设输入的广义表中:

      (1)元素均为大写字母;

      (2)符号和元素之间无空格;

      (3)广义表仅由大写字母、逗号、左括号、右括号构成。


判断是否合法的条件为(广义表表达式以字符串的形式输入):

      (1)第一个字符必须为大写字母(称为根结点字母,其他所有节点称为子结点字母)。

      (2)第二个字符必须为左括号或者空。

      (3)子结点字母后必须为左括号、逗号、或者右括号。

      (4)逗号后必须为子结点字母。

      (5)左括号后必须为子结点字母或逗号。

      (6)右括号后必须为逗号或右括号。

      (7)左右括号必须匹配,每对匹配的括号中间的元素应该是有限的(顶层元素),在1到3之间。


如下为实现的不怎么样的代码:

#include <iostream>
#include <string>

using namespace std;


class BinaryGLists
{
private:
    struct BinTreeNode
    {
        char data;
        BinTreeNode* lchild;
        BinTreeNode* rchild;
    };

public:
    // 首先判断输入的广义表是否合法
    bool isGListslegal(string str)
    {
        if (str.length() == 0)        /* 长度为0非法  */
        {
            return false;
        }

        unsigned int i = 0;
        if (!isupper(str[i++]))       /* 第一个字符不为大写字母则非法 */
        {
            return false;
        }
        else
        {
            if (str.length() == 1)    /* 只有一个大写字母,合法 */
            {
                return true;
            }
        }



        int prev_type = -1;  
/* 上一个字符的类型,-1:根结点字母,0:子结点字母,1:逗号,2:左括号,3右括号 */
        int current_type = 0;
        char* stack = new char(str.length());  /* 这里用数组模拟栈 */
        if (stack == NULL)
        {
            return false;
        }
        
        int top_index = -1;
        int push_flag = 0;
        while (i < str.length())
        {

            switch (prev_type)
            {
                case -1: 
/* 上一个字符的类型为根结点字母,则下一个元素的类型必须为左括号 */
                    if (str[i] != '(')
                    {
                        return false;
                    }
                    else
                    {
                        prev_type = 2;
                    }

                    break;

                case 0:     
/* 上一个字符的类型为子结点字母,则下一个字符的类型必须为逗号、右括号、左括号 */
                    current_type = getType(str[i]);
                    if ((current_type == 1) || (current_type == 2) || (current_type == 3))
                    {
                        prev_type = current_type;
                    }
                    else
                    {
                        return false;
                    }

                    break;

                case 1:     
/* 上一个字符的类型为逗号,则下一个字符的类型必须为结点字母 */
                    if (isupper(str[i]))
                    {
                        prev_type = 0;
                    }
                    else
                    {
                        return false;
                    }

                    break;

                case 2:     
/* 上一个字符为左括号,则下一个字符必须为结点字母、逗号 */
                    current_type = getType(str[i]);
                    if ((current_type == 0) || (current_type == 1))
                    {
                        prev_type = current_type;
                    }
                    else
                    {
                        return false;
                    }
                    break;

                case 3:     
/* 上一个字符为右括号,则下一结点必须为逗号、右括号     */
                    current_type = getType(str[i]);
                    if ((current_type == 1) || (current_type == 3))
                    {
                        prev_type = current_type;
                    }
                    else
                    {
                        return false;
                    }
                    break;

                default:
                    return false;
                    break;
            }

/* 接下来还需要保证括号是匹配的,并且括号内的元素个数是合法的(每一对括号之间最多只
* 包括左右2个结点,包含3个字符;如果只包含一个结点,包含1个字符;字符的个数在1和3之间)
*/

            if (str[i] == '(')  /* 碰到第一个左括号就可以开始入栈 */
            {
                push_flag = 1;
            }

            if (push_flag == 1)
            {
                top_index++;
                stack[top_index] = str[i];
            }

            if (str[i] == ')')  
/* 碰到右括号就需要进行判断括号匹配是否合法,以及括号间的字符个数是否合法 */
            {
                int current_index = top_index;
                while (top_index >= 0)
                {
                    if (stack[top_index] != '(')
                    {
                        top_index--;
                    }
                    else
                    {
                        int count = current_index - top_index - 1;
                        if ((1 <= count) && (count <= 3))   
                        /* 最近的一对括号之间的字符个数必须在1和3之间 */
                        {
                            if (top_index > 0)
                            {
                                top_index--;
                            }
                            break;
                        }
                        else
                        {
                            return false;
                        }
                    }
                }

                if (top_index < 0)  /* 没匹配到左括号 */
                {
                    return false;
                }
            }

            i++;
        }

        if (top_index != 0) /* 括号未能正确匹配 */
        {
            return false;
        }

        if ((prev_type != -1) && (prev_type != 3)) 
        /* 字符串必须以右括号结尾,或者只有一个根结点字符 */
        {
            return false;
        }

        delete[] stack;

        return true;
    }

    int getType(char c)
    {
        int ret = -1;

        if (isupper(c))
        {
            c = 'U';
        }

        switch (c)
        {
            case 'U':
                ret = 0;
                break;

            case ',':
                ret = 1;
                break;

            case '(':
                ret = 2;
                break;

            case ')':
                ret = 3;
                break;

            default:
                ret = 99;
                break;
        }

        return ret;
    }

};


using namespace std;

int main()
{
    BinaryGLists binTree;
    string str;

    while (1)
    {
        cin >> str;

        if (binTree.isGListslegal(str))
        {
            cout << "YES..." << endl;
        }
        else
        {
            cout << "NO!!!" << endl;
        }
    }
    return 0;
}


3.3 由广义表生成二叉树

由广义表生成二叉树的伪代码如下:

/**************************************************
设置一个标记变量 k,初始为 -1;
    循环遍历存储广义表的字符串 str:
        如果 str[i] 是左括号:
            则设置 k 为 0;
            把 temp 压入栈中。
            继续循环;
            
        否则如果 str[i] 是逗号:
            则设置 k 为 1。
            继续循环。
            
        否则如果 str[i] 是右括号:
            则栈顶元素出栈。
            继续循环。
            
        否则如果 str[i] 是一个字母,用节点 temp 来存储:
            如果 k 为 -1:
                则把 temp 作为根节点。
            如果 k 为 0:
                然后将 temp 作为栈顶节点的左孩子;
            如果 k 为 1:
                将 temp 作为栈顶节点的右孩子;
**************************************************/



输出二叉树广义表形式的伪代码:

/**************************************************
输出节点存储的值;
    如果左孩子不为空:
        输出 "(";
        递归输出左子树;
        如果右孩子为空:
            输出 ")"。
            
    如果右孩子不为空:
        如果左孩子为空:
            输出 "("。
        输出 “,”;
        递归输出右子树;
        输出 ")"。
**************************************************/


相关代码如下:

#include <iostream>
#include <string>

using namespace std;


class BinaryGLists
{
private:
    struct BinTreeNode
    {
        char data;
        BinTreeNode* lchild;
        BinTreeNode* rchild;
    };

    BinTreeNode* root_node;
public:

    void createBinTreeByGList(string str)
    {
        BinTreeNode* current = NULL;

        /* 这里用数组来模拟栈 */
        BinTreeNode** stack = new BinTreeNode*[str.length()];
        if (stack == NULL)
        {
            root_node = NULL;
        }

        int top_index = -1;
        int flag = -1;
        unsigned int i = 0;

        while (i < str.length())
        {
            switch (str[i])
            {
                /* 碰到左括号则把括号前的一个元素入栈 */
                case '(':
                    stack[++top_index] = current;
                /* flag为0表示左括号后紧挨着的元素是栈顶元素的左子结点 */
                    flag = 0;
                    break;

                case ',':
                /* flag为1则表示逗号后紧挨的元素为栈顶元素的右子结点 */
                    flag = 1;
                    break;

                case ')':
                /* 碰到右括号表示栈顶元素的左右子结点已经处理完 */
                    top_index--;
                    break;

                default:
                    current = new BinTreeNode();
                    current->data = str[i];
                    current->lchild = NULL;
                    current->rchild = NULL;

                    /* flag为-1时,则把当前结点作为根结点 */
                    if (flag == -1)
                    {
                        root_node = current;
                    }
                    else if(flag == 0)
                    {
                        stack[top_index]->lchild = current;
                    }
                    else if (flag == 1)
                    {
                        stack[top_index]->rchild = current;
                    }
            }

            i++;
        }

        delete[] stack;
    }


    void output()
    {
        preOrderTraversingGList(root_node);
    }

    /* 前序遍历 */
    void preOrderTraversing(BinTreeNode* node)
    {
        if (node != NULL)
        {
            cout << node->data << endl;

            preOrderTraversing(node->lchild);
            preOrderTraversing(node->rchild);
        }
    }

    /* 以广义表的形式输出二叉树 */
    void preOrderTraversingGList(BinTreeNode* node)
    {
        if (node != NULL)
        {
            cout << node->data;

            if (node->lchild != NULL)
            {
                cout << "(";
                preOrderTraversingGList(node->lchild);

                if (node->rchild == NULL)
                {
                    cout << ")";
                }
            }

            if (node->rchild != NULL)
            {
                if (node->rchild == NULL)
                {
                    cout << "(";
                }

                cout << ",";
                preOrderTraversingGList(node->rchild);

                cout << ")";
            }
        }
    }

};


using namespace std;

int main()
{
    BinaryGLists binTree;
    string str = "A(B(C,D),E(F,G))";

    /* 不判断是否合法 */
    binTree.createBinTreeByGList(str);
    binTree.output();

    return 0;
}