形式化定义:
树:T={K,R}。K是包含n个结点的有穷集合(n>0),关系R满足以下条件:
(1) 有且仅有一个结点k0∈K,它对于关系R来说没有前驱结点,结点k0称作树的根。
(2) 除结点k0外,K中的每个结点对于关系R来说都有且仅有一个前驱结点。
(3) K中每个结点对于关系R来说可以有多个后继结点。
树的表示
(1) 树形表示法。这是树的最基本的表示,使用一棵倒置的树表示树结构,非常直观和形象。
(2) 文氏图表示法。使用集合以及集合的包含关系描述树结构。
(3) 凹入表示法。使用线段的伸缩描述树结构。
(4) 括号表示法。将树的根结点写在括号的左边,除根结点之外的其余结点写在括号中并用逗号间隔来描述树结构。
树的基本术语
1. 结点的度与树的度:树中某个结点的子树的个数称为该结点的度。树中各结点的度的最大值称为树的度,通常将度为m的树称为m次树。
2. 分支结点与叶结点:度不为零的结点称为非终端结点,又叫分支结点。度为零的结点称为终端结点或叶结点。在分支结点中,每个结点的分支数就是该结点的度。如对于度为1的结点,其分支数为1,被称为单分支结点;对于度为2的结点,其分支数为2,被称为双分支结点,其余类推。
3. 路径与路径长度:对于任意两个结点ki和kj,若树中存在一个结点序列ki,ki1,ki2,…,kin,kj,使得序列中除ki外的一结点都是其在序列中的前一个结点的后继,则称该结点序列为由ki到kj的一条路径,用路径所通过的结点序列(ki,ki1,ki2,…,kj)表示这条路径。路径的长度等于路径所通过的结点数目减1(即路径上分支数目)。可见,路径就是从ki出发“自上而下”到达kj所通过的树中结点序列。显然,从树的根结点到树中其余结点均存在一条路径。
4. 孩子结点、双亲结点和兄弟结点:在一棵树中,每个结点的后继,被称作该结点的孩子结点(或子女结点)。相应地,该结点被称作孩子结点的双亲结点(或父母结点)。具有同一双亲的孩子结点互为兄弟结点。进一步推广这些关系,可以把每个结点的所有子树中的结点称为该结点的子孙结点,从树根结点到达该结点的路径上经过的所有结点被称作该结点的祖先结点。
5. 结点的层次和树的高度:树中的每个结点都处在一定的层次上。结点的层次从树根开始定义,根结点为第1层,它的孩子结点为第2层,以此类推,一个结点所在的层次为其双亲结点所在的层次加1。树中结点的最大层次称为树的高度(或树的深度)。
6. 有序树和无序树:若树中各结点的子树是按照一定的次序从左向右安排的,且相对次序是不能随意变换的,则称为有序树,否则称为无序树。
二叉树概念
二叉树也称为二次树或二分树,它是有限的结点集合,这个集合或者是空,或者由一个根结点和两棵互不相交的称为左子树和右子树的二叉树组成。
二叉树的定义是一种递归定义。
二叉树有五种基本形态,如下图所示,任何复杂的二叉树都是这五种基本形态的复合。
二叉树存储结构
二叉树的顺序存储结构
二叉树的顺序存储结构中结点的存放次序是:对该树中每个结点进行编号,其编号从小到大的顺序就是结点存放在连续存储单元的先后次序。若把二叉树存储到一维数组中,则该编号就是下标值加1(注意,C/C++语言中数组的起始下标为0)。树中各结点的编号与等高度的完全二叉树中对应位置上结点的编号相同。
二叉树的链式存储结构
在二叉树的链接存储中,结点的结构如下:
typedef struct node
{ ElemType data;
struct node *lchild,*rchild;
} BTNode;
其中,data表示值域,用于存储对应的数据元素,lchild和rchild分别表示左指针域和右指针域,用于分别存储左孩子结点和右孩子结点(即左、右子树的根结点)的存储位置。
二叉树遍历的概念
二叉树的遍历是指按照一定次序访问树中所有结点,并且每个结点仅被访问一次的过程。它是最基本的运算,是二叉树中所有其他运算的基础。
1. 先序遍历
先序遍历二叉树的过程是:
(1) 访问根结点;
(2) 先序遍历左子树;
(3) 先序遍历右子树。
2. 中序遍历
中序遍历二叉树的过程是:
(1) 中序遍历左子树;
(2) 访问根结点;
(3) 中序遍历右子树。
3. 后序遍历
后序遍历二叉树的过程是:
(1) 后序遍历左子树;
(2) 后序遍历右子树;
(3) 访问根结点。
先序遍历:
void PreOrder(BTNode *b) /*先序遍历的递归算法*/
{
if (b!=NULL)
{ printf("%c ",b->data); /*访问根结点*/
PreOrder(b->lchild);
PreOrder(b->rchild);
}
}
中序遍历:void InOrder(BTNode *b) /*中序遍历的递归算法*/
{
if (b!=NULL)
{ InOrder(b->lchild);
printf("%c ",b->data); /*访问根结点*/
InOrder(b->rchild);
}
}
后序遍历:
void PostOrder(BTNode *b) /*后序遍历递归算法*/
{
if (b!=NULL)
{ PostOrder(b->lchild);
PostOrder(b->rchild);
printf("%c ",b->data); /*访问根结点*/
}
}
二叉树编程示例:
#include "stdafx.h"
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#define MaxSize 100
typedef char ElemType;
typedef struct node
{
ElemType data;//数据元素
struct node *lchild;//指向左子树
struct node *rchild;//指向右子树
}BTNode;
//由str串创建二叉树
void CreateBTNode(BTNode *&b, char *str)
{
BTNode *St[MaxSize];
BTNode *p = NULL;
int top = -1;//栈为空
int k, j =0;
char ch;
b = NULL;//建立的二叉树初始时为空
ch = str[j];
while (ch != '\0')
{//str未扫描完时循环
switch (ch)
{
case '(':top++;//遇到‘(’进栈
St[top] = p;
k = 1;//为左节点标记
break;
case ')':top--;//遇到‘)’出栈
break;
case ',':k = 2;//为右节点标记
break;
default: p = (BTNode*)malloc(sizeof(BTNode));
if (NULL == p)
{
exit(0);//分配错误则退出
}
p->data = ch;
p->lchild = NULL;
p->rchild = NULL;
if (NULL == b)
{//b指向二叉树的根
b = p;
}else
{//已建立二叉树根节点
switch (k)
{
case 1:St[top]->lchild = p;break;
case 2:St[top]->rchild = p;break;
}
}
}
j++;//读取下一个字符
ch = str[j];
}
}
//采用先序遍历递归算法查找值为x的结点。找到后返回其指针,否则返回NULL
BTNode *FindNode(BTNode *b, ElemType x)
{
BTNode *p;
if (NULL == b)
{
return NULL;
}else if (b->data == x)
{
return b;
}else
{
p = FindNode(b->lchild, x);
if (p != NULL)
{
return p;
}else
{
return FindNode(b->rchild, x);
}
}
}
// 直接返回*p结点的左孩子结点的指针
BTNode *LchildNode(BTNode *p)
{
return p->lchild;
}
//直接返回*p节点的右孩子结点的指针
BTNode *RchildNode(BTNode *p)
{
return p->rchild;
}
//求二叉树的深度
int BTNodeDepth(BTNode *b)
{
int lchilddep, rchilddep;
if (NULL == b)
{
return 0;//空树高度为0
}else
{
lchilddep = BTNodeDepth(b->lchild);//左子树的高度
rchilddep = BTNodeDepth(b->rchild);//右子树的高度
return (lchilddep > rchilddep) ? (lchilddep + 1) : (rchilddep +1);
}
}
//以括号表示法输出二叉树
void DispBTNode(BTNode *b)
{
if (b != NULL)
{
printf("%c", b->data);
if (b->lchild != NULL)
{
printf("(");
DispBTNode(b->lchild);
if (b->rchild != NULL)
{
printf(",");
}
DispBTNode(b->rchild);
printf(")");
}
}
}
//求二叉树的宽度
int BTWidth(BTNode *b)
{
struct
{
int lno;//节点的层次编号
BTNode *p;//节点指针
}Qu[MaxSize];//定义非循环队列
int front, rear;//定义队首和队尾
int lnum, max, i, n;
front = rear = 0;//队列为空
if (b != NULL)
{
rear++;
Qu[rear].p = b;//根节点指针入队
Qu[rear].lno = 1;//根节点的层次编号为1
while (rear != front)
{//队列不为空
front++;//对头出队
b = Qu[front].p;
lnum = Qu[front].lno;
if (b->lchild != NULL)
{//左孩子入队
rear++;
Qu[rear].p = b->lchild;
Qu[rear].lno = lnum + 1;
}
if (b->rchild != NULL)
{//右孩子入队
rear++;
Qu[rear].p = b->rchild;
Qu[rear].lno = lnum + 1;
}
}
max = 0;
lnum = 1;
i = 1;
while (i <= rear)
{
n = 0;
while (i <= rear && Qu[i].lno == lnum)
{
n++;
i++;
}
lnum = Qu[i].lno;
if (n > max)
{
max = n;
}
}
return max;
}else
{
return 0;
}
}
//求二叉树的节点个数
int Nodes(BTNode *b)
{
int num1, num2;
if (NULL == b)
{
return 0;
}else if (b->lchild == NULL && b->rchild == NULL)
{
return 1;
}else
{
num1 = Nodes(b->lchild);
num2 = Nodes(b->rchild);
return (num1 + num2 + 1);
}
}
//求二叉树的叶子节点个数
int LeafNodes(BTNode *b)
{
int num1, num2;
if (NULL == b)
{
return 0;
}else if (b->lchild == NULL && b->rchild == NULL)
{
return 1;
}else
{
num1 = LeafNodes(b->lchild);
num2 = LeafNodes(b->rchild);
return (num1 + num2);
}
}
//销毁二叉树
void DestroyBTNode(BTNode *&b)
{
if (b != NULL)
{
DestroyBTNode(b->lchild);
DestroyBTNode(b->rchild);
free(b);
}
}
int main()
{ BTNode *b,*p,*lp,*rp;;
CreateBTNode(b,"A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,I)))");
printf("二叉树的基本运算如下:\n");
printf(" (1)输出二叉树:");DispBTNode(b);printf("\n");
printf(" (2)H节点:");
p=FindNode(b,'H');
if (p!=NULL)
{ lp=LchildNode(p);
if (lp!=NULL)
printf("左孩子为%c ",lp->data);
else
printf("无左孩子 ");
rp=RchildNode(p);
if (rp!=NULL)
printf("右孩子为%c",rp->data);
else
printf("无右孩子 ");
}
printf("\n");
printf(" (3)二叉树b的深度:%d\n",BTNodeDepth(b));
printf(" (4)二叉树b的宽度:%d\n",BTWidth(b));
printf(" (5)二叉树b的节点个数:%d\n",Nodes(b));
printf(" (6)二叉树b的叶子节点个数:%d\n",LeafNodes(b));
printf(" (7)释放二叉树b\n");
DestroyBTNode(b);
return 0;
}