数据结构与算法 树

目录

一、定义

二、性质(二叉树性质)

三、树的存储(二叉树存储)

1、二叉树的顺序存储

2、二叉树的链式存储

四、树的遍历(二叉树的遍历,采用链式存储)

1、二叉树先序遍历(递归函数实现)

2、二叉树中序遍历(递归函数实现)

3、二叉树后序遍历(递归函数实现)

4、二叉树层次遍历(顺序队列实现)

五、树的建立(二叉树的建立,采用链式存储)

1、由先序遍历结果建立二叉树(递归实现)

六、树的其他操作

1、统计二叉树叶子结点数目

2、计算二叉树的结点总数

3、求二叉树的深度

4、复制一颗二叉树

七、其他

1、线索二叉树

2、树的存储结构

3、树与二叉树之间的转换

4、森林与二叉树之间的转换

八、哈夫曼树与哈夫曼编码

1、相关概念的理解

2、哈夫曼树的构造

3、哈夫曼构树造算法的实现

4、哈夫曼编码

一、定义

树是一种常用的一种结构,下面看看有哪些关于树的定义。

根结点、孩子结点、兄弟结点、叶子结点、内部结点、节结的度、树的度,如下图:

满二叉树一棵深度为k且有\bg_red \fn_phv 2^{k-1}个结点的二叉树称为满二叉树。其特点:1、每一层上的结点数都是最大结点数(即每层都满),2、叶子结点全部在最底层。如下图:

完全二叉树:深度为k的具有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号为1~n的结点一一对应时,称之为完全二叉树。如下图:

二、性质(二叉树性质)

二叉树(每个节点最多有两个孩子节点)是常用的一种树结构, 有如下些性质:

性质1:在二叉树的第i层上至多有\LARGE {\color{Green} {\color{Red} }}\bg_red \fn_phv 2^{i-1}个节点(i\geq1)。

性质2:深度为k的二叉树至多有\bg_red \fn_phv 2^{k-1}个节点(k\geq1)。

性质3:对任意一颗二叉树T,如果其叶子节点个数为n_{1},度为2的节点个数为n_{2},则n_{1} = n_{2} + 1。

证明,利用两种计算边的方法。每个节点都和一个父节点有条边相连,度为二的节点有两个边分别与左右孩子节点相连,度为1的节点则有一条边和孩子节点相连,两者计算结果相等。如下图:

性质4:具有n个结点的完全二叉树的深度为\bg_white \left \lfloor log_{2}^{n} \right \rfloor + 1。\left \lfloor x \right \rfloor称为x的低,表示不大于x的最大整数。

三、树的存储(二叉树存储)

1、二叉树的顺序存储

实现:按照满二叉树的结点层次编号,依次存放二叉树中的数据元素。如下几张图:

代码上,使用数组或者顺序表都可以,需要注意的是,没有结点的编号一般使用#代替,表示其孩子结点为NULL。 

2、二叉树的链式存储

 二叉树的链式定义如下:

typedef struct Tree{       // 二叉树链式存储 
	char data;
	struct Tree * lchild;  // 左孩子指针 
	struct Tree * rchild;  // 右孩子指针 
}TNode;

四、树的遍历(二叉树的遍历,采用链式存储)

二叉树可以按照先序遍历中序遍历后续遍历层次遍历方式遍历。

1、二叉树先序遍历(递归函数实现)

void dfs_xian(TNode * tree){    // 递归先序访问二叉树 
	if(tree == NULL)
		return ;
	printf("%c\n",tree->data);
	dfs_xian(tree->lchild);     // 递归访问该结点的左孩子结点
	dfs_xian(tree->rchild);     // 递归访问该结点的右孩子结点
}

2、二叉树中序遍历(递归函数实现)

void dfs_zhong(TNode * tree){    // 递归中序访问二叉树 
	if(tree == NULL)
		return ;
	dfs_zhong(tree->lchild);     // 递归访问该结点的左孩子结点
	printf("%c\n",tree->data);
	dfs_zhong(tree->rchild);     // 递归访问该结点的右孩子结点
}

3、二叉树后序遍历(递归函数实现)

void dfs_hou(TNode * tree){    // 递归后序访问二叉树 
	if(tree == NULL)
		return ;
	dfs_hou(tree->lchild);     // 递归访问该结点的左孩子结点
	dfs_hou(tree->rchild);     // 递归访问该结点的右孩子结点
	printf("%c\n",tree->data);	
}

4、二叉树层次遍历(顺序队列实现)

关于队列这篇文章有详细介绍和代码:数据结构与算法 队列_JAVA5120162041的博客-CSDN博客

