数据结构 树与二叉树
文章目录
一、树的概念及结构
1.1树的概念
树是一种非线性的数据结构,它是由n个有限节点组成的一个具有层次关系的集合。它的根朝上,叶朝下。
- 有一个特殊的结点,称为根结点,根节点没有前驱结点(父节点)。
- 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集
合Ti(1<= i <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。因此,树是递归定义的。
- 节点:数据元素及指向子树的分支。
- 节点的度:一个节点含有的子树的个数称为该节点的度。如上图:A的为6。
- 叶节点或终端节点:度为0的节点称为叶节点。如上图:B、C、H、I…等节点为叶节点。
- 树的度:一棵树中,最大的节点的度称为树的度。如上图:树的度为6。
- 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
- 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4。
- 有序树:树中节点各个子树从左至右有次序。相反的,无序树。
- 森林:由m(m>0)棵互不相交的多颗树的集合称为森林;(数据结构中的学习并查集本质就是一个森林)。把根节点删除,树就变成了森林。一棵树可以看成一个特殊的森林。给森林中各个子树加上一个双亲节点,森林就变成了树。故树一定是森林,森林不一定是树。
1.2树的表示
- 左孩子右兄弟表示法(了解即可)
typedef int DataType;
struct Node
{
struct Node* _firstChild1; // 第一个孩子结点
struct Node* _pNextBrother; // 指向其下一个兄弟结点
DataType _data; // 结点中的数据域
};
-
双亲表示法
数组中存双亲的下标
-
树的其他表示方法
1.嵌套集合(有层次)
2.广义表。
1.3树在实际中的应用
- 一个文件的路径
1.4二叉树
特点:
1.每个节点最多度为2.
2.子树有左右之分,不能颠倒次序。
3.二叉树可以是空集合,根可以有空的左子树或者右子树。
性质:
1.在二叉树的第i层最多有2^(i-1)个节点。第i层至少有一个节点。
2.深度为k的二叉树至多有(2^k)-1个节点。最少得有k个节点。
3.对于任何一棵二叉树T,如果叶子数为n0,度为2的节点数为n2,那么n0=n2+1。
1.5特殊二叉树
- 满二叉树
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
1.在满二叉树中:
总节点个数:N=2^h-1(h表示二叉树的深度)
h=log2 N
2.满二叉树在同样深度的二叉树中节点个数最多,叶子节点也最多。 - 完全二叉树
当且仅当其每一个结点都与深度为K的满二叉树中编号
从1至n的结点一一对应时称之为完全二叉树。
性质:
1.具有n个节点的完全二叉树的深度为[log2 N]+1
2.
值得注意的是:任何一个二叉树可以被分为三个部分,根节点,左子树,右子树。多采用分治算法,大问题分成类似的子问题,子问题再分成子问题。
满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树。
二叉树不是树的特殊情况,它们是两种概念
二叉树的子树要区分左子树和右子树,即使是只有一颗子树也应该区分
树的节点只有一个孩子时,无须区分是左还是右的次序
二、二叉树的存储结构
1.二叉树的顺序存储
按照满二叉树的节点层次编号,依次存放二叉树中的数据元素
#define MAXSIZE 100
Typedef TElemType SqBiTree[MAXSIZE]
SqBiTree bt; //bt是一个数组,最多可以放100个元素,每个元素的类型为 TElemType
值得注意的是:空节点部分也需要存储下来为0或者空
缺点:
- 空间有限
- 空节点的空间被浪费,比如单侧右单支树,当其深度为k,且只有k个节点时,需要长度为(2^k)-1的一维数组
故其适合存储满二叉树和完全二叉树
2.二叉树链式存储结构
二叉树的链式遍历
所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访
问结点所做的操作依赖于具体的应用问 题。 遍历是二叉树上最重要的运算之一,是二叉树上进行
其它运算之基础。
- DLR前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。根->左子树->右子树。
- LDR中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中。左子树->根->右子树。
- LRD后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。左子树->右子树->根。
遍历算法的时间复杂度: O(n)
遍历算法的空间复杂度: O(n) 栈占用的最大辅助空间
/*分治思想*/
typedef char BTDataType;
typedef struct BinaryTreeNode
{
struct BinaryTreeNode* left;
struct BinaryTreeNode* right;
BTDataType data;
}BTNode;
void PrevOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return ;
}
printf("%c ", root->data);
PrevOrder(root->left);
PrevOrder(root->right);
}
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return 0;
}
InOrder(root->left);
printf("%c ", root->data);
InOrder(root->right);
}
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return ;
}
PostOrder(root->left);
PostOrder(root->right);
printf("%c ", root->data);
}
空指针个数=2n-(n-1)
根据遍历序列确定二叉树
先序+中序,或者,后序+中序可以确定一个二叉树的顺序。
值得注意的是:
先序第一个总为祖先,后序最后一个总为祖先
中序遍历二叉树的非递归算法
基本思想:
- 建立一个栈
- 根节点进栈,遍历左子树
- 根节点出栈,遍历右子树
void InOrderTraverse(BTNode *T)
{
BTNode *root;
InitStack(S);
root=T;
while(root||!StackEmpty(s))
{
if(root)
{
Push(S,root);
root=root->left;
}
else
{
Pop(S,q);
printf("%c",q->data);
root=q->right;
}
return;
}
二叉树的层次遍历
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然
后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问
树的结点的过程就是层序遍历。
基本思想:使用一个队列
- 将根节点进队列
- 从队列中出列一个节点,先出上一层带动下一层
若它有左孩子节点,将左孩子节点进队;若它有右孩子节点,将右孩子节点进队
void LevelOrder(BTNode* root)
{
//先进先出上一层带动下一层
Queue q;
QueueInit(&q);
if (root)
{
QueuePush(&q, root);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
printf("%c", front->data);
if (front->left)
{
QueuePush(&q, front->left);
}
if (front->right)
{
QueuePush(&q, front->right);
}
}
printf("\n");
QueueDestory(&q);
}
3.二叉树遍历算法的应用
- 按先序遍历序列建立二叉树的二叉链表
注意包括上NULL才能唯一确定好二叉树
BTNode* CreateBTNode(BTNode *root)
{
char ch;
ch=getchar();
if(m=="#")
{
root=NULL;
return NULL:
}
else
{
root=(BTNode*)malloc(sizeof(BTNode));
root->data=ch;
CreateBTNode(root->left);
CreateBTNode(root->right);
return root;
}
}
- 复制二叉树
//创建新节点
BTNode* createNode(char data)
{
BTNode* newnode=(BTNode*)malloc(sizeeof(BTNode));
if(newnode==NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data=data;
newnode->left=NULL;
newnode->right=NULL;
return newnode;
}
BTNode *CopyTree(BTNode*root)
{
if(root==NULL)
{
return NULL;
}
BTNode*p=createNode('\0');
BTNode*newlptr=NULL;
BTNode*newrptr=NULL;
if(root->left!=NULL)
{
newlptr=CopyTree(root->left);
}
if(root->right!=NULL)
{
newrptr=CopyTree(root->right);
}
p->data=root->data;
p->left=newlptr;
p->right=newrptr;
return p;
}
- 计算二叉树的深度
//分治法,利用后序遍历,先求出左子树个数,再求出右子树个数,然后返回根的+1
int maxDepth(struct TreeNode* root)
{
if(root==NULL)
return 0;
//直接返回会丢失左树深度和右树深度的数据
int leftDepth=maxDepth(root->left);
int rightDepth=maxDepth(root->right);
return leftDepth>rightDepth?leftDepth+1:rightDepth+1;
}
- 计算二叉树总节点数
int TreeSize(BTNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
计算二叉树叶子节点的个数
//叶子节点的个数
int TreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return;
}
if (root->left == NULL && root->right == NULL)
return 1;
return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
三 线索二叉树(Threaded Binary Tree)
二叉链表有2n个节点,n-1个指针域,故有(n+1)空指针域
按照某种排列次序,利用二叉链表中的空指针域,将空的左孩子指针域指向前驱,空的右孩子指针域指向后继
将二叉树按照某种遍历次序变成线索二叉树的过程叫做线索化
如图为按照中序线索化
为了区分left和right指针到底是指向左右孩子的还是指向前驱后继的,
对二叉树每个节点增设两个标志域 Itag和rtag
Itag=0或者rtag=0指向节点的左右孩子
Itag=1或者rtag=1指向节点的前驱或者后继
typedef struct BiThrNode
{
int data;
int Ltag,rtag; //标志类型
struct BiThNode*left;
struct BiThNode*right;
}BiThrNode;
BiThrNode* pre;//前驱节点的变量
BiThrNode* head;//头指针指向遍历的第一个节点
//中序线索化
void inorderthreadTree(BiThrNode*root)
{
if(root==NULL)
{
return;
}
inorderthreadTree(root->left);
if(root->left==NULL)
{
//设置前驱结点
root->Ltag=1
root->left=pre;
}
//如果节点的右子节点为空,处理前驱的右指针
if(pre!=NULL&&pre->right==NULL)
{
pre->rtag=1;
pre->right=root;
}
//每处理一个节点 当前节点是下一个节点的前驱
pre=root;
//最后处理右子树
inorderthreadTree(root->right);
}