二叉树(binary tree)是n(n>=0)个结点的有限结合,该集合或者为空集(
空二叉树),或由一个根结点和两棵互不相交的,分别称为根结点的左子树(
left subtree)和右子树(right subtree)的二叉树组成。
二叉树的特点:
1.每个结点最多有两棵字树,所以二叉树中不存在度大于2的结点
2.二叉树是有序的,其次序不能任意颠倒,即使树中的某个结点只有一棵
字树,也要区分它是左子树还是右子树。
下面介绍几种特殊的二叉树:
1.斜树
所有结点都只有左子树的二叉树称为左斜树;所有结点都只有右子树的二叉树称为右斜树。是两棵不同的二叉树。如下示意图。
2.满二叉树
在一棵二叉树中,如果所有分支结点都存在左子树和右字树,并且所有叶子结点都在同一层上,这样的二叉树称为满二叉树。如下示意图。
3.完全二叉树
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树。显然一棵满二叉树必定是一棵完全二叉树,完全二叉树的特点是:
1.如果有度为1的结点,只可能有一个,且该结点只有左孩子。
2.叶子结点只能出现在最下面两层,且最下层的叶子结点都集中在二叉树左侧连续的位置。
以上图画的不是太优雅,大家不要介意哈。
关于二叉树的更多的性质,请读者查看维基百科( 点击打开链接)或者其他资料。
在 编程语言 中能用多种方法来构造二叉树。常用的是顺序存储结构和二叉链表存储结构。
顺序存储结构:
二 叉树可以用 数组 或线性表来存储,而且如果这是 满二叉树 ,这种方法不会浪费空间。用这种紧凑排列,如果一个结点的索引为i ,它的子结点能在索引2i +1和2i +2找到,并且它的父节点(如果有)能在索引 floor((i-1)/2) 找到(假设根节点的索引为0)。这种方法更有利于紧凑存储和更好的 访问的局部性 ,特别是在前序遍历中。然而,它需要连续的 存储空间 ,这样在存储高度为 h 的 n 个结点组成的一般普通树时将会浪费很多空间。一种最极坏的情况下如果深度为h的二叉树每个节点只有右孩子需要占用2的h次幂减1,而实际却只有h个结点,空间的浪费太大,这是顺序存储结构的一大缺点。
对于顺序存储结构,本文不作介绍,这种存储结构的缺点导致物理空间的浪费,是我们难以接受的。下面重点介绍二叉链表存储结构。
二叉链表存储结构:
在使用记录或内存地址指针的编程语言中,二叉树通常用树结点结构来存储。有时也包含指向唯一的父节点的指针。如果一个结点的子结点个数小于2,一些子结点指针可能为空值,或者为特殊的哨兵结点。 使用链表能避免顺序储存浪费空间的问题,算法和结构相对简单,但使用二叉链表,由于缺乏父链的指引,在找回父节点时需要重新扫描树得知父节点的节点地址。
对于二叉链表最重要的操作莫过于遍历了,概括来说有递归和非递归两种,本文介绍的是递归遍历二叉树。
一定要注意创建的二叉树时的方法,上面也是通过递归的方法创建二叉树,根据前中后序遍历,很容易画出这棵二叉树:(如下字母即指结点又指结点数据域)
下面详细的介绍是如何进行递归遍历的,以上面构建的二叉树为例:
前序遍历:
调用PostOrderBiTree(BiTree &T),T根结点不为空,打印字符A
再调用PreOrderBiTree(T->leftChild),访问根结点的左孩子,不为NULL,打印字符B
再次调用PreOrderBiTree(T->leftChild),访问B结点的左孩子,不为空,打印C
再次调用PreOrderBiTree(T->leftChild),访问结点C的左孩子,因为C没有左孩子,T=NULL,返回函数。调用PreOrderBiTree(T->rightChild),C的右孩子也为空,此时返回到 上一级递归的函数,也就是打印B结点的函数;此时调用PreOrderBiTree(T->rightChild),B没有右孩子,为空,此时返回到上一级递归的函数,就是打印A结点的函数;此时调用PreOrderBiTree(T->rightChild),打印了D;调用PreOrderBiTree(T->leftChild),因为D没有左孩子,T=NULL,返回函数,调用了PreOrderBiTree(T->rightChild),D也没有右孩子,T=NULL,返回函数,到此前序遍历了整个二叉树。
中序遍历:
调用PostOrderBiTree(BiTree &T),T根结点不为空
调用PreOrderBiTree(T->leftChild),访问根结点的左孩子B,不为NULL
调用PreOrderBiTree(T->leftChild),访问B结点的左孩子C,不为NULL
调用PreOrderBiTree(T->leftChild),访问C结点的左孩子,由于C结点没有左孩子,所以T=NULL,返回函数;打印字符C,又调用PreOrderBiTree(T->rightChild),因为C没有右PreOrderBiTree(T->rightChild)孩子,所以返回到上级递归函数,打印字符B;调用PreOrderBiTree(T->rightChild),由于B没有右孩子,返回到上级递归函数;打印字符A,调用了PreOrderBiTree(T->rightChild),访问了根结点的右孩子D,不为空;然后调用了PreOrderBiTree(T->leftChild),因为D没有左孩子,返回函数;打印字符D,又调用PreOrderBiTree(T->rightChild),因为D没有右孩子,返回函数,到此中序遍历了整个二叉树。
后序遍历操作读者感兴趣可以自己完成,这里不作赘述,从上面的分析,整个过程不是太复杂,关键要理解递归的思想。
代码中也简单实现了打印二叉树的叶子结点个数,二叉树的高度,以及销毁一棵二叉树。如果明白了二叉树的创建和遍历,那么树的其他操作对于我们也不是难事。
二叉树的特点:
1.每个结点最多有两棵字树,所以二叉树中不存在度大于2的结点
2.二叉树是有序的,其次序不能任意颠倒,即使树中的某个结点只有一棵
字树,也要区分它是左子树还是右子树。
下面介绍几种特殊的二叉树:
1.斜树
所有结点都只有左子树的二叉树称为左斜树;所有结点都只有右子树的二叉树称为右斜树。是两棵不同的二叉树。如下示意图。
![](http://blog.itpub.net/attachment/201511/19/29876893_1447947188rLev.png)
在一棵二叉树中,如果所有分支结点都存在左子树和右字树,并且所有叶子结点都在同一层上,这样的二叉树称为满二叉树。如下示意图。
![](http://blog.itpub.net/attachment/201511/19/29876893_1447947507IgPv.png)
对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中的位置完全相同,则这棵二叉树称为完全二叉树。显然一棵满二叉树必定是一棵完全二叉树,完全二叉树的特点是:
1.如果有度为1的结点,只可能有一个,且该结点只有左孩子。
2.叶子结点只能出现在最下面两层,且最下层的叶子结点都集中在二叉树左侧连续的位置。
![](http://blog.itpub.net/attachment/201511/19/29876893_1447947841ZUv2.png)
关于二叉树的更多的性质,请读者查看维基百科( 点击打开链接)或者其他资料。
在 编程语言 中能用多种方法来构造二叉树。常用的是顺序存储结构和二叉链表存储结构。
顺序存储结构:
二 叉树可以用 数组 或线性表来存储,而且如果这是 满二叉树 ,这种方法不会浪费空间。用这种紧凑排列,如果一个结点的索引为i ,它的子结点能在索引2i +1和2i +2找到,并且它的父节点(如果有)能在索引 floor((i-1)/2) 找到(假设根节点的索引为0)。这种方法更有利于紧凑存储和更好的 访问的局部性 ,特别是在前序遍历中。然而,它需要连续的 存储空间 ,这样在存储高度为 h 的 n 个结点组成的一般普通树时将会浪费很多空间。一种最极坏的情况下如果深度为h的二叉树每个节点只有右孩子需要占用2的h次幂减1,而实际却只有h个结点,空间的浪费太大,这是顺序存储结构的一大缺点。
![](http://blog.itpub.net/attachment/201511/19/29876893_14479484550Ofm.png)
二叉链表存储结构:
在使用记录或内存地址指针的编程语言中,二叉树通常用树结点结构来存储。有时也包含指向唯一的父节点的指针。如果一个结点的子结点个数小于2,一些子结点指针可能为空值,或者为特殊的哨兵结点。 使用链表能避免顺序储存浪费空间的问题,算法和结构相对简单,但使用二叉链表,由于缺乏父链的指引,在找回父节点时需要重新扫描树得知父节点的节点地址。
对于二叉链表最重要的操作莫过于遍历了,概括来说有递归和非递归两种,本文介绍的是递归遍历二叉树。
点击(此处)折叠或打开
- #include<iostream>
- using namespace std;
- typedef struct node
- {
- struct node *leftChild;
- struct node *rightChild;
- char data;
- }BiTreeNode, *BiTree;
-
- void createBiTree(BiTree &);
- void PreOrderBiTree(BiTree *);
- void InOrderBiTree(BiTree *);
- void PostOrderBiTree(BiTree *);
- int BiTreeDeep(BiTree *);
- int LeafTreecount(BiTree *);
-
- void createBiTree(BiTree &T)
- {
- char c;
- cin >> c;
- if ('#' == c)
- T = NULL;
- else
- {
- T = new BiTreeNode;
- T->data = c;
- createBiTree(T->leftChild);
- createBiTree(T->rightChild);
- }
- }
-
- void PreOrderBiTree(BiTree &T)
- {
- if (T == NULL)
- return;
- cout << T->data << " ";
- PreOrderBiTree(T->leftChild);
- PreOrderBiTree(T->rightChild);
-
- }
- void InOrderBiTree(BiTree &T) {
-
- if (T == NULL)
- return;
- InOrderBiTree(T->leftChild);
- cout << T->data << " ";
- InOrderBiTree(T->rightChild);
-
- }
- void PostOrderBiTree(BiTree &T) {
- if (T == NULL)
- return;
- PostOrderBiTree(T->leftChild);
- PostOrderBiTree(T->rightChild);
- cout << T->data<<" ";
- }
-
- int BiTreeDeep(BiTree T)
- {
- int deep = 0;
- if (T)
- {
- int leftdeep = BiTreeDeep(T->leftChild);
- int rightdeep = BiTreeDeep(T->rightChild);
- deep = leftdeep >= rightdeep ? leftdeep + 1 : rightdeep + 1;
- }
- return deep;
- }
-
- //求二叉树叶子结点个数
-
- int LeafTreecount(BiTree T,int &num)
- {
- if (T)
- {
- if (T->leftChild == NULL &&T->rightChild == NULL)
- num++;
- LeafTreecount(T->leftChild,num);
- LeafTreecount(T->rightChild,num);
-
- }
- return num;
- }
- void DeleteBiTree(BiTree T) {
- if (T == NULL)
- return;
- DeleteBiTree(T->leftChild);
- DeleteBiTree(T->rightChild);
- delete T;
-
- }
- int main()
- {
- BiTree T;
- cout << "输入的字符是:" << endl;
- createBiTree(T);
- cout << "前序遍历:" << endl;
- PreOrderBiTree(T); \
- cout << endl;
- cout <<"中序遍历:"<< endl;
- InOrderBiTree(T);
- cout << endl;
- cout << "后序遍历:" << endl; \
- PostOrderBiTree(T);
- cout << endl;
- cout << "二叉树的高度为:" << BiTreeDeep(T) << endl;
- int num = 0;
- cout << "二叉树的叶子结点个数为:" << LeafTreecount(T, num)<< endl;
- DeleteBiTree(T);
- cout << "树已销毁!!!" << endl;
- return 0;
- }
运行结果如下:
![](http://blog.itpub.net/attachment/201511/20/29876893_1447951582PHRp.png)
![](http://blog.itpub.net/attachment/201511/20/29876893_1447951846Io18.png)
前序遍历:
调用PostOrderBiTree(BiTree &T),T根结点不为空,打印字符A
再调用PreOrderBiTree(T->leftChild),访问根结点的左孩子,不为NULL,打印字符B
再次调用PreOrderBiTree(T->leftChild),访问B结点的左孩子,不为空,打印C
再次调用PreOrderBiTree(T->leftChild),访问结点C的左孩子,因为C没有左孩子,T=NULL,返回函数。调用PreOrderBiTree(T->rightChild),C的右孩子也为空,此时返回到 上一级递归的函数,也就是打印B结点的函数;此时调用PreOrderBiTree(T->rightChild),B没有右孩子,为空,此时返回到上一级递归的函数,就是打印A结点的函数;此时调用PreOrderBiTree(T->rightChild),打印了D;调用PreOrderBiTree(T->leftChild),因为D没有左孩子,T=NULL,返回函数,调用了PreOrderBiTree(T->rightChild),D也没有右孩子,T=NULL,返回函数,到此前序遍历了整个二叉树。
中序遍历:
调用PostOrderBiTree(BiTree &T),T根结点不为空
调用PreOrderBiTree(T->leftChild),访问根结点的左孩子B,不为NULL
调用PreOrderBiTree(T->leftChild),访问B结点的左孩子C,不为NULL
调用PreOrderBiTree(T->leftChild),访问C结点的左孩子,由于C结点没有左孩子,所以T=NULL,返回函数;打印字符C,又调用PreOrderBiTree(T->rightChild),因为C没有右PreOrderBiTree(T->rightChild)孩子,所以返回到上级递归函数,打印字符B;调用PreOrderBiTree(T->rightChild),由于B没有右孩子,返回到上级递归函数;打印字符A,调用了PreOrderBiTree(T->rightChild),访问了根结点的右孩子D,不为空;然后调用了PreOrderBiTree(T->leftChild),因为D没有左孩子,返回函数;打印字符D,又调用PreOrderBiTree(T->rightChild),因为D没有右孩子,返回函数,到此中序遍历了整个二叉树。
后序遍历操作读者感兴趣可以自己完成,这里不作赘述,从上面的分析,整个过程不是太复杂,关键要理解递归的思想。
代码中也简单实现了打印二叉树的叶子结点个数,二叉树的高度,以及销毁一棵二叉树。如果明白了二叉树的创建和遍历,那么树的其他操作对于我们也不是难事。