树的基本概念和性质及其操作
结点:树的数据元素
***结点的度:有几个直接后继就是几度,亦称次数
结点的层次:
中断结点:从根到该结点的层数(根结点算第一层)
分支结点:度为0的结点,即叶子结点
***树的度:所有结点度中的最大值
***树的深度:所有结点中最大的层数
由于树中,每个根结点的后继结点不确定,对于结点的存储关系难以确定,导致直接研究树
是一个很难得问题,问题解决得办法在于,所有的树都可以转换成二叉树,二叉树在所有的树中是
最简单的,他的存储和恢复也相对比较简单,所以我们直接转为研究二叉树;
二叉树的基本性质
1:在二叉树的第i层上,最多有2exp(i-1)个结点;
2:深度为k的二叉树至多有2exp(k)-1个结点;(等比数列求和)
3:如果不是完全二叉树,则一律转为完全二叉树!方法很简单,将各层空缺统统补上虚结点,其内容为空。
树的遍历规则:
1.二叉树由根,左子树,右子树构成,定义为D,L,R
2.D,L,R的自由排列组合定义了六种可能的遍历方案(A(3,3)),LDR,LRD,RLD,RDL,DLR,DRL
3.如果限定先左后右,则有三种实现方案:DLR 先(根)序遍历,LDR中(根)序遍历,LRD后(根)序遍历;
注:“先,中,后”的意思是指访问的结点D是先于子树出现还是后于子树出现;
先序遍历: 根 ,左子树,右子树;
中序遍历:左子树,根,右子树;
后序遍历:左子树,右子树,根;
先序遍历不能确定一棵树,中序和后续可以确定树;
树的表示法:(1)二叉链法:包括一个数据域,一个左指针域,一个右指针域;然后把他们串起来;
(2)三叉链表示法:每个结点包含一个数据域,一个左指针域,一个右指针域,再加一个指向双亲结点的指针域;
由于树本身就是递归定义的,所以,对所有关于树的API函数,递归是一个很好的思想。我练习了树的三种遍历方式,求树的叶子结点的个数,树的深度,树的拷贝等几个API函数
#include<iostream>
using namespace std;
typedef struct BitNode
{
int data;
struct BitNode* lchild, * rchild;
}BitNode;
typedef struct BiNode* Bitree;
void preorder(BitNode* T)
{
if (T == NULL)
{
return;
}
cout << T->data<<" ";
if (T->lchild != NULL)
{
preorder(T->lchild);
}
if (T->rchild != NULL)
{
preorder(T->rchild);
}
return;
}
void midorder(BitNode* T)//传来的是树的根节点
{
if (T == NULL)
{
return;
}
if (T->lchild != NULL)//先遍历树的左子树
{
midorder(T->lchild);
}
cout << T->data << " ";
if (T->rchild != NULL)//再遍历树的根结点
{
midorder(T->rchild);
}
return;
}
void postorder(BitNode* T)//传来的是树的根节点
{
if (T == NULL)
{
return;
}
if (T->lchild != NULL)//先遍历树的左子树
{
postorder(T->lchild);
}
if (T->rchild != NULL)//再遍历树的右子树
{
postorder(T->rchild);
}
cout << T->data << " ";//最后遍历树的根节点
return;
}
/* 写法1:定义全局变量
int sum = 0;//全局变量用于统计叶子结点的个数
void CountLeafNum(BitNode* node)
{
if(node==NULL)
{
return ;
}
if (node->lchild==NULL && node->rchild==NULL)//求根节点的叶子结点的个数
{
sum++;
}
CountLeafNum(node->lchild);//求左子树的叶子结点的个数
CountLeafNum(node->rchild);//求右子树的叶子结点的个数
}*/
// 写法2:指针做函数参数
void CountLeafNum2(BitNode* node,int* sum)
{
if (node == NULL)
{
return;
}
if (node->lchild == NULL && node->rchild == NULL)//求根节点的叶子结点的个数
{
*sum = *sum + 1;
//或者这么写
//(*sum)++;但是不能这么写:*sum++ 因为++的优先级高,指针指向了别的地方
}//写在这里相当于先序遍历
CountLeafNum2(node->lchild,sum);//求左子树的叶子结点的个数
/*if (node->lchild == NULL && node->rchild == NULL)//求根节点的叶子结点的个数
{
*sum = *sum + 1;
//或者这么写
//(*sum)++;但是不能这么写:*sum++ 因为++的优先级高,指针指向了别的地方
}写在这里相当于中序遍历*/
CountLeafNum2(node->rchild,sum);//求右子树的叶子结点的个数
/*if (node->lchild == NULL && node->rchild == NULL)//求根节点的叶子结点的个数
{
*sum = *sum + 1;
//或者这么写
//(*sum)++;但是不能这么写:*sum++ 因为++的优先级高,指针指向了别的地方
}写在这里相当于后序遍历*/
}
//求树的高度
int depth(BitNode* node)
{
int depthvalue = 0;
int depthLeftValue = 0, depthRightValue = 0;
if (node == NULL)
{
return 0;//递归结束的条件
}
depthLeftValue = depth(node->lchild);
depthRightValue = depth(node->rchild);
depthvalue = 1 + ((depthLeftValue > depthRightValue) ? depthLeftValue : depthRightValue);
return depthvalue;
}
BitNode* copy(BitNode* T)
{
//拷贝二叉树的思想是,首先拷贝结点的数据然后拷贝结点的关系
BitNode* newLptr = NULL;
BitNode* newRptr = NULL;
BitNode* node = NULL;
if (T == NULL)
{
return NULL;
}
newLptr = copy(T->lchild);//在递归内部拷贝左子树的根结点和结点之间的关系
newRptr = copy(T->rchild);//同上,拷贝右子树
node = (BitNode*)malloc(sizeof(BitNode));
if (node == NULL)
{
return NULL;
}
node->data = T->data;//根结点的拷贝
node->lchild = newLptr;//根结点左子树的拷贝
node->rchild = newRptr;//根结点右子树的拷贝
return node;
}
void main()
{
BitNode nodeA, nodeB, nodeC, nodeD, nodeE;
memset(&(nodeA), 0, sizeof(BitNode));
memset(&(nodeB), 0, sizeof(BitNode));
memset(&(nodeC), 0, sizeof(BitNode));
memset(&(nodeD), 0, sizeof(BitNode));
memset(&(nodeE), 0, sizeof(BitNode));
nodeA.data = 1;
nodeB.data = 2;
nodeC.data = 3;
nodeD.data = 4;
nodeE.data = 5;
nodeA.lchild = &nodeB;
nodeA.rchild = &nodeC;
nodeB.lchild = &nodeD;
nodeC.lchild = &nodeE;
BitNode *newnode=NULL;
newnode = copy(&nodeA);
cout << "先序遍历拷贝树: ";
preorder(newnode);
int mysum = 0;
CountLeafNum2(&nodeA,&mysum);
cout << "叶子结点的个数: " << mysum<<endl;
cout << "树的深度: " << depth(&nodeA) << endl;
cout << "先序遍历树: ";
preorder(&nodeA);
cout << "\n中序遍历树: ";
midorder(&nodeA);
cout << "\n后序遍历树: ";
postorder(&nodeA);
return;
}
运行结果: