• [数据结构


    一、什么是二叉树?

    1.1 定义

    二叉树,是度为二的树,二叉树的每一个节点最多只有二个子节点,且两个子节点有序


    1.2 二叉树的重要特性

    (1)二叉树的第 i 层上节点数最多为 2n-1

    (2)高度为 k 的二叉树中,最多有 2k-1个节点;

    (3)在任意一棵二叉树中,如果终端节点的度为 n,度为 2 的节点数为 m,则 n=m+1;

    (4)二叉树的子树有左右之分,顺序不能颠倒。


    1.3 特殊二叉树

    斜树:所有的结点都只有左子树的二叉树叫左斜树,有结点都是只有右子树的二叉树叫右斜树。


    满二叉树:如果所有分支结点都存在左子树和右子树,并且所有的叶子都在同一层上,就称为满二叉树。


    完全二叉树:如果二叉树的深度为 k,则除第 k 层外其余所有层节点的度都为 2,且叶子节点从左到右依次存在,将满二叉树的最后一层从左到右依次删除若干节点就得到满二叉树。满二叉树是一棵特殊的完全二叉树,但完全二叉树不一定是满二叉树。


    因为二叉树的顺序存储结构缺点很明显,不能反应逻辑关系;对于特殊的二叉树(左斜树、右斜树),浪费存储空间。所以二叉树顺序存储结构一般只用于完全二叉树,因此这里只重点介绍二叉树的链式存储结构。


    二、什么是链式二叉树?

    链式二叉树:采用链式存储结构的二叉树。二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域是比较自然的想法。

    以下是链式二叉树的结点结构定义:

    /* 二叉树结点结构 */
    typedef struct BiTNode
    {
    	ElemType data;		// 结点数据
    	struct BiTNode *lchild, *rchild; // 左右孩子指针
    }BiTNode, BiTree;
    

    结构示意图如下图所示:


    三、具体实现

    3.1 二叉树的遍历

    二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

    二叉树的遍历次序不同于线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱和后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。


    二叉树的遍历方式可以很多,如果我们限制了从左到右的习惯方式,那么主要就分为四种:

    1. 前序遍历

    规则是若二叉树为空,则返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如下图所示,遍历顺序为:根节点-->左孩子-->右孩子,结果为:ABDGHCEIF。


    二叉树的定义是用递归的方式,所以,实现遍历算法也可以采用递归,而且极其简洁明了。实现代码如下:

    // 前序遍历(递归方式)
    void preOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    	preOrderTraverse(T->lchild); // 再前序遍历左子树
    	preOrderTraverse(T->rchild); // 最后前序遍历右子树
    }
    

    对于结点 B,只会执行左子树遍历函数;到了结点 D,先执行左子树遍历函数,再执行右子树遍历函数;至此结点 A 的左子树递归完毕,然后就会回到结点 A,开始执行结点 A 的右子树遍历函数。


    2. 中序遍历

    规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如下图所示,遍历顺序为:左孩子-->根节点-->右孩子,结果为:GDHBAEICF。


    那么二叉树的中序遍历算法是如何呢?哈哈,别以为很复杂,它和前序遍历算法仅仅只是代码的顺序上的差异。实现代码如下:

    // 中序遍历(递归方式)
    void inOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	inOrderTraverse(T->lchild); // 中序遍历左子树
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    	inOrderTraverse(T->rchild); // 最后中序遍历右子树
    }
    

    第一轮遍历到结点 D,接着执行左子树遍历函数,T指向结点 G,才到第一次执行 printf 的时候;然后回到结点 D,显示其数据,执行右子树遍历函数,显示结点 H 的数据。


    3. 后序遍历

    规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。如下图所示,遍历顺序为:左孩子-->右孩子-->根节点,结果为:GHDBIEFCA。


    实现代码如下:

    // 后序遍历(递归方式)
    void postOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	postOrderTraverse(T->lchild); // 先后序遍历左子树
    	postOrderTraverse(T->rchild); // 再后序遍历右子树
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    }
    

    4. 层序遍历

    规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。如下图所示,结果为:ABCDEFGHI。


    有同学会说,研究这么多遍历的方法干什么呢?

    我们用图形的方式来表现树的结构,应该说是非常直观和容易理解,但是对于计算机来说,它只有循环、判断等方式来处理,也就是说,它只会处理线性序列,而我们刚才提到的四种遍历方法,其实都是在把树中的结点变成某种意义的线性序列,这就给程序的实现带来了好处。另外不同的遍历提供了对结点依次处理的不同方式,可以在遍历过程中对结点进行各种处理。


    3.2 二叉树的建立

    如果我们要在内存中建立一个如下图左这样的树,为了能让每个结点确认是否有左右孩子,我们对它进行了扩展,变成下图右的样子,也就是将二叉树中每个结点的空指针引出一个虚结点,其值为一特定值,比如 “#”。我们称这种处理后的二叉树为原二叉树的扩展二叉树。扩展二叉树就可以做到一个遍历序列确定一棵二叉树了。比如下图的前序遍历序列就为 AB#D##C##。


    有了这样的准备,我们就可以来看看如何生成一棵二叉树了。假设二叉树的结点均为一个字符,我们把先前的前序遍历序列 ABDG##H###CE#I##F## 赋给一个字符数组。实现代码如下:

    // 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
    void createBiTree(BiTree **T)
    {
    	ElemType ch;
    
    	// str是全局数组,指向 'length' + "ABDG##H###CE#I##F##" 字符串
    	ch = str[index++];
    
    	if (ch != '#')
    	{
    		*T = (BiTree *)malloc(sizeof(BiTNode));
    		if (!*T)
    			return;
    		(*T)->data = ch; // 生成根结点
    
    		createBiTree(&(*T)->lchild); // 构造左子树
    		createBiTree(&(*T)->rchild); // 构造右子树
    	}		
    	else 
    	{
    		*T = NULL; // 值为#表示空树
    	}
    }
    

    其实建立二叉树,也是利用了递归的原理。只不过在原来应该是打印结点的地 方,改成了生成结点、给结点赋值的操作而已。所以大家理解了前面的遍历的话,对于这段代码就不难理解了。

    当然,你完全也可以用中序或后序遍历的方式实现二叉树的建立,只不过代码里生成结点和构造左右子树的代码顺序交换一下。另外,输入的字符也要做相应的更改。比如上图的扩展二叉树的中序遍历字符串就应该为 #B#D#A#C#,而后序字符串应该为 ###DB##CA。


    四、完整程序

    #include "stdio.h"    
    #include "stdlib.h"  
    #include "string.h"
    #include "io.h"  
    #include "math.h"  
    #include "time.h"
    
    #define TRUE 1
    #define FALSE 0
    
    #define MAXSIZE 100 /* 存储空间初始分配量 */
    
    typedef int Status;		/* Status是函数的类型,其值是函数结果状态代码,如TRUE等 */
    typedef char ElemType;
    
    /* 二叉树结点结构 */
    typedef struct BiTNode
    {
    	ElemType data;		// 结点数据
    	struct BiTNode *lchild, *rchild; // 左右孩子指针
    }BiTNode, BiTree;
    
    //Status strAssign(ElemType T[24], ElemType *chars); // 构建字符串
    Status initBiTree(BiTree **T); // 初始化二叉树
    void createBiTree(BiTree **T); // 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
    void preOrderTraverse(BiTree *T); // 前序遍历(递归方式)
    void inOrderTraverse(BiTree *T); // 中序遍历(递归方式)
    void postOrderTraverse(BiTree *T); // 后序遍历(递归方式)
    void destroyBiTree(BiTree **T); // 销毁二叉树
    Status isEmpty(BiTree *T); // 判断是否是空二叉树
    int getBiTreeDepth(BiTree *T); // 获得二叉树的深度
    ElemType getRoot(BiTree *T); // 获得二叉树的根
    ElemType getNodeValue(BiTree *p); // 获得指定结点的值
    void setValue(BiTree *p, ElemType value); // 给指定节点设置值
    
    /* 用于构造二叉树********************************** */
    int index = 1; // 因为str的头字节是字符串的长度,所以从1开始插入
    ElemType str[24] = {19, 'A','B','D','G','#','#','H','#','#','#','C','E','#','I','#','#','F','#','#'};
    
    // 初始化二叉树
    Status initBiTree(BiTree **T)
    {
    	*T = NULL;
    
    	return TRUE;
    }
    
    // 建立二叉树 [按前序输入二叉树中结点的值(一个字符)]
    void createBiTree(BiTree **T)
    {
    	ElemType ch;
    
    	// str是全局数组,指向 'length' + "ABDG##H###CE#I##F##" 字符串
    	ch = str[index++];
    
    	if (ch != '#')
    	{
    		*T = (BiTree *)malloc(sizeof(BiTNode));
    		if (!*T)
    			return;
    		(*T)->data = ch; // 生成根结点
    
    		createBiTree(&(*T)->lchild); // 构造左子树
    		createBiTree(&(*T)->rchild); // 构造右子树
    	}		
    	else 
    	{
    		*T = NULL; // 值为#表示空树
    	}
    }
    
    // 前序遍历(递归方式)
    void preOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    	preOrderTraverse(T->lchild); // 再前序遍历左子树
    	preOrderTraverse(T->rchild); // 最后前序遍历右子树
    }
    
    // 中序遍历(递归方式)
    void inOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	inOrderTraverse(T->lchild); // 中序遍历左子树
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    	inOrderTraverse(T->rchild); // 最后中序遍历右子树
    }
    
    // 后序遍历(递归方式)
    void postOrderTraverse(BiTree *T)
    {
    	// 判断二叉树是否存在
    	if (T == NULL)
    		return;
    
    	postOrderTraverse(T->lchild); // 先后序遍历左子树
    	postOrderTraverse(T->rchild); // 再后序遍历右子树
    	printf("%c", T->data); // 显示结点数据,可以更改为其它对结点操作
    }
    
    // 销毁二叉树
    void destroyBiTree(BiTree **T)
    {
    	if (*T != NULL)
    	{
    		if ((*T)->lchild) // 有左孩子
    			destroyBiTree(&(*T)->lchild); // 销毁左孩子子树
    		if ((*T)->rchild) // 有右孩子
    			destroyBiTree(&(*T)->rchild); // 销毁右孩子子树
    
    		free(*T); // 释放根结点
    		*T = NULL; // 空指针赋NULL
    	}
    }
    
    // 判断是否是空二叉树
    Status isEmpty(BiTree *T)
    {
    	return T == NULL ? TRUE : FALSE;
    }
    
    // 获得二叉树的深度
    int getBiTreeDepth(BiTree *T)
    {
    	if (!T)
    		return 0;
    
    	int left = getBiTreeDepth(T->lchild); // 获得左子树的深度
    	int right = getBiTreeDepth(T->rchild); // 获得右子树的深度
    
    	return left>right ? left + 1 : right + 1;
    }
    
    // 获得二叉树的根
    ElemType getRoot(BiTree *T)
    {
    	if (isEmpty(T))
    		return ' ';
    	else
    		return T->data;
    }
    
    // 获得指定结点的值
    ElemType getNodeValue(BiTree *p)
    {
    	return p->data;
    }
    
    // 给指定节点设置值
    void setValue(BiTree *p, ElemType value)
    {
    	p->data = value;
    }
    
    int main()
    {
    	BiTree *T;
    	
    	// 初始化二叉树
    	initBiTree(&T);
    
    	// 建立二叉树
    	createBiTree(&T);
    
    	// 判断是否是空二叉树 | 获得二叉树的深度
    	printf("构造空二叉树后,树空否?%d(1:是 0:否) 二叉树的深度=%d
    
    ", isEmpty(T), getBiTreeDepth(T));
    
    	// 获得二叉树的根
    	ElemType e = getRoot(T);
    	printf("二叉树的根为: %c
    ", e);
    
    	// 遍历二叉树
    	printf("
    前序遍历二叉树:");
    	preOrderTraverse(T);
    	printf("
    中序遍历二叉树:");
    	inOrderTraverse(T);
    	printf("
    后序遍历二叉树:");
    	postOrderTraverse(T);
    	printf("
    ");
    
    	// 销毁二叉树
    	destroyBiTree(&T);
    	printf("
    销毁二叉树后,树空否?%d(1:是 0:否) 树的深度=%d
    
    ", isEmpty(T), getBiTreeDepth(T));
    
    	return 0;
    }
    

    输出结果如下图所示:


    参考:

    《大话数据结构 - 第6章》 树


  • 相关阅读:
    linux安装skype
    (转)程序员最应该读的图书
    Smarty 学习笔记六 缓存
    Smarty 学习笔记二 常用内置变量
    Smarty 学习笔记七 debug
    文本文件与二进制文件区别
    zz 通用线程:Awk 实例,第 2部分
    MIT墙上的格言
    AWK学习笔记
    zz SED单行脚本快速参考 以及 AWK版本
  • 原文地址:https://www.cnblogs.com/linuxAndMcu/p/10338186.html
Copyright © 2020-2023  润新知