1 树的基本概念
(1)树是由若干结点组成的具有层次关系的集合,非空树有且只有一个根结点(/)。
(2)某个结点及其下面所有的结点并称为以该结点为根的子树(usr及其下的所有结点就是/的一颗子树,usr是该子树的根)。
(3)结点拥有的子树的个数称为结点的度(/的度为7,home的度为3)。
(4)度为0的节点称为叶子结点(lib就是叶子结点)。
(5)树的深度就是结点的最大层数(上图中的树深度为4)。
注意:
(1)在一颗非空树上,一定会有根结点和叶子结点。当只存在一个结点时,它既是根结点也是叶子结点。
(2)在树上,父结点可以有多个孩子结点,但是除根结点外的孩子结点仅有一个父结点。
2 二叉树的基本概念
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层的结点全部集中在左边,那么该二叉树被称为完全二叉树。
注意:这里的全部集中在左边的意思是第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。
上图中的二叉树用广义表的形式表示为: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; }