#define MAXSIZE 100 
typedef struct{             // 顺序循环队列 
	TNode * data[MAXSIZE];  // 静态开辟空间
	int front;              // 队头指针 
	int rear;               // 队尾指针 
}SqQueue; 
bool QueueEmpty(SqQueue Q){ // 判断队列是否为空 
	return Q.front == Q.rear;
}
void InQueue(SqQueue &Q,TNode * e){       // 循环队列入队 
	if((Q.rear + 1) % MAXSIZE == Q.front) // 队满退出函数
        return ;
	Q.data[Q.rear] = e;                   // 进入队尾排队
	Q.rear = (Q.rear + 1) % MAXSIZE;      // 队尾指针加一
}
void OutQueue(SqQueue &Q,TNode * &e){     // 循环队列出队 
    if(Q.front == Q.rear)                 // 队空退出函数
		return ;
	e = Q.data[Q.front];                  // 出队到引用e
	Q.front = (Q.front + 1) % MAXSIZE;    // 头指针往后移
}
void bfs(TNode * T){  // 二叉树的层次遍历函数
	TNode * p;        
	SqQueue Q;        
	InQueue(Q,T);     // 根节点先指针入队列
	while(!QueueEmpty(Q)){      // 队列不为空时 
		OutQueue(Q,p);          // 队头元素出队列到p
		printf("%c",p->data);   // 访问p节点
		if(p->lchild != NULL)
			InQueue(Q,p->lchild); // 左孩子入队
		if(p->rchild != NULL)
			InQueue(Q,p->rchild); // 右孩子入队
	} 
} 

五、树的建立(二叉树的建立,采用链式存储)

由二叉树的先序序列和中序序列,或由二叉树的后序序列和中序序列可以确定唯一棵二叉树。下面就分别用代码实现一下。

1、由先序遍历结果建立二叉树(递归实现)

按理说还需要中序序列才能确定一颗二叉树,但是如果在空结处以#代替,采用递归是可以建立该二叉树的。代码如下:

char ch;
void CreTree(TNode * &T){     // 利用先序遍历结果建立二叉树 
	scanf("%c",&ch);
	if(ch == '#')
		T = NULL;
	else{
		T = (TNode *)malloc(sizeof(TNode));
		T->data = ch;         // 生成根节点
		CreTree(T->lchild);   // 递归建立左子树 
		CreTree(T->rchild);   // 递归建立右子树 
	}
}

六、树的其他操作

1、统计二叉树叶子结点数目

int LeafCount(TNode * T){  // 统计二叉树叶子节点 
	if(T == NULL)   // 为空返回0
		return 0;
	if(T->lchild == NULL && T->rchild == NULL)   // 左右孩子结点都为空时返回1
		return 1;
	else
		return LeafCount(T->lchild) + LeafCount(T->rchild);  // 否则返回孩子结点的叶子结点的和
}

2、计算二叉树的结点总数

int NodeCount(TNode * T){  // 计算二叉树的节点总数 
	if(T == NULL)  // 为空返回0
		return 0; 
	else           // 否则返回孩子结点数目 + 1(自己)
		return NodeCount(T->lchild) + NodeCount(T->rchild) + 1;
}

3、求二叉树的深度

int Depth1(TNode *T){  // 求二叉树的深度 方法一 
	if(T == NULL)  // 空返回0
		return 0;
	else{          // 否则返回孩子结点中最深的深度
		int m = Depth1(T->lchild);
		int n = Depth1(T->rchild);
		return m > n ? m + 1 : n + 1;
	} 
} 

int depth = 0;
int max = 0;
void Depth2(TNode * T){  // 求二叉树的深度 方法二 
	if(T == NULL)
		if(max < depth)
			max = depth;
	else{
		depth ++;           // 往下遍历时深度加一
		Depth2(T->lchild);  // 递归左孩子结点
		Depth2(T->rchild);  // 递归右孩子结点
		depth --;           // 往回回溯时,此刻深度减一 
	}
}

4、复制一颗二叉树

void CopyTree(TNode * T, TNode * &newT){  // 复制二叉树,新树以newT为根结点
	if(T == NULL)
		newT = NULL;
	else{
		newT = (TNode *)malloc(sizeof(TNode));
		newT->data = T->data;               // 复制数据域 
		CopyTree(T->lchild, newT->lchild);  // 递归复制左子树 
		CopyTree(T->rchild, newT->rchild);  // 递归复制右子树 
	}
} 

七、其他

这部分内容以介绍为主,应用的比前面相对少一些,如果感兴趣可以继续往下看。

1、线索二叉树

利用二叉链表中的空指针域:

如果某个结点的左孩子为空,则将空的左孩子指针域改为指向其前驱; 如果某结点的右孩子为空,则将空的右孩子指针域改为指向其后继。这种改变指向的指针称为“线索"。加上线索的二叉树称为线索二叉树。对二叉树按某种遍历次序使其变为二叉树的过程叫线索化。下图为一颗线索二叉树。

为了区分lchild和rchild指针到底是指向孩子的指针还是指向前驱或者后续的指针,对二叉链表中每个结点增设两个标志域ltag和rtag,并约定:

ltag = 0        lchild指向该结点的左孩子                                                                                              ltag =       lchild指向该结点的前驱                                                                                                rtag = 0        rchild指向该结点的右孩子                                                                                              rtag =       rchild指向该结点的后续    

其结构定义如下:

下面看一个例子:

2、树的存储结构

 

3、树与二叉树之间的转换

4、森林与二叉树之间的转换

八、哈夫曼树与哈夫曼编码

1、相关概念的理解

2、哈夫曼树的构造

3、哈夫曼构树造算法的实现

 伪代码如下:

4、哈夫曼编码

根据哈夫曼表求哈夫曼树: 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值