【数据结构】实现二叉树

文章目录

  • 1.树
    • [1.1 树的概念与结构](#1.1 树的概念与结构)
    • [1.2 树相关术语](#1.2 树相关术语)
    • [1.3 树的表⽰](#1.3 树的表⽰)
    • [1.4 树形结构实际运⽤场景](#1.4 树形结构实际运⽤场景)
  • 2.⼆叉树
    • [2.1 概念与结构](#2.1 概念与结构)
    • [2.2 特殊的⼆叉树](#2.2 特殊的⼆叉树)
      • [2.2.1 满⼆叉树](#2.2.1 满⼆叉树)
      • [2.2.2 完全⼆叉树](#2.2.2 完全⼆叉树)
    • [2.3 ⼆叉树存储结构](#2.3 ⼆叉树存储结构)
      • [2.3.1 顺序结构](#2.3.1 顺序结构)
      • [2.3.2 链式结构](#2.3.2 链式结构)
  • 3.实现顺序结构⼆叉树
    • [3.1 堆的概念与结构](#3.1 堆的概念与结构)
    • [3.2 堆的实现](#3.2 堆的实现)
    • [3.2 堆的应⽤](#3.2 堆的应⽤)
      • [3.2.1 堆排序](#3.2.1 堆排序)
      • [3.2.2 TOP-K问题](#3.2.2 TOP-K问题)
  • 4.实现链式结构⼆叉树
    • [4.1 前中后序遍历](#4.1 前中后序遍历)
      • [4.1.1 遍历规则](#4.1.1 遍历规则)
      • [4.1.2 代码实现](#4.1.2 代码实现)
    • [4.2 结点个数以及⾼度等](#4.2 结点个数以及⾼度等)
    • [4.3 层序遍历](#4.3 层序遍历)
    • [4.4 判断是否为完全⼆叉树](#4.4 判断是否为完全⼆叉树)

1.树

1.1 树的概念与结构

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

• 有⼀个特殊的结点,称为根结点,根结点没有前驱结点。

• 除根结点外,其余结点被分成 M(M>0) 个互不相交的集合 T1、T2、......、Tm ,其中每⼀个集合 Ti(1 <= i <= m) ⼜是⼀棵结构与树类似的⼦树。每棵⼦树的根结点有且只有⼀个前驱,可以 有 0 个或多个后继。因此,树是递归定义的。

树形结构中,⼦树之间不能有交集,否则就不是树形结构

树形结构:

• ⼦树是不相交的(如果存在相交就是图了,图以后会讲解)

• 除了根结点外,每个结点有且仅有⼀个⽗结点

• ⼀棵N个结点的树有N-1条边

1.2 树相关术语

⽗结点/双亲结点:若⼀个结点含有⼦结点,则这个结点称为其⼦结点的⽗结点;-如上图:A是B的⽗结点

⼦结点/孩⼦结点:⼀个结点含有的⼦树的根结点称为该结点的⼦结点;如上图:B是A的孩⼦结点

结点的度:⼀个结点有⼏个孩⼦,他的度就是多少;⽐如A的度为6,F的度为2,K的度为0

树的度:⼀棵树中,最⼤的结点的度称为树的度;如上图:树的度为 6

叶⼦结点/终端结点:度为 0 的结点称为叶结点;如上图: B、C、H、I... 等结点为叶结点

分⽀结点/⾮终端结点:度不为 0 的结点;如上图: D、E、F、G... 等结点为分⽀结点

兄弟结点:具有相同⽗结点的结点互称为兄弟结点(亲兄弟);如上图: B、C 是兄弟结点

结点的层次:从根开始定义起,根为第 1 层,根的⼦结点为第 2 层,以此类推;

树的⾼度或深度:树中结点的最⼤层次;如上图:树的⾼度为 4

结点的祖先:从根到该结点所经分⽀上的所有结点;如上图: A 是所有结点的祖先

路径:⼀条从树中任意节点出发,沿⽗节点-⼦节点连接,达到任意节点的序列;⽐如A到Q的路径为: A-E-J-Q;H到Q的路径H-D-A-E-J-Q

⼦孙:以某结点为根的⼦树中任⼀结点都称为该结点的⼦孙。如上图:所有结点都是A的⼦孙

森林:由 m(m>0) 棵互不相交的树的集合称为森林;

1.3 树的表⽰

孩⼦兄弟表⽰法: 树结构相对线性表就⽐较复杂了,要存储表⽰起来就⽐较⿇烦了,既然保存值域,也要保存结点和结点之间的关系,实际中树有很多种表⽰⽅式如:双亲表⽰法,孩⼦表⽰法、孩⼦双亲表⽰法以及孩⼦兄弟表⽰法等。

我们这⾥就简单的了解其中最常⽤的孩⼦兄弟表⽰法

c 复制代码
struct TreeNode  
{  
    struct Node* child; // 左边开始的第⼀个孩⼦结点  
    struct Node* brother; // 指向其右边的下⼀个兄弟结点  
    int data; // 结点中的数据域  
};

1.4 树形结构实际运⽤场景

⽂件系统是计算机存储和管理⽂件的⼀种⽅式,它利⽤树形结构来组织和管理⽂件和⽂件夹。在⽂件系统中,树结构被⼴泛应⽤,它通过⽗结点和⼦结点之间的关系来表⽰不同层级的⽂件和⽂件夹之间的关联。

2.⼆叉树

2.1 概念与结构

在树形结构中,我们最常⽤的就是⼆叉树,++⼀棵⼆叉树是结点的⼀个有限集合,该集合由⼀个根结点加上两棵别称为左⼦树和右⼦树的⼆叉树组成或者为空++。

从上图可以看出⼆叉树具备以下特点:

  1. ⼆叉树不存在度⼤于 2 的结点

  2. ⼆叉树的⼦树有左右之分,次序不能颠倒,因此⼆叉树是有序树

    注意:对于任意的⼆叉树都是由以下⼏种情况复合⽽成的现实中的⼆叉树

2.2 特殊的⼆叉树

2.2.1 满⼆叉树

⼀个⼆叉树,如果每⼀个层的结点数都达到最⼤值,则这个⼆叉树就是满⼆叉树。也就是说,如果⼀个⼆叉树的层数为 K ,且结点总数是2k-1 ,则它就是满⼆叉树。

第K层的结点个数为2k-1

2.2.2 完全⼆叉树

完全⼆叉树是效率很⾼的数据结构,完全⼆叉树是由满⼆叉树⽽引出来的。对于深度为 K 的,有 n 个 结点的⼆叉树,当且仅当其每⼀个结点都与深度为K的满⼆叉树中编号从 1 ⾄ n 的结点⼀⼀对应时称之为完全⼆叉树。++(除了最后一层,其他每层的结点个数都达到最大,最后一层结点个数不一定达到最大)++

++要注意的是必须先有左子树,再有右子树,如果只有右子树,就不是完全二叉树++


⼆叉树性质

根据满⼆叉树的特点可知:

1)若规定根结点的层数为 1 ,则⼀棵⾮空⼆叉树的第i层上最多有2i−1个结点

2)若规定根结点的层数为 1 ,则深度为 h 的⼆叉树的最⼤结点数是2h − 1

3)若规定根结点的层数为 1 ,具有 n 个结点的满⼆叉树的深度 h = log2 (n + 1)

2.3 ⼆叉树存储结构

⼆叉树⼀般可以使⽤两种结构存储,⼀种顺序结构,⼀种链式结构。

2.3.1 顺序结构

顺序结构存储就是使⽤数组来存储,⼀般使⽤数组只适合表⽰完全⼆叉树,因为不是完全⼆叉树会有空间的浪费,完全⼆叉树更适合使⽤顺序结构存储。

现实中我们通常把堆(⼀种⼆叉树)使⽤顺序结构的数组来存储,需要注意的是这⾥的堆和操作系统虚拟进程地址空间中的堆是两回事,⼀个是数据结构,⼀个是操作系统中管理内存的⼀块区域分段。

2.3.2 链式结构

⼆叉树的链式存储结构是指,⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址。链式结构⼜分为⼆叉链和三叉链,当前我们学习中⼀般都是⼆叉链。后⾯⾼阶数据结构如红⿊树等会⽤到三叉链。


3.实现顺序结构⼆叉树

⼀般堆使⽤顺序结构的数组来存储数据,堆是⼀种特殊的++完全⼆叉树++,具有⼆叉树的特性的同时,还具备其他的特性。

3.1 堆的概念与结构

如果有⼀个关键码的集合 ,把它的所有元素按完全⼆叉树的顺序存储⽅式存储,在⼀个⼀维数组中,并满⾜++子结点的值不大于(不小于 )父结点的值则称为大堆(或小堆)++。

堆顶是最值(大堆是最大值,小堆是最小值)

将根结点最⼤的堆叫做最⼤堆或⼤根堆,根结点最⼩的堆叫做最⼩堆或⼩根堆。

堆具有以下性质

• 堆中某个结点的值总是不⼤于或不⼩于其⽗结点的值;

• 堆总是⼀棵完全⼆叉树。

⼆叉树性质

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

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

  2. 左孩⼦序号: 2i+1 ,若 2i+1<n, 有左孩⼦

  3. 右孩⼦序号:2i+2,若 2i+2<n ,有右孩⼦

    3.2 堆的实现

    堆底层结构为数组,因此定义堆的结构为:

c 复制代码
//Heap.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdlib.h>
typedef int HPDataType; 
typedef struct Heap 
{ 
    HPDataType* arr;//数组 
    int size;//有效数据个数 
    int capacity;//容量
}HP; 
//默认初始化堆 
void HPInit(HP* php); 
 //堆的销毁 
void HPDestroy(HP* php); 
//堆的插⼊ 
void HPPush(HP* php, HPDataType x); 
//取堆顶 
HPDataType HPTop(HP* php); 
// 删除堆顶的数据 
void HPPop(HP* php); 
// 判空 
bool HPEmpty(HP* php); 
//数据交换
void Swap(int* x,int* y);
//打印
void HPPrint(HP* php);
//向上调整算法 
void AdjustUp(HPDataType* arr, int child); 
//向下调整算法 
void AdjustDown(HPDataType* arr, int n, int parent); 
c 复制代码
//test.c
#include"Heap.h"
void test01()
{
    HP hp;
    HPInit(&hp);
    HPPush(&hp,25);
    HPPush(&hp,15);
    HPPush(&hp,10);
    HPPush(&hp,80);
    HPPrint(HP* php);
    
    //如果是大堆,输出的结果是从大到小,如果是小堆,输出的结果是从小到大
    while(!HPEmpty(&hp))
    {
        int top=HPTop(&hp);
        printf("%d ",top);
        HPPop(&hp);
    }
    
    //HPPop(&hp);
    //HPPrint(&hp);
    //HPPop(&hp);
    //HPPrint(&hp);
    //HPPop(&hp);
    //HPPrint(&hp);
    //HPPop(&hp);
    //HPPrint(&hp);
    
    HPDesTroy(&hp);
}
int main()
{
    test01();
    return 0;
}
c 复制代码
//Heap.c
//默认初始化堆 
#include"Heap.h"
void HPInit(HP* php)
{
    assert(php);
    php->arr=NULL;
    php->size=php->capacity=0;
}
//堆的销毁 
void HPDestroy(HP* php)
{
    assert(php);
    if(php->arr)
        free(php->arr);
    php->arr=NULL;
    php->size=php->capacity=0;
}
void Swap(int* x,int* y)
{
    int tmp=*x;
    *x=*y;
    *y=tmp;
}
void HPPrint(HP* php)
{
    for(int i=0;i<php->size;i++)
    {
        printf("%d ",php->arr[i]);
    }
    printf("\n");
}
//向上调整算法 
void AdjustUp(HPDataType* arr, int child)
{
    int parent=(child-1)/2;
    while(child>0)
    {
        //建大堆用">"
        //建小堆用"<"
        if(arr[child]>arr[parent])
        {
            Swap(&arr[child],&arr[parent]);
            child=parent;
            parent=(child-1)/2;
        }
        else
        {
            break;
        }
    }
}
// 判空 
bool HPEmpty(HP* php)
{
    assert(php);
    return php->size==0;
}
//向下调整算法 
void AdjustDown(HPDataType* arr, int n, int parent)
{
    int child=parent*2+1;
    while(child<n)
    {
        //建大堆:arr[child]<arr[child+1]
        //建小堆:arr[child]>arr[child+1]
        if((child+1<n)&&(arr[child]<arr[child+1]))
        {
            child++;
        }
        //建大堆:>
        //建小堆:<
        if(arr[child]>arr[parent])
        {
            Swap(&arr[child],&arr[parent]);
            parent=child;
            child=parent*2+1;
        }
        else
            break;
    }
}
//堆的插⼊ 
void HPPush(HP* php, HPDataType x)
{
    assert(php);
    //空间不够要增容
    if(php->size==php->capacity)
    {
        int newCapacity=php->capacity==0?4:2*capacity;
        HPDataType* tmp=(HPDataType*)realloc(php->arr,newCapacity*sizeof(HPDataType));
        if(tmp==NULL)
        {
            perror("realloc");
            exit(1);
        }
        php->arr=tmp;
        php->capacity=newCapacity;
    }
    //空间足够
    php->arr[php->size++]=x;
    //必须用向上调整
    AdjustUp(php->arr,php->size-1);
}
//取堆顶 
HPDataType HPTop(HP* php)
{
    assert(!HPEmpty(php));
    return php->arr[0];
}
// 删除堆顶的数据(操作堆顶) 
void HPPop(HP* php)
{
    assert(!HPEmpty(php));
    Swap(&php->arr[0],&php->arr[php->size-1]);
    --php->size;
    //堆顶数据要向下调整
    AdjustDown(php->arr,php->size,0);
}

3.2 堆的应⽤

3.2.1 堆排序

++直接修改数组的值,不是只打印结果++

版本⼀:基于已有数组建堆、取堆顶元素完成排序

c 复制代码
// 1、需要堆的数据结构 
// 2、空间复杂度 O(N) 
void HeapSort(int* arr, int n) 
{ 
    HP hp; 
    HPInit(&hp);
    //调用push将数组中的数据放入堆中
    for(int i = 0; i < n; i++) 
    { 
        HPPush(&hp,arr[i]); 
    } 
    int i = 0; 
    while (!HPEmpty(&hp)) 
    { 
        arr[i++] = HPTop(&hp);//直接修改数组的值,不是只打印结果 
        HPPop(&hp); 
    } 
    HPDestroy(&hp); 
}
int main()
{
    int arr[6]={25,15,10,56,70,30};
    printf("排序之前:\n");
    for(int i=0;i<6;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
    
    HeapSort(arr,6);
    
    printf("排序之后:\n");
    for(int i=0;i<6;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
    return 0;
}

该版本有⼀个前提,必须提供有现成的数据结构堆

版本⼆(借助堆排序的思想):对数组直接建堆,⾸尾交换,交换后的堆尾数据从堆中删掉,将堆顶数据向下调整选出次⼤的数据

c 复制代码
// O(N*logN)
//排升序,修改向下调整算法内部建大堆
//排降序,建小堆
void HeapSort(int* arr, int n) 
{ 
    //根据最后一个点求父结点 
    for (int i = (n-1-1)/2; i >= 0; i--) 
    { 
        //从大到小或者从小到大排序直接修改向下调整函数的内部代码
        AdjustDown(arr, n, i); 
    } 
    // O(N*logN) 
    int end = n - 1; 
    while (end > 0) 
    { 
        Swap(&arr[0], &arr[end]); 
        AdjustDown(arr, end, 0); 
        --end; 
    } 
}
c 复制代码
//用向上调整算法排序
void HeapSort(int* arr, int n) 
{ 
     //从前往后调整
    for (int i = 0; i <n; i++) 
    { 
        AdjustUp(arr, i); 
    }  
    int end = n - 1; 
    while (end > 0) 
    { 
        Swap(&arr[0], &arr[end]); 
        AdjustDown(arr, end, 0); 
        --end; 
    } 
}

分析: 向上调整算法的复杂度:结点向上调整了几层,while就循环了几次。假设结点个数为n,满二叉树的结点个数=2k-1,最坏的情况结点从最后一层要向上调整到顶点,所以k=log2(n+1),所以向上调整算法的时间复杂度是logn

向下调整算法的复杂度:结点向下调整了几层,while就循环了几次。与向上调整算法相同,时间复杂度为logn

堆排序时间复杂度(向上调整算法):


再采用错位相减得到T(h)=2h(h-2)+2

根据二叉树性质:n=2h-1和h=log2(n+1)

T(n)=(n+1)(log2(n+1)-2)+2

堆排序时间复杂度(向上调整算法)为:O(n*log n)
堆排序时间复杂度(向下调整算法)



所以堆排序用向下调整法,堆排序的时间复杂度为O(n + n ∗ log n) ,即O(n*log n)

3.2.2 TOP-K问题

TOP-K问题:即求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤。 ⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

面试常问:假如有10亿整数,需要申请多大的内存

1G=1024MB=10241024KB=1024 10241024byte(差不多10亿)
因为是整数(四个字节)10亿
4=4G

面试官又问:如果只有1KB呢

对于Top-K问题,能想到的最简单直接的⽅式就是排序,但是:如果数据量⾮常⼤,排序就不太可取了 (可能数据都不能⼀下⼦全部加载到内存中)。最佳的⽅式就是⽤堆来解决,基本思路如下:

1)⽤数据集合中前K个元素来建堆
前k个最⼤的元素,则建⼩堆
前k个最⼩的元素,则建⼤堆

2)⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素 将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素

c 复制代码
void CreateNDate() 
{ 
    // 造数据 
    int n = 100000; 
    srand(time(0)); 
    const char* file = "data.txt"; 
    FILE* fin = fopen(file, "w"); 
    if (fin == NULL) 
    { 
        perror("fopen error"); 
        exit(1); 
    } 
    for (int i = 0; i < n; ++i) 
    { 
    //随机生成小于1000000的100000个数据存入文件
        int x = (rand()+i) % 1000000; 
        fprintf(fin, "%d\n", x); 
    } 
    fclose(fin); 
} 
void topk() 
{ 
    printf("请输⼊k:>"); 
    int k = 0; 
    scanf("%d", &k); 
    const char* file = "data.txt"; 
    FILE* fout = fopen(file, "r"); 
    if (fout == NULL)
    { 
        perror("fopen error"); 
        exit(1); 
    } 
    int* minheap = (int*)malloc(sizeof(int) * k); 
    if (minheap == NULL) 
    { 
        perror("malloc error"); 
        exit(2); 
    } 
    //读取k个数据存入数组中
    for (int i = 0; i < k; i++) 
    { 
        fscanf(fout, "%d", &minheap[i]); 
    } 
    // 建k个数据的⼩堆 
    for (int i = (k - 1 - 1) / 2; i >= 0; i--) 
    { 
        AdjustDown(minheap, k, i); 
    } 
    int x = 0; 
    while (fscanf(fout, "%d", &x) != EOF) 
    { 
        // 读取剩余数据(n-k)个,⽐堆顶的值⼤,就替换他进堆 
        if (x > minheap[0]) 
        { 
            minheap[0] = x; 
            AdjustDown(minheap, k, 0); 
        } 
    } 
    for (int i = 0; i < k; i++) 
    { 
        printf("%d ", minheap[i]); 
    } 
    fclose(fout); 
}

时间复杂度:O(n) = 3k+(n-k)logn =k + (n − k)logn

4.实现链式结构⼆叉树

⽤链表来表⽰⼀棵⼆叉树,即⽤链来指⽰元素的逻辑关系。通常的⽅法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别⽤来给出该结点左孩⼦和右孩⼦所在的链结点的存储地址, 其结构如下:

c 复制代码
typedef int BTDataType; 
// ⼆叉链 
typedef struct BinaryTreeNode 
{ 
    struct BinTreeNode* left; 
    // 指向当前结点左孩⼦  
    struct BinTreeNode* right; 
    // 指向当前结点右孩⼦  
    BTDataType data;
    //当前结点存储数据   
}BTNode;

回顾⼆叉树的概念,⼆叉树分为空树和⾮空⼆叉树,⾮空⼆叉树由根结点、根结点的左⼦树、根结点的右⼦树组成的

根结点的左⼦树和右⼦树分别⼜是由⼦树结点、⼦树结点的左⼦树、⼦树结点的右⼦树组成的,因此⼆叉树定义是递归式的,后序链式⼆叉树的操作中基本都是按照该概念实现的。

4.1 前中后序遍历

⼆叉树的操作离不开树的遍历,我们先来看看⼆叉树的遍历有哪些⽅式

4.1.1 遍历规则

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

1)前序遍历(PreorderTraversal亦称先序遍历):访问根结点的操作发⽣在遍历其左右⼦树之前

访问顺序为:根结点、左⼦树、右⼦树 (根左右)

2)中序遍历(InorderTraversal):访问根结点的操作发⽣在遍历其左右⼦树之中(间)

访问顺序为:左⼦树、根结点、右⼦树 (左根右)

3)后序遍历(PostorderTraversal):访问根结点的操作发⽣在遍历其左右⼦树之后 (左右根)

访问顺序为:左⼦树、右⼦树、根结点

4)层序遍历:按照层次依次遍历 (从上到下,从左到右)

例子:

4.1.2 代码实现

c 复制代码
//Tree.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

//前序遍历函数
void PreOrder(BTNode* root);
void InOrder(BTNode* root);
void PostOrder(BTNode* root)
c 复制代码
//前序遍历---根左右
void PreOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;//只能用return
    }
    printf("%c ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}
//
void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    } InOrder(root->left);
    printf("%c ", root->data);
    InOrder(root->right);
}
void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    } 
    InOrder(root->left);
    InOrder(root->right);
    printf("%c ", root->data);
}
c 复制代码
#include"Tree.h"
BTNode* buyNode(char x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}
void test01()
{
	BTNode* nodeA = buyNode('A');
	BTNode* nodeB = buyNode('B');
	BTNode* nodeC = buyNode('C');
	BTNode* nodeD = buyNode('D');
	BTNode* nodeE = buyNode('E');
	BTNode* nodeF = buyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	//PreOrder(nodeA);
	//InOrder(nodeA);
	PostOrder(orderA);
}
int main()
{
	test01();
	return 0;
}
c 复制代码
//test.c
#include"Tree.h"
BTNode* buyNode(char x)
{
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->left = newnode->right = NULL;
	return newnode;
}
BTNode* createTree()
{
	BTNode* nodeA = buyNode('A');
	BTNode* nodeB = buyNode('B');
	BTNode* nodeC = buyNode('C');
	BTNode* nodeD = buyNode('D');
	BTNode* nodeE = buyNode('E');
	BTNode* nodeF = buyNode('F');

	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}
void test01()
{
	BTNode* root=createTree();

	//PreOrder(root);
	//InOrder(root);
	//PostOrder(root);

	//方法一
	//printf("%d\n", BinaryTreeSize(root));
	//printf("%d\n", BinaryTreeSize(root));
	//方法二
	//int size = 0;
	//BinaryTreeSize(root, &size);
	//printf("%d\n", size);
	//size = 0;
	//BinaryTreeSize(root, &size);
	//printf("%d\n", size);

	//方法三
	printf("size:%d\n", BinaryTreeSize(root));
	printf("size:%d\n", BinaryTreeSize(root));

	printf("leaf size:%d\n", BinaryTreeLeafSize(root));
	printf("K level size:%d\n", BinaryTreeLevelKSize(root, 3));
	printf("Tree Depth:%d\n", BinaryTreeDepth(root));
	BTNode* find=BinaryTreeFind(root, 'E');
	if (find)
		printf("找到了\n");
	else
		printf("未找到\n");
	BinaryTreeDestroy(&root);
}
int main()
{
	test01();
	return 0;
}

4.2 结点个数以及⾼度等

c 复制代码
//Tree.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

//前序遍历函数
void PreOrder(BTNode* root);
//中序遍历函数
void InOrder(BTNode* root);
//后序遍历函数
void PostOrder(BTNode* root);

// ⼆叉树结点个数  
int BinaryTreeSize(BTNode* root);
//void BinaryTreeSize(BTNode* root, int* psize);

// ⼆叉树叶⼦结点个数(叶子结点没有左右孩子,即度为零)  
int BinaryTreeLeafSize(BTNode* root);
// ⼆叉树第k层结点个数  
int BinaryTreeLevelKSize(BTNode* root, int k);
//⼆叉树的深度/⾼度 
int BinaryTreeDepth(BTNode* root);
// ⼆叉树查找值为x的结点  
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// ⼆叉树销毁 
void BinaryTreeDestroy(BTNode** root);
c 复制代码
//Tree.c
#include"Tree.h"

//前序遍历---根左右
void PreOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;//只能用return
    }
    printf("%c ", root->data);
    PreOrder(root->left);
    PreOrder(root->right);
}
//左根右
void InOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }
    InOrder(root->left);
    printf("%c ", root->data);
    InOrder(root->right);
}
//左右根
void PostOrder(BTNode* root)
{
    if (root == NULL)
    {
        printf("NULL ");
        return;
    }
    InOrder(root->left);
    InOrder(root->right);
    printf("%c ", root->data);
}


//int size = 0;
// ⼆叉树结点个数  
//int binarytreesize(BTNode* root)
//{
//    if (root == NULL)
//    {
//        return 0;
//    }
//    size ++;
//    BinaryTreeSize(root->left);
//    BinaryTreeSize(root->right);
//    return size;
//}
// 方法二太麻烦了,需要改造函数定义
//void BinaryTreeSize(BTNode* root, int* psize)
//{
//    if (root == NULL)
//    {
//        return 0;
//    }
//    (*psize)++;
//    BinaryTreeSize(root->left, psize);
//    BinaryTreeSize(root->right, psize);
//}

//方法三:结点总数=1+左子树结点个数+右子树结点个数
int BinaryTreeSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

// ⼆叉树叶⼦结点个数=左子树结点个数+右子树叶子结点个数  
int BinaryTreeLeafSize(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    if (root->left == NULL && root->right == NULL)
    {
        return 1;
    }
    return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}
// ⼆叉树第k层结点个数 =左子树结点个数+右子树结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    if (root == NULL)
    {
        return 0;
    }
    if (k == 1)
    {
        return 1;
    }
    return BinaryTreeLevelKSize(root->left, k-1) + BinaryTreeLevelKSize(root->right, k-1);
}
//⼆叉树的深度/⾼度=根节点+max(左子树+右子树) 
int BinaryTreeDepth(BTNode* root)
{
    if (root == NULL)
    {
        return 0;
    }
    int leftDep = BinaryTreeDepth(root->left);
    int rightDep = BinaryTreeDepth(root->right);
    return 1 + (leftDep> rightDep ?leftDep: rightDep);
}
// ⼆叉树查找值为x的结点  
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    if (root == NULL)
    {
        return NULL;
    }
    if (root->data == x)
    {
        return root;
    }
    BTNode* leftFind = BinaryTreeFind(root->left, x);
    if (leftFind)
    {
        return leftFind;
    }
    BTNode* rightFind = BinaryTreeFind(root->right, x);
    if (rightFind)
    {
        return rightFind;
    }
    return NULL;
}
// ⼆叉树销毁 root就是地址
//用后序遍历思想
void BinaryTreeDestroy(BTNode** root)
{
    if (*root == NULL)
    {
        return;
    }
    BinaryTreeDestroy(&((*root)->left));
    BinaryTreeDestroy(&((*root)->right));
    free(*root);
    *root = NULL;
}

4.3 层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对⼆叉树进⾏层序遍历。

设⼆叉树的根结点所在层数为1,层序遍历就是从所在⼆叉树的根结点出发,⾸先访问第⼀层的树根结点,然后从左到右访问第2层上的结点,接着是第三层的结点,以此类推,⾃上⽽下,⾃左⾄右逐层访问树的结点的过程就是层序遍历

实现层序遍历需要额外借助数据结构:队列

c 复制代码
// 层序遍历--借助队列 
void LevelOrder(BTNode* root) 
{ 
	Queue q; 
    QueueInit(&q); 
    QueuePush(&q, root); 
    while (!QueueEmpty(&q)) 
    { 
        BTNode* top = QueueFront(&q); 
        printf("%c ", top->data); 
        QueuePop(&q); 
        if (top->left)  
        { 
            QueuePush(&q, top->left); 
        } 
        if (top->right)  
        { 
            QueuePush(&q, top->right); 
        } 
    } 
    QueueDesTroy(&q); 
}

4.4 判断是否为完全⼆叉树

判断条件:

1.判断每层结点

2.叶子结点是否从左到右依次排列
思路:根结点先入队列,保证队列不为空

循环判断队列是否为空,不为空取队头,出队头

将队头结点的左右孩子都入队列

非完全二叉树:取到空的队头,跳出循环,此时队列中剩下了空结点和非空结点

完全二叉树:取到空的队头,跳出循环,此时队列中剩下了空结点

c 复制代码
// 判断⼆叉树是否是完全⼆叉树 
bool BinaryTreeComplete(BTNode* root)  
{ 
    Queue q; 
    QueueInit(&q); 
    QueuePush(&q, root); 
    while (!QueueEmpty(&q)) 
    { 
        BTNode* top = QueueFront(&q); 
        QueuePop(&q); 
        if (top == NULL)  
        { 
            break; 
        } 
        QueuePush(&q, top->left); 
        QueuePush(&q, top->right); 
    } 
    while (!QueueEmpty(&q)) 
    { 
        BTNode* top = QueueFront(&q); 
        QueuePop(&q); 
        if (top != NULL)  
        { 
            QueueDesTroy(&q); 
            return false; 
        } 
    } 
    QueueDesTroy(&q); 
    return true; 
} 

下一篇更新二叉树的一些算法题和选择题

相关推荐
智航GIS2 小时前
3.2 列表(List)
数据结构·windows·list
蒙奇D索大2 小时前
【数据结构】排序算法精讲 | 插入排序全解:稳定性、复杂度与实战代码剖析
数据结构·算法·排序算法
爱学大树锯2 小时前
78 题(最长公共前缀)
数据结构
youngee112 小时前
hot100-51搜索二维矩阵
数据结构·算法·矩阵
客梦3 小时前
数据结构--哈夫曼编码
数据结构·笔记
sin_hielo3 小时前
leetcode 3075(排序+贪心)
数据结构·算法·leetcode
Bdygsl3 小时前
数据结构 —— 链表
数据结构·链表
H_BB3 小时前
LRU缓存
数据结构·c++·算法·缓存
历程里程碑5 小时前
LeetCode热题11:盛水容器双指针妙解
c语言·数据结构·c++·经验分享·算法·leetcode·职场和发展