ETJava Beta | Java    注册   登录
  • 搜索:
  • 数据结构 顺序与链式二叉树的原理与实现(万字)

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/DSCL-ing/p/18344162
    如有侵权 请联系我们删除  (页面底部联系我们)  

    一、树概念及结构

    树的概念

    树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

    • 有一个特殊的结点,称为根结点,根节点没有前驱结点

    • 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i
      <= m)又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继

    因此,树是递归定义的。

    注意:树形结构中,子树之间不能有交集,否则就不是树形结构

    image-20240805211042676

    树的相关概念

    image-20240805211021150

    • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6
    • 叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点
    • 非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点
    • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点
    • 孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
    • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
    • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6
    • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
    • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
    • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
    • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
    • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
    • 森林:由m(m>0)棵互不相交的树的集合称为森林;

    树的表示

    树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,既然保存值域,也要保存结点和结点之间
    的关系,实际中树有很多种表示方式如:双亲表示法,孩子表示法、孩子双亲表示法以及孩子兄弟表示法
    等。简单的了解其中最常用的孩子兄弟表示法。

    typedef int DataType;
    struct Node
    {
        struct Node* _firstChild1;    // 第一个孩子结点 
        struct Node* _pNextBrother;   // 指向其下一个兄弟结点 
        DataType _data;               // 结点中的数据域
    };
    

    image-20240805211149246

    树在实际中的运用(表示文件系统的目录树结构)

    image-20240805211223360

    二、二叉树概念及结构

    概念

    一棵二叉树是结点的一个有限集合,该集合:

    1. 或者为空
    2. 由一个根节点加上两棵别称为左子树和右子树的二叉树组成

    image-20240805211342983

    从上图可以看出:

    1. 二叉树不存在度大于2的结点
    2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

    对于任意的二叉树都是由以下几种情况复合而成的:

    image-20240805211453621

    特殊的二叉树

    1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
      说,如果一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。
    2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K
      的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对
      应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

    image-20240805211526297

    二叉树的性质

    1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有 个结点.

    2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是 .

    3. 对任何一棵二叉树, 如果度为0其叶结点个数为 , 度为2的分支结点个数为 ,则有 = +1

    4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h= . (ps: 是log以2
      为底,n+1为对数)

    5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对
      于序号为i的结点有:

      1. 若i>0,i位置节点的双亲序号:(i-1)/2;i=0,i为根节点编号,无双亲节点

      2. 若2i+1<n,左孩子序号:2i+1,2i+1>=n否则无左孩子

      3. 若2i+2<n,右孩子序号:2i+2,2i+2>=n否则无右孩子

    二叉树的存储结构

    二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

    1. 顺序存储
      顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空
      间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们后面的章节会专门讲解。二叉树顺
      序存储在物理上是一个数组,在逻辑上是一颗二叉树。

    image-20240805211657030

    链式存储
    二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是
    链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所
    在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前简单二叉树一般都是二叉链,红黑树等会用到三叉链。

    image-20240805211747308

    typedef int BTDataType; 
    // 二叉链
    struct BinaryTreeNode
    {
    	  struct BinTreeNode* _pLeft;  // 指向当前节点左孩子 
        struct BinTreeNode* _pRight; // 指向当前节点右孩子 
        BTDataType _data; // 当前节点值域
    }
     
    // 三叉链
    struct BinaryTreeNode
    {
        struct BinTreeNode* _pParent; // 指向当前节点的双亲 
        struct BinTreeNode* _pLeft;   // 指向当前节点左孩子 
        struct BinTreeNode* _pRight; // 指向当前节点右孩子
        BTDataType _data; // 当前节点值域
    

    三、二叉树的顺序结构及实现

    二叉树的顺序结构

    普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结
    构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统
    虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

    image-20240805212045854

    堆的概念及结构

    如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储
    在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,
    2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
    堆的性质:

    • 堆中某个节点的值总是不大于或不小于其父节点的值;
    • 堆总是一棵完全二叉树。

    image-20240805212116179

    堆的实现

    堆向下调整算法

    给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整
    成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

    int array[] = {27,15,19,18,28,34,65,49,25,37};
    

    image-20240805212201348

    堆的创建

    给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算
    法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的
    子树开始调整,一直调整到根节点的树,就可以调整成堆。

    int a[] = {1,5,3,8,7,6}; 
    

    image-20240805212305378

    建堆时间复杂度

    因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的
    就是近似值,多几个节点不影响最终结果):

    image-20240805212337869

    因此:建堆的时间复杂度为O(N)。

    堆的插入

    先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

    image-20240805212407088

    堆的删除

    删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调
    整算法。

    image-20240805212422731

    堆的代码实现

    Heap.h
    #define _CRT_SECURE_NO_WARNINGS 1
    
    
    #pragma once
    
    #include<stdio.h>
    #include<assert.h>
    #include<stdlib.h>
    #include<stdbool.h>
    #include<string.h>
    
    
    typedef int HeapDataType;
    
    typedef struct Heap
    {
    	HeapDataType *a;
    	int size;
    	int capacity;
    	//顺序表思想
    }Heap;
    
    void HeapPrint(Heap *ps);
    
    void HeapInit(Heap *ps);
    
    void HeapDestroy(Heap *ps);
    
    void HeapPush(Heap *ps,HeapDataType x);
    
    void HeapPop(Heap*ps);
    
    int HeapSize(Heap *ps);
    
    bool HeapEmpty(Heap *ps);
    
    //接受一个堆,数组, 数组大小。建一个堆
    void HeapCreate(Heap*ps, HeapDataType *a, int size);
    
    void HeapSort(HeapDataType *a, int size);
    
    void AdjustDown(HeapDataType *a, int size, int parent);
    
    void AdjustUp(HeapDataType *a, int child);
    
    void Swap(HeapDataType *p1, HeapDataType *p2);
    
    Heap.c
    #include"Heap.h"
    
    void Swap(HeapDataType *p1, HeapDataType *p2)
    {
    	HeapDataType tmp = 0;
    	tmp = *p1;
    	*p1 = *p2;
    	*p2 = tmp;
    }
    
    //向上调整算法:从0插入建堆 以及 在堆的前提下插入 //不适合直接建堆(把数组从1开始向下建堆)
    void AdjustUp(HeapDataType *a,int child)
    {
    	assert(a);
    	//小堆改成a[child] < a[parent]
    	int parent = (child - 1) / 2;
    	while (parent >= 0 && child > 0 && a[child] > a[parent]) //把边界和条件全丢进去
    	{														  //这个没问题,没有重复出现两个相同的if。可以把条件一起放在while里
    		Swap(&a[child], &a[parent]);
    		child = parent;
    		parent = (child - 1) / 2;
    	}
    }
    
    //向下调整算法:前提:左右子树是堆 //适合建堆:递归从最小父亲开始(虽然可以从最小孩子)
    void AdjustDown(HeapDataType *a,int size, int parent) //小堆
    {
    	////比较两个孩子中最大/小的进行交换
    	//int child = parent * 2 + 1;
    	//if (child < size &&  parent < size && a[child] < a[child + 1]) //// ------------这种逻辑是不对的,一个在while外面,一个在while里面,重复,当size == child的时候卡掉一个
    	//{																				   //如果出现,务必消除,把while和if条件拆分出来
    	//	child = child + 1;
    	//}
    	////调整到叶子结束	 		                                   //&&先判断左边
    	//while (child < size &&  parent < size && a[parent] < a[child]) //child 可能越界,先保证child<size	
    	//{
    	//	Swap(&a[parent], &a[child]);
    	//	parent = child;
    	//	child = parent * 2 + 1;
    	//	if (child < size &&  parent < size && a[child] < a[child + 1])//-----------------------------------------------------------------------
    	//	{
    	//		child = child + 1;
    	//	}
    	//} 
    
    
    	int child = parent * 2 + 1;
    	while (child < size)
    	{
    		if (child + 1 < size &&a[child] > a[child + 1])
    		{
    			child++;
    		}
    		if (a[child] < a[parent])//小堆大于最小孩子就交换,
    		{						 //大堆小于最大孩子就交换
    			Swap(&a[child], &a[parent]);
    			parent = child;
    			child = parent * 2 + 1;
    		}
    		else
    		{
    			break;
    		}
    	}
    }
    
    //-------------------------------------------------------------------------
    
    void HeapPrint(Heap *ps)
    {
    	assert(ps);
    	for (int i = 0; i < ps->size; i++)
    	{
    		printf("%d ", ps->a[i]);
    	}
    }
    
    void HeapInit(Heap *ps)
    {
    	assert(ps);
    	ps->a = NULL;
    	ps->size = 0;
    	ps->capacity = 0;
    }
    
    
    //接受一个堆,一个数组,一个数组大小
    void HeapCreate(Heap *ps, HeapDataType *a, int size)
    {
    	assert(ps);
    	//开辟堆中数组空间
    	ps->a = (HeapDataType *)malloc(size * sizeof(HeapDataType));
    	if (ps->a == NULL)
    	{
    		perror("malloc fail");
    		exit(1);
    	}
    	memcpy(ps->a, a, size *sizeof(HeapDataType));//拷贝数组过去
    	ps->size = size;
    	ps->capacity = 2 * size;
    	//建堆
    	//从最小父亲开始向下调整
    	//循环,直到父亲/孩子不为0
    	int parent = (size - 1 - 1) / 2;
    	while (parent >= 0)
    	{
    		AdjustDown(ps->a, ps->size, parent);
    		parent--;
    	}
    }
    
    void HeapDestroy(Heap *ps)
    {
    	assert(ps);
    	free(ps->a);
    	ps->capacity = ps->size = 0;
    }
    
    void HeapPush(Heap *ps,HeapDataType x)
    {
    	assert(ps);
    	if (ps->size == ps->capacity)//开辟/扩容
    	{
    		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
    		HeapDataType *tmp = (HeapDataType *)realloc(ps->a, newCapacity * sizeof(HeapDataType));
    		if (tmp == NULL)
    		{
    			perror("realloc fail!");
    			exit(-1);
    		}
    		ps->a = tmp;
    		ps->capacity = newCapacity;
    	}
    	ps->a[ps->size] = x;
    	ps->size++;
    	AdjustUp(ps->a, ps->size - 1);
    }
    
    void HeapPop(Heap *ps)
    {
    	assert(ps);
    	assert(ps->size > 0); // 保证堆中存在数据
    	Swap(&ps->a[ps->size-1], &ps->a[0]);
    	ps->size--;
    	AdjustDown(ps->a,ps->size, 0);
    }
    
    
    //选数相当快,只需要logN次
    HeapDataType HeapTop(Heap *ps)
    {
    	assert(ps);
    	assert(ps->size > 0);
    	return ps->a[0];
    }
    
    int HeapSize(Heap *ps)
    {
    	assert(ps);
    	return ps->size;
    }
    
    bool HeapEmpty(Heap *ps)
    {
    	assert(ps);
    	return ps->size == 0;
    }
    
    

    堆的应用

    堆排序

    堆排序即利用堆的思想来进行排序,总共分为两个步骤:

    1. 建堆
      升序:建大堆
      降序:建小堆
    2. 利用堆删除思想来进行排序

    建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

    堆排序代码实现

    HeapSort.c
    #include"Heap.h"
    
    
    void HeapSort(HeapDataType *a, int size)
    {
    	assert(a);
    	// ---- 向下调整算法建堆  ----
    	//时间复杂度O(n)
    	int parent = (size - 1 - 1) / 2;
    	while (parent >= 0)
    	{
    		AdjustDown(a, size, parent);
    		parent--;
    	}
    	// ---- 选数 ----
    	//时间复杂度O(n*logN),好像是说n个数都要高度次调整
    
    	//int end = size - 1;//
    	while (size>0)
    	{
    		Swap(&a[0], &a[size-1]);//选数,选完就少一个
    		size--;
    		AdjustDown(a, size, 0);//
    	}
    
    }
    
    时间复杂度分析
    /*
    向下调整算法建堆时间复杂度计算
    假设满二叉树树高度h
    各层的节点数为
    第一层 2 ^ 0               ------向下调整h-1次
    第二层 2 ^ 1               ------向下调整h-2次
    第三层 2 ^ 2               ------向下调整h-3次
    ...    ... 
    第h - 1层  2 ^ (h - 2)     ------向下调整1次
    第h层  2 ^ (h - 1)
    
    向下调整算法建堆是从最小父亲开始,即第h-1层的最后一个节点 parent = (size-1-1)/2
    最坏情况下所有节点需要执行的次数为
    f(h) = 2^(h-2)*1 + 2^(h-3)*2 + ... + 2^1*(h-2) + 2^0*(h-1)    错位相减
    2*f(h) = 2^(h-1)*1 + 2^(h-2)*2 + ... + 2^2*(h-2) + 2^1*(h-1)
    作差、合并得f(h) = 2^h -h-1
    其中 满二叉树节点数N = 2^h-1,即h = log(N+1) 代入得
    f(N) = N - 1 - log(N+1)  , 舍去logN(数量级)
    所以O(n) = n
    
    -------------------------------------------------------------------------------
    而向上调整算法建堆时间复杂度比较吃亏,见图
    假设满二叉树树高度h
    各层的节点数为
    第一层 2 ^ 0               
    第二层 2 ^ 1               ------向上调整1次
    第三层 2 ^ 2               ------向上调整2次
    ...    ...
    第h - 1层  2 ^ (h - 2)     ------向上调整h-2次
    第h层  2 ^ (h - 1)         ------向上调整h-1次
    计算方法还是错位相减,由图显然可发现向上调整算法执行次数数量级明显提高
    不再计算
    O(n) = n*logN
    
    
    
    总结:向下调整算法跳过最下层最多节点层,且从下层开始节点多执行次数少。快
    	向上调整算法从上开始从节点少往节点多执行次数成倍增加,前面的加起来都没最后一层多,慢
    */
    
    
    //建堆---选数,放到最后,排除掉,重新选数
    
    //合计时间复杂度    O(n + n*logN) = O(n*logN) 
    

    TOP-K问题

    TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
    比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
    对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
    数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

    1. 用数据集合中前K个元素来建堆
      前k个最大的元素,则建小堆
      前k个最小的元素,则建大堆
    2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

    将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

    代码示例
    TopK.c
    #define _CRT_SECURE_NO_WARNINGS 1
    #include"Heap.h"
    #include<time.h>
    
    //取前K个最大数
    #define K 10
    
    void TopK()
    {
    	
    	int n = 10000;//int n = 10000;//数据量
    	int randMax = 10000;//int randMax = 10000;
    	int minHeap[K] ;
    	//int K = 100;
    
    	memset(minHeap, 0, sizeof(minHeap));//初始化数组,且用sizeof(arr)---不用取地址
    	srand((size_t)time(0));//不用加size_t也行,NULL实际上会被time强制转换成(void)0
    	
    	//TopK问题思路s
    	//建一个大小为K的堆
    
    	FILE *fin = fopen("data.txt", "w");
    	if (fin == NULL)
    	{
    		perror("fopen fail");
    		return;
    	}
    
    	//随机写入数据,rand , 使用fprintf("fout","%d ",)
    
    	int randK = K;//int randK = K;
    	for (int i = 0; i < n ;i++)
    	{
    		int val = rand() % randMax;  //最大值不超过randMax
    		if (val % 3 == 0 && randK-- > 0) 
    		{
    			val = randMax + randK;   //插入自定义最大值
    		}
    		fprintf(fin, "%d ", val);
    	}
    	//如果插入的最大值不够K个,就在末尾补上  --------出现的概率很小,忽略
    	while (randK-- > 0)
    	{
    		fprintf(fin, "%d ", randMax+randK);//注意分隔符,分隔给人看的,使用空格或\n方便用fscanf默认的分隔符识别
    	}
    	fprintf(fin, "%d ", 99999); //--- 验证能否插入且插入到最后
    
    	fclose(fin);
    
    
    	//for (int i = 0 ; i < K; i++)
    	//{
    	//	AdjustDown(minHeap, K, 0);
    	//}
    
    	FILE *fout = fopen("data.txt", "r");//重置文件指针
    	if (fout == NULL)
    	{
    		perror("fopen fail");
    		return;
    	}
    
    
    
    	int tmp;
    	while (fscanf(fout, "%d", &tmp) != EOF) //注意,由于设置了scanf的默认分隔符空格或\n,随意不用在格式%d后带分隔符识别
    	{
    		//从文件中提取数据
    		//提一个就和堆顶比较一次
    		//随着文件指针走下去会比较完文件所有数据
    		//ASCII文件使用fscanf("流","格式","dest");
    		if (tmp >minHeap[0])
    		{
    			minHeap[0] = tmp;
    		}
    		AdjustDown(minHeap, K, 0);//换一次调堆一次
    	}
    	fclose(fout);
    
    	for (int i = 0; i < K; i++)//打印数组
    	{
    		printf("%d ", minHeap[i]);
    	}
    }
    

    四、二叉树链式结构的代码实现

    二叉树的遍历

    前序、中序以及后序遍历
    学习二叉树结构,最简单的方式就是遍历。所谓二叉树遍历(Traversal)是按照某种特定的规则,依次对二叉
    树中的节点进行相应的操作,并且每个节点只操作一次。访问结点所做的操作依赖于具体的应用问题。 遍历
    是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。

    按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:

    1. 前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
    2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
    3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

    由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为
    根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

    层序遍历

    层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
    层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
    上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

    image-20240805213406908

    代码实现

    Tree.h

    #define _CRT_SECURE_NO_WARNINGS 1
    
    #pragma once
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<stdbool.h>
    
    //树的遍历
    
    //根据不同的递归遍历方法  访问  每个树(结构体)的data和左右孩子  进行操作。
    
    //每次递归到一颗新树(每个节点都是一颗树),都要先判断是否空树 ---- 以防止野指针错误
    
    
    //---- 创建二叉树 ----
    
    typedef int BTDataType;
    
    typedef struct BinaryTreeNode
    {
    	BTDataType data;
    	struct BinaryTreeNode *left;  //left sub tree
    	struct BinaryTreeNode *right; //right sub tree
    }BTNode;
    
    
    /*
    node->data = x;
    node->left = NULL;
    node->right = NULL;
    */
    BTNode* BuyBTNode(BTDataType x);//构建值为x的节点
    
    
    /*
    printf("%d", root->data);
    PrevOrder(root->left);
    PrevOrder(root->right);
    */
    void PrevOrder(BTNode *root);//先根遍历
    
    
    /*
    PrevOrder(root->left);s
    printf("%d", root->data);
    PrevOrder(root->right);
    */
    void InOrder(BTNode *root); //中根遍历
    
    
    /*
    PrevOrder(root->left);
    PrevOrder(root->right);
    printf("%d", root->data);
    */
    void PostOrder(BTNode *root); //后根遍历
    
    void LevelOrder(BTNode *root); //层序遍历
    
    
    /*
    return BTSize(root->left) + BTSize(root->right) + 1;
    */
    int BTSize(BTNode* root); //求二叉树节点个数
    
    
    /**/
    int BTLeafSize(BTNode* root); //求二叉树叶子节点个数
    
    
    int BTHeight(BTNode *root);//求二叉树高度
    
    int BTLevelKSize(BTNode *root, int k);//计算二叉树第k层节点
    
    BTNode *BTFind(BTNode* root, BTDataType x); //二叉树查找某节点地址
    
    void BTDestroy(BTNode*root); //二叉树销毁
    
    BTNode * rebuildBinaryTree(BTDataType* str, int *pi);  //构建一颗二叉树, pi为数组的下标地址
    
    
    bool isBTComplete(BTNode *root);
    

    构建二叉树与基本功能

    tree.c

    
    #include"Tree.h"
    
    
    /*创建一个值为x的二叉树节点*/
    BTNode* BuyBTNode(BTDataType x)    //创建值为x的节点
    {
    
    	BTNode *node = (BTNode*)malloc(sizeof(BTNode));//使用指针要指向实际空间
    	if (!node)
    	{
    		perror("malloc fail!");
    		exit(1);
    	}
    	node->data = x;
    	node->left = NULL;
    	node->right = NULL;
    	return node;
    }
    
    /*前序遍历*/
    void PrevOrder(BTNode *root) //previous  order顺序   NLR:前序遍历(Preorder Traversal 亦称
    {
    	if (!root )
    	{
    		printf("NULL ");
    		return;
    	}
    							  
    	printf("%d ", root->data);
    	PrevOrder(root->left);
    	PrevOrder(root->right);
    
    }
    
    
    /*中序遍历*/
    void InOrder(BTNode *root)  // ..中
    {
    
    	if (!root )
    	{
    		printf("NULL ");
    		return;
    	}
    
    	InOrder(root->left);
    	printf("%d ", root->data);
    	InOrder(root->right);
    }
    
    
    
    /*后续遍历*/
    void PostOrder(BTNode *root) // post- 前缀  ..之后   ..后面  ..后
    {
    	if (!root )
    	{
    		printf("NULL ");
    		return;
    	}
    
    	PostOrder(root->left);
    	PostOrder(root->right);
    	printf("%d ", root->data);
    
    }
    
    
    /*求二叉树所有节点个数*/
    int BTSize(BTNode* root)
    {
    /*
    	if (root == NULL)
    	{
    		return 0;
    	}
    
    	//思想:左子树 和 右子树 和 自己 的节点数 的和
    	return BTSize(root->left) + BTSize(root->right) + 1;
    */
    
    	//两个重复语法可以合并
    	return !root ? 0 : BTSize(root->left) + BTSize(root->right) + 1;
    
    }
    
    
    
    /*求二叉树叶子节点个数*/
    int BTLeafSize(BTNode* root)
    {
    	if (!root)
    	{
    		return  0;
    	}
    //思想:左右节点为空即为叶子
    	if(!root->left && !root->right)
    	{
    		return 1;
    	}
    
    	return BTLeafSize(root->left) + BTLeafSize(root->right);
    
    }
    
    
    
    /*求二叉树高度*/
    int BTHeight(BTNode *root)
    {
    	if (!root)
    	{
    		return 0;
    	}
    
    
    
    	//思路:比较左右子树高度,不要小的  只有关系运算符能够实现:比较两边选一边舍弃另一边
    
    	//方法一:
    	//return BTHeight(root->left) > BTHeight(root->right) 
    	//	? BTHeight(root->left) + 1 
    	//	: BTHeight(root->right) + 1;
    /*
    	点评;问题很大,递归之后栈帧会销毁,数据不再保存,
    		  每次递归都会计算两次子树高度,每个子树高度计算都是指数级*/
    
    
    	//优化:方法二:
    
    	int leftHeight = BTHeight(root->left);
    	int rightHeight = BTHeight(root->right);
    	return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    /*	点评:通过记下当前栈帧数据,避免重复计算*/
    }
    
    
    
    /*求二叉树第k层节点个数*/
    int BTLevelKSize(BTNode *root , int k)
    {
    	/*遇到第k层或空怎么做*/
    	if (!root)  
    	{
    		return 0;
    	}
    
    	if (k == 1)   //最后一层,第k层
    	{
    		return 1;
    	}
    
    	/*没到第k层怎么做*/
    	return BTLevelKSize(root->left,k-1) + BTLevelKSize(root->right,k-1) ;  //不是第k层,继续深入,找到第k层
    	//为什么是k-1,因为传的是k
    }
    
    
    
    /*二叉树查找*/
    BTNode *BTFind(BTNode* root, BTDataType x)
    {
    	if (!root )
    	{
    		return NULL;
    	}
    
    	if (root->data == x)
    	{
    		return root;
    	}
    
    	BTNode*left = BTFind(root->left ,x);
    	if (left)
    	{
    		return left;
    	}
    	
    	BTNode*right = BTFind(root->right,x);
    	if (right)
    	{
    		return right;
    	}
    
    	//可以写成下面这种形式,看起来好看
    /*
    	if (right)
    		return right;)*/
    	return NULL; //root !=NULL , 值不为x ,左右子树找不到,返回空
    }
    
    
    /*构建一个二叉树*/
    BTNode *rebuildBinaryTree(BTDataType* str, int *pi)  // pi为数组的下标地址
    {
    	if (str[*pi] == '#')
    	{
    		(*pi)++;
    		return  NULL;
    	}
    	BTNode*root = (BTNode*)malloc(sizeof(BTNode));
    	root->data = str[(*pi)++];
    	root->left = rebuildBinaryTree(str, pi);
    	root->right = rebuildBinaryTree(str, pi);
    
    	return root;
    
    }
    
    
    
    /*二叉树销毁*/
    void BTDestroy(BTNode*root) /*一级指针传值,调用后需要在函数外置空root*/
    {
    	if (!root)
    	{
    		return;
    	}
    	BTDestroy(root->left);
    	BTDestroy(root->right);
    	free(root);
    	root->left = root->right = NULL;
    }
    
    
    /*
    遍历命名
    
    N(Node)、L(Left subtree)和R(Right subtree)
    
    根据访问结点操作发生位置命名:
    ① NLR:前序遍历(Preorder Traversal 亦称(先序遍历))
    ——访问根结点的操作发生在遍历其左右子树之前。
    ② LNR:中序遍历(Inorder Traversal)
    ——访问根结点的操作发生在遍历其左右子树之中(间)。
    ③ LRN:后序遍历(Postorder Traversal)
    ——访问根结点的操作发生在遍历其左右子树之后。
    
    前三种次序与后三种次序对称
    
    */
    
    层序遍历
    
    
    #define _CRT_SECURE_NO_WARNINGS 1
    #include"Tree.h"
    #include"Queue.h"
    
    
    void LevelOrder(BTNode *root)
    {
    	//通过队列实现
    	Queue queue;//不带指针==给空间 , 带指针==只给指针空间,成员无空间
    	QueueInit(&queue);
    	if (root)
    		QueuePush(&queue, root);
    
    	//通过队列缓存子树滚动数据实现遍历
    	while (!QueueEmpty(&queue))
    	{
    		BTNode* front = QueueFront(&queue);//把数据从队列中取出
    		printf("%d ", front->data);
    		QueuePop(&queue);
    		if (front->left)
    		{
    			QueuePush(&queue, front->left);
    		}
    		if (front->right)
    		{
    			QueuePush(&queue, front->right);
    		}
    	}
    	QueueDestroy(&queue);
    
    }
    
    
    void LevelOrder2(BTNode *root)
    {
    	if (!root)
    	{
    		return;
    	}
    	Queue queue;
    	QueueInit(&queue);
    	int levelSize = 0;
    
    	QueuePush(&queue, root);
    	levelSize = 1;
    	while (!QueueEmpty(&queue))
    	{
    		while (levelSize--)
    		{
    			BTNode* front = QueueFront(&queue);
    			printf("%d ", front->data);
    			QueuePop(&queue);
    			if (front->left)
    				QueuePush(&queue, front->left);
    			if (front->right)
    				QueuePush(&queue, front->right);
    		}
    			printf("\n");
    			levelSize = QueueSize(&queue);
    
    	}
    
    	QueueDestroy(&queue);
    }
    
    节点个数
    #define _CRT_SECURE_NO_WARNINGS 1
    #include"Tree.h"
    
    int TreeSize(struct BinaryTreeNode *root)
    {
    	return !root ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
    }
    
    void _preorderTraversal(struct BinaryTreeNode *root, int *str, int* pi)
    {
    	if (!root)
    	{
    		return ;
    	}
    	str[(*pi)++] = root->data;
    	_preorderTraversal(root->left, str, pi);
    	_preorderTraversal(root->right, str, pi);
    }
    
    int* preorderTraversal(struct BinaryTreeNode* root, int* returnSize){
    
    	*returnSize = TreeSize(root);
    	int *a = (int*)malloc(*returnSize * sizeof(int));
    	int i = 0;
    	_preorderTraversal(root, a, &i);
    	return a;
    }
    
    判断是否完全二叉树
    #include"Tree.h"
    #include"Queue.h"
    
    bool isBTComplete(BTNode *root)
    {
    	if (!root)
    	{
    		return false;
    	}
    	Queue queue;
    	QueueInit(&queue);
    	QueuePush(&queue, root);
    
    	while (!QueueEmpty(&queue))
    	{
    		BTNode* front = QueueFront(&queue);
    		QueuePop(&queue);
    		if (!front) //一定存在下一层节点,所以直接跳出去,不用插入所有节点
    		{
    			QueuePop(&queue);
    			break;
    		}
    		QueuePush(&queue, front->left);
    		QueuePush(&queue, front->right);
    	}
    
    	while (!QueueEmpty(&queue))
    	{
    		if (QueueFront(&queue))
    		{
    			QueueDestroy(&queue);
    			return false;
    		}
    		QueuePop(&queue);
    	}
    	QueueDestroy(&queue);
    	return true;
    }
    
    判断是否是子树
    #include<stdio.h>
    struct TreeNode
    {
    	int val;
    	struct TreeNode* left;  //left sub tree
    	struct TreeNode* right; //right sub tree
    };
    
    bool isSubtree(struct TreeNode* root, struct TreeNode* subRoot){
    	if (subRoot == NULL)
    	{
    		return true;
    	}
    
    	if (root == NULL)
    	{
    		return false;
    	}
    
    	if (root->val == subRoot->val) //如果相等,且是相同子树,返回真
    	{
    		if (isSameTree(root, subRoot))
    		{
    			return true;
    		}
    	}
    
    	return isSubtree(root->left, subRoot)
    		|| isSubtree(root->right, subRoot);
    }
    
    判断两棵树是否相同相同
    #include<stdio.h>
    struct TreeNode
    {
    	int val;
    	struct TreeNode* left;  //left sub tree
    	struct TreeNode* right; //right sub tree
    };
    
    bool isSameTree(struct TreeNode* p, struct TreeNode* q){
    	if (p == NULL && q == NULL)
    	{
    		return 1;
    	}
    	else if (p == NULL&&p != q || q == NULL && p != q)
    	{ /*注意逻辑运算先后顺序,整个if-else是判断有1个NULL以上为前提,运算符要求先有空*/
    		//可以优化成 p == NULL && q == NULL,因为已有前提;
    		return 0;
    	}
    
    	if (p->val != q->val)
    	{
    		return 0;
    	}
    	else
    		return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
    
    
    
    
    两个树是否对称
    #include "Tree.h"
    
    
    bool _isSymmetric(BTNode *root1, BTNode *root2)
    {
    	if (!root1 &&!root2)
    	{
    		return true;
    	}
    	if (!root1 || !root2)
    	{
    		return false;
    
    	}
    
    	if (root1->data != root2->data)
    	{
    		return false;
    	}
    
    	return _isSymmetric(root1->left, root2->right) && _isSymmetric(root1->right, root2->left);
    }
    
    
    bool isSymmetric(BTNode *root)
    {
    	return !root || _isSymmetric(root->left, root->right);
    
    }
    
    单值二叉树

    如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树

    #include<stdio.h>
    struct TreeNode
    {
    	int val;
    	struct TreeNode* left;  //left sub tree
    	struct TreeNode* right; //right sub tree
    };
    
    bool isUnivalTree(struct TreeNode* root){
    
    	if (root == NULL)
    	{
    		return true;
    	}
    
    	if (root->left && root->left->val != root->val)
    	{
    		return false;
    	}
    
    	if (root->right && root->right->val != root->val)
    	{
    		return false;
    	}
    
    	return isUnivalTree(root->left) && isUnivalTree(root->right);
    }
    

    用到的队列

    Quque.h
    #pragma once
    #define _CRT_SECURE_NO_WARNINGS 1
    #include<stdio.h>
    #include<stdlib.h>
    #include<stdbool.h>
    #include<assert.h>
    //#include"tree.h"
    
    struct BinaryTreeNode; //类型声明   : 类型是可以声明的,只要变量名就可以---原理,搜索整个源码
    
    
    typedef  struct BinaryTreeNode* QDataType;
    
    typedef struct QueueNode
    {
    	struct QueueNode *next;
    	QDataType data;
    }QNode;
    
    //控制变量结构体
    //只有一个值,就不用定义结构体,有多个就定义结构题。
    
    typedef struct Queue
    {
    	struct QueueNode *head; //队头,出队,头删
    	struct QueueNode *tail; //队尾,入队,尾插
    }Queue;
    //是指针变量就传二级指针,是普通变量就传一级
    
    void QueueInit(Queue *pq);
    void QueueDestroy(Queue *pq);
    
    void QueuePush(Queue *pq, QDataType x);
    void QueuePop(Queue *pq);
    
    QDataType QueueFront(Queue *pq);
    QDataType QueueBack(Queue *pq);
    
    int QueueSize(Queue *pq);
    bool QueueEmpty(Queue *pq);
    
    
    Queue.c
    #include "Queue.h"
    
    
    void QueueInit(Queue *pq)
    {
    	assert(pq);
    	pq->head = NULL;
    	pq->tail = NULL;
    	
    
    }
    
    void QueueDestroy(Queue* pq)
    {
    	assert(pq);
    	QNode * next = NULL;
    	QNode *cur = pq->head;
    	while (cur)
    	{
    		next = cur->next;
    		free(cur);
    		cur = next;
    	}
    	pq->head=pq->tail = NULL;
    }
    
    void QueuePush(Queue *pq,QDataType x)
    {
    	assert(pq);
    	QNode *newnode = (QNode*)malloc(sizeof(QNode));
    	if (newnode == NULL)
    	{
    		perror("malloc fail ");
    		exit(-1);
    	}
    	//先初始化再使用
    	newnode->data = x;
    	newnode->next = NULL;
    	//单链表队列-尾插头删。
    	if (pq->tail == NULL)//头尾都行,判断一个就可以了
    	{
    		pq->head = pq->tail = newnode;
    	}
    	else                             //尾插
    	{
    		pq->tail->next = newnode; //队尾指向的节点链接上新节点
    		pq->tail = newnode;      //队尾指向新节点
    	}
    }
    
    void QueuePop(Queue *pq)
    {
    	assert(pq);
    	assert(!QueueEmpty(pq));
    	if (pq->head->next == NULL)//只有一个节点
    	{
    		free(pq->head);//先释放
    		pq->tail = pq->head = NULL;//后置空
    	}
    	else
    	{
    		QNode *next = pq->head->next;//记住下一个
    		free(pq->head);//释放头节点
    		pq->head = next;//下个节点成为新节点
    	}
    }
    
    QDataType QueueFront(Queue *pq)
    {
    	assert(pq);
    	assert(!QueueEmpty(pq));
    	return pq->head->data;
    }
    
    
    QDataType QueueBack(Queue *pq)
    {
    	assert(pq);
    	assert(!QueueEmpty(pq));
    	return pq->tail->data;
    }
    
    int QueueSize(Queue *pq)
    {
    	assert(pq);
    	QNode *cur = pq->head;
    	int size = 0;
    	while (cur)
    	{
    		size++;
    		cur = cur->next;
    	}
    	return size;
    }
    
    bool QueueEmpty(Queue *pq)
    {
    	assert(pq);
    	//return QueueSize(pq) == 0;
    	return pq->head == NULL ;//只要有一个就可以了
    	//head为空tail也为空
    }