算法导论学习笔记(七):二叉树

来源:互联网 发布:淘宝怎么搜vr资源 编辑:程序博客网 时间:2024/06/03 17:21

前言

昨天看算法导论看到二叉查找树,虽然以前学数据结构的时候已经学过了二叉树,但感觉自己很多东西已经忘了,为

更好的学习二叉查找树以及后面所涉及到的二叉树方面的相关算法,故再一次复习了下二叉树。并在这写个复习笔

记,便于以后的复习。


定义

学习二叉树之前,肯定得了解数据结构中的树。数据结构中的树和我们现实中的树还是有一点区别的。首先数据结构

的树与现实相比是倒立的。其次数据结构中的树只能有一个根,而现实中的树是有很多根的。关于树结构的一些专

业术语,如树的深度,左子树,右子树等书上已经讲的很详细了,这里不多做累赘。

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别

为根结点的左子树和右子树的二叉树组成。


特点

1、每个结点最多有两颗子树

2、左子树和右子树是有顺序的,次序不能任意颠倒

3、即使树中某结点只有一颗子树,也要区分它是左子树还是右子树


几类特殊的二叉树

1、斜树:所有的结点都只有左子树的二叉树叫左斜树。所有的结点都只有右子树的二叉树叫右斜树。这两者统称为   

                斜树。其结构如下图所示:



2、满二叉树:在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二  

                       叉树称为满二叉树。其结构如下图所示:



3、完全二叉树:对一颗具有n个结点的二叉树按层序编号,如果编号为 i(0<i<=n)的结点与同样深度的满二叉树中编

                          号为 i 的结点在二叉树中位置完全相同,则这颗二叉树称为完全二叉树。其结构如下图所示:




二叉树基本操作的链式存储结构的实现

二叉树的基本操作主要包括:初始化、建树、销毁、判断是否为空树、遍历等。

对于链式存储结构实现二叉树,首先我们要自己建好这么一个结构,即结点的类型。这个结构包括两个部分:数

据域和指针域。为了便于理解,我这里数据域直接用的char类型。指针域有两个,分别指向左子树和右子树。该结构

义如下:

typedef struct BitNode{char data;                       //数据域struct BitNode *lchid, *rchild;  //指针域}BiTNode, *BiTree;
接下来是基本操作的实现:

/*初始化二叉树*/void InitBiTree (BiTree* T){*T = NULL;}/*前序输入创建二叉树*/void CreateBiTree (BiTree* T){char ch;cin >> ch;if (ch == '#')*T = NULL;else{*T = new BiTNode;if (!*T)exit (OVERFLOW);(*T)->data = ch;CreateBiTree (&(*T)->lchid);CreateBiTree (&(*T)->rchild);}}/*销毁二叉树 */void DestroyBiTree (BiTree* T){if (*T){if ((*T)->lchid)DestroyBiTree (&(*T)->lchid);if ((*T)->rchild)DestroyBiTree (&(*T)->rchild);free (*T);*T = NULL;}}/************************************ ** 判断二叉树是否为空** 若为空,返回TRUE,否则返回FALSE ************************************/bool BiTreeEmpty (BiTree T){if (T)return false;elsereturn true;}/*返回二叉树的深度*/int BiTreeDepth (BiTree T){int i, j;if (!T)return 0;if (T->lchid)i = BiTreeDepth (T->lchid);elsei = 0;if (T->rchild)j = BiTreeDepth (T->rchild);elsej = 0;return i > j ? i+1 : j+1;}/*前序遍历二叉树*/void PreOrderTraverse (BiTree T){if (T == NULL)return ;cout << T->data;PreOrderTraverse (T->lchid);PreOrderTraverse (T->rchild);}/*中序遍历二叉树*/void InOrderTraverse (BiTree T){if (T == NULL)return ;InOrderTraverse (T->lchid);cout << T->data;InOrderTraverse (T->rchild);}/*后序遍历二叉树*/void PostOrderTraverse (BiTree T){if (T == NULL)return ;PostOrderTraverse (T->lchid);PostOrderTraverse (T->rchild);cout << T->data;}

思考

从上面那些基本操作的代码我们可以发现对于二叉树的初始化、建立以及销毁传递的都是二级指针。那么传递一级指

针是否可以呢?于是便重新调整代码,把二级指针的地方都改为一级指针,运行后发现,当我们调用CreateBiTree

立二叉树后,我们传递的指针仍指向NULL。这是为什么呢?

我们可以猜测问题一定是出在指针身上。那么我们回过头来看CreateBiTree(BiTree* T)函数,是不是很快联想到我们

写过swap函数呢,当我们直接按值传递时,并不能真正交换实参。当我们传递指针时就可以真正交换实参了。根据

我们的记忆,函数中传递指针,在函数中改变指针的值,就是在改变实参中的数据信息。其实这里我们是有些东西没

讲细,我们这里改变指针的值实际是指改变指针指向地址的值,因为传递指针就是把指针指向变量的地址传递过来,

而不是像值传递一样只是传进来一个实参副本。所以当我们改变指针的值时,实参也改变了。

我们仔细看函数CreateBiTree(BiTree* T)可以发现,在该函数中改变了指针的指向,也就是改变了指针自身的值。这

样一来就有点像按值传递了,只不过这里的"值"是一个指针,所以我们要想指针本身的改变可以反映到实参指针上,

必须使用二级指针。下面通过看一个例子来理解。

#include<iostream>#include<cstring>using namespace std;void fun1(char* p){p = new char[5];strcpy (p, "test");}void fun2(char** p){*p = new char[5];strcpy (*p, "test");}int main(){char* ph = NULL;cout << "call function fun1" << endl;fun1 (ph);if (!ph)cout << "ph is null!" << endl;elsecout << ph << endl;cout << "call function fun2" << endl;fun2 (&ph);if (!ph)cout << "ph is null!" << endl;elsecout << ph << endl;system ("pause");return 0;}


分析:在fun1中,当调用p = new char[5]时,p和ph已经没什么关系了,所以p仍指针NULL。当调用fun2时,因为是

二级指针,p指向ph,这里*p = new char[5],*p就是ph,所以给*p分配空间就是给ph分配空间。这个大家可以自己体

会下。

理解上面的之后,那么对于为什么对于某些二叉树基本操作需要传递二级指针也就很容易理解了。