数据结构树与二叉树

数据结构 树与二叉树

一、树的概念及结构

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);
}
  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值