本篇将较为详细的介绍二叉树的相关知识,以及二叉树的实现。对于二叉树的相关知识,本篇介绍了其概念、特殊的二叉树、性质还有存储结构。
接着对于实现二叉树的每个函数都有其思路讲解,主要的函数分为:遍历:前中后序遍历;结点个数:二叉树总结点个数、叶子结点个数、第k层结点个数、二叉树的高度;建立销毁二叉树:前序遍历建立二叉树与销毁二叉树;层序遍历与判断是否为完全二叉树;在二叉树中查找值为x的结点。
然后测试了所有的代码,其中使用到的队列代码本篇就没有讲解,若详细查看队列代码可去主页查找对应的文章。
目录如下:
目录
[1. 二叉树的概念及结构](#1. 二叉树的概念及结构)
[1.1 概念](#1.1 概念) [1.2 特殊的二叉树](#1.2 特殊的二叉树) [1.3 二叉树的性质](#1.3 二叉树的性质) [1.4 二叉树的存储结构](#1.4 二叉树的存储结构)
[2. 二叉树的实现](#2. 二叉树的实现)
[2.1 二叉树的遍历](#2.1 二叉树的遍历) [2.2 计算结点个数](#2.2 计算结点个数) [2.3 通过前序遍历建立二叉树、销毁二叉树](#2.3 通过前序遍历建立二叉树、销毁二叉树) [2.4 层序遍历与判断二叉树是否为完全二叉树](#2.4 层序遍历与判断二叉树是否为完全二叉树) [2.5 二叉树查找值为x的结点。](#2.5 二叉树查找值为x的结点。)
[3. 所有代码](#3. 所有代码)
[3.1 All.h](#3.1 All.h) [3.2 Queue.c](#3.2 Queue.c) [3.3 BinaryTree.c](#3.3 BinaryTree.c) [3.4 test.c](#3.4 test.c) [3.5 测试结果](#3.5 测试结果)
1. 二叉树的概念及结构
1.1 概念
一颗二叉树是结点的一个有限集合,该集合:或者为空(空二叉树) 、或一个根节点加上两颗别称为左子树和右子树的二叉树组成。
从上图中可以看出:
1.二叉树中不存在度大于2的结点;
2.二叉树的子树存在左右之分,次序不能颠倒,因此二叉树是有序树。
注意:对于任意的二叉树都是由以下几种情况复合而成:
1.2 特殊的二叉树
特殊的二叉树包括两种:满二叉树、完全二叉树。
满二叉树:一个二叉树,若每一层的节点数都达到最大值,则这个二叉树就为满二叉树。也就是说,若一个二叉树的层数为 k ,且结点总数为 2^k - 1 ,则它就是满二叉树。
完全二叉树 :对于深度为 k 的,有 n 个结点的二叉树,当且仅当其中每一个结点都与深度为 k 的满二叉树中编号从 1 到 n 的结点一一对应时称之为完全二叉树。满二叉树是一种特殊的完全二叉树,完全二叉树是一种效率很高的数据结构,完全二叉树是由满二叉树引出来的。
1.3 二叉树的性质
1.若规定根节点的层数为1,则一颗非空二叉树的第 i 层上最多有2^( i - 1 )个结点。
2.若规定根节点的层数为1,则深度为 h 的二叉树的最大节点数为 2^h - 1。
3.对任意一棵二叉树,若度为0的叶子节点个数为n0,度为2的分支结点个数为n2,则有n0 = n2 + 1。
4.若规定根节点的层数为1,若具有n个结点的满二叉树的深度:。
5.对于具有n个结点的完全二叉树,若按照从上至下从左至右的数组顺序对所有结点从0开始编号,则对于序号为i的结点:
a.若 i > 0, i 位置结点的双亲结点序号:( i - 1 ) / 2;i = 0时,表示为根节点编号,无双亲结点。
b.若 2 * i + 1 < n, 左孩子序号:2 * i + 1,若2 * i + 1 >= n 表示无左孩子。
c.若 2 * i + 2 < n, 右孩子序号:2 * i + 2,若2 * i + 2 >= n 表示无右孩子。
1.4 二叉树的存储结构
二叉树一般可以使用两种结构存储,一种顺序存储,一种链式存储。本篇主讲链式存储。
1.顺序存储:顺序结构存储就是使用数组来存储 ,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费 。而现实中使用中只有堆才会使用数组来存储。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
2.链式存储:二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们所介绍的主要为二叉链。
2. 二叉树的实现
接下来将实现二叉树,其中主要实现的为链式二叉树,其中包括几个部分:
1.二叉树的遍历:二叉树的前序、中序、后序遍历;
2.计算结点个数:计算二叉树结点个数、叶子结点个数、第k层结点的个数和二叉树的高度;
3.通过前序遍历构建二叉树、二叉树的销毁;
4.层序遍历二叉树、判断二叉树是否为完全二叉树;
5.二叉树查找值为x的结点。
主要就为以上几个部分,其中需要使用到的其他数据结构为:队列。
2.1 二叉树的遍历
对于二叉树的前序、中序和后序遍历本文使用递归遍历;对于层序遍历就使用非递归遍历,首先介绍的为前序、中序和后序遍历:
对于递归遍历,主要思路为:递归分治,将问题分为当前问题和子问题,以及递归结束的调节。
以前序遍历为主:前序遍历的主要关键为,若当前结点不为NULL,我们就将其遍历,然后接着遍历左子树、右子树。若当前结点为NULL,我们就返回当前函数,此次递归结束。中序就为:先遍历左子树,然后遍历根节点、最后遍历右子树;后序为先遍历左子树,然后遍历右子树,最后遍历根节点。代码如下:
cpp// 二叉树前序遍历 void BinaryTreePrevOrder(BTNode* root) { if (root) { printf("%c ", root->data); BinaryTreePrevOrder(root->left); BinaryTreePrevOrder(root->right); } } // 二叉树中序遍历 void BinaryTreeInOrder(BTNode* root) { if (root) { BinaryTreeInOrder(root->left); printf("%c ", root->data); BinaryTreeInOrder(root->right); } } // 二叉树后序遍历 void BinaryTreePostOrder(BTNode* root) { if (root) { BinaryTreePostOrder(root->left); BinaryTreePostOrder(root->right); printf("%c ", root->data); } }
2.2 计算结点个数
接下来将用递归分别计算二叉树的结点个数、叶子结点个数、第k层结点的个数以及计算二叉树的高度;先以计算二叉树的个数为例:我们要计算二叉树的结点个数,可以将其分为:左子树结点个数+右子树结点个数+1(根节点个数),其中的核心思想就为此,若遇到空结点则返回0,代码具体实现如下:
cpp// 二叉树节点个数 int BinaryTreeSize(BTNode* root) { return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; }
计算叶子结点个数:我们首先需要找到叶子结点的特点,左右子树为NULL,所以我们可以将此递归分为:遇到叶子结点返回1、左子树叶子结点+右子树叶子结点,当访问到空结点时即返回0,具体代码实现如下:
cpp// 二叉树叶子节点个数 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层的结点个数:既然需要计算第k层的结点个数,那么我们肯定需要锁定到第k层,所以我们需要在每一次递归时都将k减一,然后在k==1时返回1。所以我们可以将这个递归分为:当k==1时返回1,递归返回左子树第k个结点+右子树第k个结点。具体实现代码如下:
cpp// 二叉树第k层节点个数 int BinaryTreeLevelKSize(BTNode* root, int k) { assert(k > 0); if (k == 1) return 1; if (root == NULL) return 0; return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1); }
计算二叉树的高度:对于计算二叉树的高度,我们首先需要知道的是左子树更高还是右子树更高,计算出子树的最高高度,然后加上根节点,所以可以将其递归分为:比较左右子树的高度,然后返回最高的高度+1(加根节点),若递归到空节点返回0,具体实现如下:
cpp// 二叉树的高度 int BinaryTreeHeight(BTNode* root) { if (root == NULL) return 0; int leftHeight = BinaryTreeHeight(root->left); int rightHeight = BinaryTreeHeight(root->right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; }
2.3 通过前序遍历建立二叉树、销毁二叉树
对于用此方法构建二叉树,我们只需要输入二叉树的前序序列,然后根据此建立出对应的二叉树。其中输入的前序序列需要包含空结点,空结点用"#"表示。
此函数传入的参数为:字符串(前序序列)、记录当前递归处理到了前序序列字符串的哪一个位置的整数的地址。函数返回根节点。
对于此函数的实现,主要还是依靠递归,将这个递归分为:若当前字符为:#,返回NULL,若不为:#,malloc一个新节点,新节点的数据为当前字符,然后将整数+1,然后递归新节点的左右孩子结点,最后返回新节点,具体代码实现如下:
cppBTNode* CreatBTNode(BTDataType x) { BTNode* newnode = (BTNode*)malloc(sizeof(BTNode)); if (newnode == NULL) { perror("newnode malloc:\n"); exit(1); } newnode->data = x; newnode->left = newnode->right = NULL; return newnode; } // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* BinaryTreeCreate(BTDataType* a, int* pi) { if (a[*pi] == '#') { (*pi)++; return NULL; } BTNode* newNode = CreatBTNode(a[*pi]); (*pi)++; newNode->left = BinaryTreeCreate(a, pi); newNode->right = BinaryTreeCreate(a, pi); return newNode; }
二叉树的销毁:二叉树的销毁仍然使用递归进行销毁处理,其主要思想和后序遍历基本一致,主要为:递归删除左子树+递归删除右子树+删除根节点,主要实现代码如下:
cpp// 二叉树销毁 void BinaryTreeDestory(BTNode** root) { // 递归销毁,先销毁左子树,后销毁右子树 if (*root == NULL) return; BTNode* tmp = *root; BinaryTreeDestory(&tmp->left); BinaryTreeDestory(&tmp->right); free(tmp); tmp = NULL; }
2.4 层序遍历与判断二叉树是否为完全二叉树
层序遍历:对于层序遍历的主要思想就是将二叉树按层次遍历,先遍历第一层根节点,然后遍历下一层,从左到右,直至遍历结束。对于此方法,我们不使用递归,我们使用队列来辅助我们遍历。
我们首先将根节点放入队列,然后开始进行遍历循环,只要队列不为空,我们就一直入循环。在循环中,我们得到排在队列的第一个元素将其访问,然后,若第一个元素的左子树不为空,则将其入队列,右子树不为空,将其入队列(孩子结点不为空就入队列),接着将第一个元素出队列,循环往复就可以将二叉树进行层序遍历,因为是将结点一层一层的加入队列,然后又出队列的。具体实现代码如下:
cpp// 层序遍历 void BinaryTreeLevelOrder(BTNode* root) { if (root == NULL) return; Queue pQ; QueueInit(&pQ); QueuePush(&pQ, root); while (!QueueEmpty(&pQ)) { BTNode* new = QueueFront(&pQ); printf("%c ", new->data); if (new->left) QueuePush(&pQ, new->left); if (new->right) QueuePush(&pQ, new->right); QueuePop(&pQ); } QueueDestory(&pQ); }
判断二叉树是否为完全二叉树:首先我们要知道完全二叉树的层序遍历为全部结点,其中不会出现空结点。所以我们可以沿用上面的思路,将结点放入队列,不过这次放入不需要判断结点是否为NULL,将所有结点都放入。然后在得到队首元素时进行判断,判断是否为NULL,若为NULL,则跳出当前循环。接着对剩余的队列元素进行遍历判断,若发现在之后的元素中遇见非空结点,说明不是完全二叉树。若队列为NULL时,还没遍历到非空结点,说明为完全二叉树。
具体实现代码如下:
cpp// 判断二叉树是否是完全二叉树 bool BinaryTreeComplete(BTNode* root) { if (root == NULL) return true; Queue pQ; QueueInit(&pQ); QueuePush(&pQ, root); while (!QueueEmpty(&pQ)) { BTNode* new = QueueFront(&pQ); //当遍历到NULL结点,跳出循环 if (new == NULL) break; QueuePush(&pQ, new->left); QueuePush(&pQ, new->right); QueuePop(&pQ); } while (!QueueEmpty(&pQ)) { BTNode* front = QueueFront(&pQ); QueuePop(&pQ); //若在之后遍历到非空结点,说明不是完全二叉树 if (front) return false; } QueueDestory(&pQ); return true; }
2.5 二叉树查找值为x的结点。
对于二叉树的查找,我们同样使用递归对二叉树进行遍历查找,其主要思想为:若当前结点为NULL,则返回NULL,若当前结点的值等于 x ,则返回当前结点,然后依次遍历查找左子树和右子树,但是在左子树和右子树的查找返回中,我们一个函数只能返回一次,不能同时递归返回左右子树,所有需要在对左子树的递归返回时加上一个判断,若左子树不为NULL,则先查找递归返回左子树,当左子树递归结束都还没找到时,自然递归返回右子树。具体实现代码如下:
cpp// 二叉树查找值为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; return BinaryTreeFind(root->right, x); }
3. 所有代码
3.1 All.h
以下为头文件:All.h的代码:
cpp#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> #include <math.h> typedef char BTDataType; typedef struct BinaryTreeNode { BTDataType data; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BTNode; typedef BTNode* QDataType; typedef struct Node { QDataType val; struct Node* next; }QNode; typedef struct Queue { int size; QNode* phead; QNode* ptail; }Queue; //队列的初始化/销毁 void QueueInit(Queue* pQ); void QueueDestory(Queue* pQ); //队列的入队/出队 void QueuePush(Queue* pQ, QDataType x); void QueuePop(Queue* pQ); //返回队首元素/队尾元素 QDataType QueueFront(Queue* pQ); QDataType QueueBack(Queue* pQ); //判断队列是否为NULL/返回队列的大小 bool QueueEmpty(Queue* pQ); int QueueSize(Queue* pQ); // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* BinaryTreeCreate(BTDataType* a, int* pi); // 二叉树销毁 void BinaryTreeDestory(BTNode** root); // 二叉树节点个数 int BinaryTreeSize(BTNode* root); // 二叉树叶子节点个数 int BinaryTreeLeafSize(BTNode* root); // 二叉树第k层节点个数 int BinaryTreeLevelKSize(BTNode* root, int k); // 二叉树查找值为x的节点 BTNode* BinaryTreeFind(BTNode* root, BTDataType x); // 二叉树前序遍历 void BinaryTreePrevOrder(BTNode* root); // 二叉树中序遍历 void BinaryTreeInOrder(BTNode* root); // 二叉树后序遍历 void BinaryTreePostOrder(BTNode* root); // 层序遍历 void BinaryTreeLevelOrder(BTNode* root); // 判断二叉树是否是完全二叉树 bool BinaryTreeComplete(BTNode* root); // 二叉树的高度 int BinaryTreeHeight(BTNode* root);
3.2 Queue.c
以下为队列部分的代码:
cpp#define _CRT_SECURE_NO_WARNINGS 1 #include "All.h" void QueueInit(Queue* pQ) { assert(pQ); pQ->phead = pQ->ptail = NULL; pQ->size = 0; } void QueueDestory(Queue* pQ) { assert(pQ); QNode* cur = pQ->phead; while (cur) { QNode* next = cur->next; free(cur); cur = next; } pQ->phead = pQ->ptail = NULL; pQ->size = 0; } void QueuePush(Queue* pQ, QDataType x) { assert(pQ); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc:"); exit(1); } newnode->val = x; newnode->next = NULL; if (pQ->phead == NULL) { pQ->phead = pQ->ptail = newnode; } else { pQ->ptail->next = newnode; pQ->ptail = newnode; } pQ->size++; } void QueuePop(Queue* pQ) { assert(pQ); assert(pQ->phead); QNode* cur = pQ->phead; pQ->phead = pQ->phead->next; free(cur); cur = NULL; pQ->size--; } QDataType QueueFront(Queue* pQ) { assert(pQ); assert(pQ->phead); return pQ->phead->val; } QDataType QueueBack(Queue* pQ) { assert(pQ); assert(pQ->phead); return pQ->ptail->val; } bool QueueEmpty(Queue* pQ) { assert(pQ); return pQ->phead == NULL; } int QueueSize(Queue* pQ) { assert(pQ); return pQ->size; }
3.3 BinaryTree.c
以下为实现二叉树的代码:
cpp#define _CRT_SECURE_NO_WARNINGS 1 #include "All.h" // 二叉树节点个数 int BinaryTreeSize(BTNode* root) { return root == NULL ? 0 : BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1; } // 二叉树叶子节点个数 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) { assert(k > 0); if (k == 1) return 1; if (root == NULL) return 0; return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1); } // 二叉树查找值为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; return BinaryTreeFind(root->right, x); } // 二叉树前序遍历 void BinaryTreePrevOrder(BTNode* root) { if (root) { printf("%c ", root->data); BinaryTreePrevOrder(root->left); BinaryTreePrevOrder(root->right); } } // 二叉树中序遍历 void BinaryTreeInOrder(BTNode* root) { if (root) { BinaryTreeInOrder(root->left); printf("%c ", root->data); BinaryTreeInOrder(root->right); } } // 二叉树后序遍历 void BinaryTreePostOrder(BTNode* root) { if (root) { BinaryTreePostOrder(root->left); BinaryTreePostOrder(root->right); printf("%c ", root->data); } } // 层序遍历 void BinaryTreeLevelOrder(BTNode* root) { if (root == NULL) return; Queue pQ; QueueInit(&pQ); QueuePush(&pQ, root); while (!QueueEmpty(&pQ)) { BTNode* new = QueueFront(&pQ); printf("%c ", new->data); if (new->left) QueuePush(&pQ, new->left); if (new->right) QueuePush(&pQ, new->right); QueuePop(&pQ); } QueueDestory(&pQ); } // 二叉树的高度 int BinaryTreeHeight(BTNode* root) { if (root == NULL) return 0; int leftHeight = BinaryTreeHeight(root->left); int rightHeight = BinaryTreeHeight(root->right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } BTNode* CreatBTNode(BTDataType x) { BTNode* newnode = (BTNode*)malloc(sizeof(BTNode)); if (newnode == NULL) { perror("newnode malloc:\n"); exit(1); } newnode->data = x; newnode->left = newnode->right = NULL; return newnode; } // 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 BTNode* BinaryTreeCreate(BTDataType* a, int* pi) { if (a[*pi] == '#') { (*pi)++; return NULL; } BTNode* newNode = CreatBTNode(a[*pi]); (*pi)++; newNode->left = BinaryTreeCreate(a, pi); newNode->right = BinaryTreeCreate(a, pi); return newNode; } // 二叉树销毁 void BinaryTreeDestory(BTNode** root) { // 递归销毁,先销毁左子树,后销毁右子树 if (*root == NULL) return; BTNode* tmp = *root; BinaryTreeDestory(&tmp->left); BinaryTreeDestory(&tmp->right); free(tmp); tmp = NULL; } // 判断二叉树是否是完全二叉树 bool BinaryTreeComplete(BTNode* root) { if (root == NULL) return true; Queue pQ; QueueInit(&pQ); QueuePush(&pQ, root); while (!QueueEmpty(&pQ)) { BTNode* new = QueueFront(&pQ); //当遍历到NULL结点,跳出循环 if (new == NULL) break; QueuePush(&pQ, new->left); QueuePush(&pQ, new->right); QueuePop(&pQ); } while (!QueueEmpty(&pQ)) { BTNode* front = QueueFront(&pQ); QueuePop(&pQ); //若在之后遍历到非空结点,说明不是完全二叉树 if (front) return false; } QueueDestory(&pQ); return true; }
3.4 test.c
以下为测试的所有代码:
cpp#define _CRT_SECURE_NO_WARNINGS 1 #include "All.h" int main() { char ch[] = "ABD##E#H##CF##G##"; int i = 0; BTNode* root = BinaryTreeCreate(ch, &i); printf("BinaryTreePrevOrder: "); BinaryTreePrevOrder(root); printf("\n"); printf("BinaryTreeInOrder: "); BinaryTreeInOrder(root); printf("\n"); printf("BinaryTreePostOrder: "); BinaryTreePostOrder(root); printf("\n"); if (BinaryTreeComplete(root)) printf("BinaryTree is Complete: true\n"); else printf("BinaryTree is Complete: false\n"); printf("BinaryTreeLevelKSize: %d\n", BinaryTreeLevelKSize(root, 3)); printf("BinaryTreeLeafSize: %d\n",BinaryTreeLeafSize(root)); printf("BinaryTreeSize: %d\n", BinaryTreeSize(root)); printf("BinaryTreeHeight: %d\n", BinaryTreeHeight(root)); BTNode* Find = BinaryTreeFind(root, 'A'); if (Find) Find->data = 'Y'; printf("BinaryTreePrevOrder: "); BinaryTreePrevOrder(root); printf("\n"); printf("BinaryTreeLevelOrder: "); BinaryTreeLevelOrder(root); BinaryTreeDestory(&root); return 0; }
3.5 测试结果
对于字符串"ABD##E#H##CF##G##"建立的树如下:
代码实现如下: