目录
[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 二叉树的性质:)
[3. 二叉树的顺序存储结构](#3. 二叉树的顺序存储结构)
[3.1 二叉树的顺序存储结构](#3.1 二叉树的顺序存储结构)
[3.2 堆的概念及其结构](#3.2 堆的概念及其结构)
[3.3 堆的实现](#3.3 堆的实现)
[4. 二叉树的链式存储](#4. 二叉树的链式存储)
[4.1 前序 ,中序 ,后序遍历](#4.1 前序 ,中序 ,后序遍历)
[4.2 层序遍历](#4.2 层序遍历)
1. 树的概念及其结构
1.1 树的概念:
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一棵非空树中:1.有且仅有一个特定的称为根(Root)的结点;2.当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2...,Tm,其中每一个集合本身又是一颗树,称为根的子树(Sub Tree),如下图所示。
注:
子树是不相交的。
除了根节点以外,每个节点有且只有一个父节点
一棵有N个节点的树有N-1条边。
1.2 树的相关概念:
a.结点的分类
结点的度:结点拥有的子树的个数。例如,A的度为6。
树的度:最大结点的度为树的度。例如,上图结点的度为6,。
叶结点/终端结点:度为0的结点。例如,结点B。
非终端结点/分支结点:度不为0的结点。例如,结点D,E....
b.结点的关系
双亲结点/父结点:若一个结点含有子结点,那么该结点就是父结点。例如,A是B的父结点。
孩子结点/子结点:结点子树的根称为该结点的孩子(Child)。例如,B是A的孩子。
兄弟结点:具有相同父结点的结点称为兄弟结点。例如,B和C是兄弟结点。
堂兄弟结点:双亲在同一层的结点称为堂兄弟结点。例如,H和I是堂兄弟结点。
结点的祖先:从根结点到该结点的分支上的所有结点,称为该结点的祖先。例如,A时所有结点的祖先
子孙:以某结点为根的子树中的任意结点都是该结点的子孙,例如,所有结点都是A的子孙。
c.树的相关概念
结点的层次:从根结点开始定义,根结点为第1层,根结点的子结点为第2层,以此类推。
树的高度或深度:树中结点的最大层次。上图,树的高度为4。
森林:由m棵互不相交的树的集合称为森林。
1.3 树的表示方法:
这里我们简单了解一下比较常用的:孩子兄弟表示法。
cpp
typedef int DataType;
struct Node
{
struct Node* LeftChild; // 指向左边第一个孩子节点
struct Node* RightBrather; // 指向右边的兄弟节点
DataType _data; // 结点中的数据域
};
1.4 树的应用:
我们在Linux系统中(操作系统的一种),使用的目录结构就是树状结构。
2. 二叉树的概念及其结构
2.1 概念:
二叉树(Binary Tree)是n(n>=0)个结点的有限集,该集合或者为空集(空二叉树),或者由一个根结点和两棵互不相交的,分别称为根结点的左子树和右子树的二叉树组成。
2.2 特点:
a. 二叉树不存在度大于2的结点。
b. 二叉树有左子树右子树之分,是有顺序的,不能颠倒,即使只有一棵子树,也要区分左子树还是右子树。
2.3 特殊二叉树:
a. 满二叉树:一个二叉树,每一层的结点数都达到最大值,则这个二叉树称为满二叉树。也就是说,如果一个满二叉树一共有h层,那么它的结点个数为 2^h-1。
b. 完全二叉树:前h-1层结点数都达到最大值,最后一层不一定是满的,但一定从左往右有序。满二叉树是一个特殊的完全二叉树。
2.4 二叉树的性质:
-
若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^( i - 1 ) 个节点。
-
若规定根节点的层数为1,则深度为h的二叉树的最大节点数是 2^( h ) - 1 。
-
对任何一棵二叉树,度为0的叶子节点个数为n0,度为2的分支节点个数为n2,则有n0 = n2 + 1。
-
若贵定根节点的层数为1,具有n个节点的满二叉树的深度,h = log₂(n + 1)。
-
对具有n个节点的完全二叉树,如果按照从上到下从左至右的数组顺序对所有节点开始编号,啧对于序号为i的节点有:
a. 若 i > 0,i位置节点的双亲序号:(i - 1) / 2 ; i =0,i为根节点编号,无双亲节点。
b. 若2 * i + 1 < n,左孩子序号: 2 * i + 1,若 2 * i + 1 >= n ,则无左孩子节点。
c. 若2 * i + 2 < n,右孩子序号: 2 * i + 2,若 2 * i + 1 >= n,则无右孩子节点。
- 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( B )
A 不存在这样的二叉树
B 200
C 198
D 199
解析:
叶子节点:度为0的节点,n0 = n2 +1- 在具有 2n 个结点的完全二叉树中,叶子结点个数为( A )
A n
B n+1
C n-1
D n/2
解析:
完全二叉树的节点度分为3种情况:度=1 or 度 =2 or 度 = 0;
2n = n0 + n1 + n2
n0 = n2 + 1 ,且 n1 = 0 或 n1 =0
2n0 + n1 = 2n 这里 n1只能为0 (偶数 + 奇数 != 偶数)
所以叶子节点个数 = n
4.一棵完全二叉树的节点数位为531个,那么这棵树的高度为( B )
A 11
B 10
C 8
D 12
解析:
满二叉树的节点个数 = 2*h -1
完全二叉树中,叶子节点个数不确定,区间为:[ 2^( h - 1 ) + 1 , 2^( h ) -1 ]
将选项代入,得出高度为10
5.在一颗度为3的树中,度为3的结点有2个,度为2的结点有1个,度为1的结点有2个,则叶子结点有( C)个
A.4B.5
C.6
D.7
解析:
度为i的节点为ni,树的节点个数为n,则 n = n0 + n1 + n2 + n3;
有n个节点的树的总边数为 : n-1 条。
根据度的定义,总边数 与 度 的关系: n -1 = 0 * n0 + 1 * n1 + 2 * n2 + 3 * n3
联立方程可得,n0 = n2 +2 *n3 +1 , n0 = 6
3. 二叉树的顺序存储结构
3.1 二叉树的顺序存储结构
普通的二叉树不适合用顺序存储结构,会造成大量空间浪费,但完全二叉树适合用顺序存储结构,现实中我们常把堆(一种二叉树)使用顺序结构存储。
3.2 堆的概念及其结构
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
3.3 堆的实现
cpp
//Heap.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
cpp
#include "Heap.h"
void Swap(HPDataType* x1, HPDataType* x2)
{
HPDataType x = *x1;
*x1 = *x2;
*x2 = x;
}
void AdjustDown(HPDataType* a, int n, int root)
{
int parent = root;
int child = parent*2+1;
while (child < n)
{
// 选左右孩纸中大的一个
if (child+1 < n
&& a[child+1] > a[child])
{
++child;
}
//如果孩子大于父亲,进行调整交换
if(a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
void AdjustUp(HPDataType* a, int n, int child)
{
int parent;
assert(a);
parent = (child-1)/2;
//while (parent >= 0)
while (child > 0)
{
//如果孩子大于父亲,进行交换
if (a[child] > a[parent])
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child-1)/2;
}
else
{
break;
}
}
}
void HeapInit(Heap* hp, HPDataType* a, int n)
{
int i;
assert(hp && a);
hp->_a = (HPDataType*)malloc(sizeof(HPDataType)*n);
hp->_size = n;
hp->_capacity = n;
for (i = 0; i < n; ++i)
{
hp->_a[i] = a[i];
}
// 建堆: 从最后一个非叶子节点开始进行调整
// 最后一个非叶子节点,按照规则: (最后一个位置索引 - 1) / 2
// 最后一个位置索引: n - 1
// 故最后一个非叶子节点位置: (n - 2) / 2
for(i = (n-2)/2; i >= 0; --i)
{
AdjustDown(hp->_a, hp->_size, i);
}
}
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->_a);
hp->_a = NULL;
hp->_size = hp->_capacity = 0;
}
void HeapPush(Heap* hp, HPDataType x)
{
assert(hp);
//检查容量
if (hp->_size == hp->_capacity)
{
hp->_capacity *= 2;
hp->_a = (HPDataType*)realloc(hp->_a, sizeof(HPDataType)*hp->_capacity);
}
//尾插
hp->_a[hp->_size] = x;
hp->_size++;
//向上调整
AdjustUp(hp->_a, hp->_size, hp->_size-1);
}
void HeapPop(Heap* hp)
{
assert(hp);
//交换
Swap(&hp->_a[0], &hp->_a[hp->_size-1]);
hp->_size--;
//向下调整
AdjustDown(hp->_a, hp->_size, 0);
}
HPDataType HeapTop(Heap* hp)
{
assert(hp);
return hp->_a[0];
}
int HeapSize(Heap* hp)
{
return hp->_size;
}
int HeapEmpty(Heap* hp)
{
return hp->_size == 0 ? 0 : 1;
}
void HeapPrint(Heap* hp)
{
int i;
for (i = 0; i < hp->_size; ++i)
{
printf("%d ", hp->_a[i]);
}
printf("\n");
}
4. 二叉树的链式存储
先来简单复习一下二叉树的概念,二叉树是:
- 空树
2.非空:根节点,左子树,右子树组成
从概念中可以看出,二叉树的定义是递归式的,因此后续基本操作都是按照该概念实现的。
4.1 前序 ,中序 ,后序遍历
二叉树遍历是按照某种特定的规则,依次对二叉树的节点进行相应的操作,并且每个节点只操作1次。
按照规则,二叉树的遍历有:前序/中序/后序的递归结构遍历:
前序遍历(Preorder Traversal):访问根节点的操作发生在遍历其左右子树之前。
后序遍历(Postorder Traversal):访问根节点发生在遍历其左右子树之后。
中序遍历(Inorder Traversal):访问根节点的操作发生在遍历其左右子树之间。
4.2 层序遍历
层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
层序遍历中,我们使用队列来实现,但因为C语言的局限性,需要自己创建轮子,所以实现起来比较复杂,这里如果你对队列不太熟悉,可以参考下面这篇文章,帮助你更好的理解。
数据结构入门------------栈和队列(C语言/零基础/小白/新手+模拟实现+例题讲解)
cpp
//Tree.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef char BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, 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);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);
cpp
#include "BTree.h"
#include "queue.h" //参考之前的代码
#include "stack.h"
BTNode *BinaryTreeCreate(BTDataType * src, int n, int* pi)
{
if (*pi >= n || src[*pi] == '#')
{
(*pi)++;
return NULL;
}
BTNode * cur = (BTNode *)malloc(sizeof(BTNode));
cur->_data = src[*pi];
(*pi)++;
cur->_left = BinaryTreeCreate(src, n, pi);
cur->_right = BinaryTreeCreate(src, n, pi);
return cur;
}
void BinaryTreePrevOrder(BTNode* root)
{
if (root)
{
putchar(root->_data);
BinaryTreePrevOrder(root->_left);
BinaryTreePrevOrder(root->_right);
}
}
void BinaryTreeInOrder(BTNode* root)
{
if (root)
{
BinaryTreeInOrder(root->_left);
putchar(root->_data);
BinaryTreeInOrder(root->_right);
}
}
void BinaryTreePostOrder(BTNode* root)
{
if (root)
{
BinaryTreePostOrder(root->_left);
BinaryTreePostOrder(root->_right);
putchar(root->_data);
}
}
void BinaryTreeDestory(BTNode** root)
{
if (*root)
{
BinaryTreeDestory(&(*root)->_left);
BinaryTreeDestory(&(*root)->_right);
free(*root);
*root = NULL;
}
}
void BinaryTreeLevelOrder(BTNode* root)
{
Queue qu;
BTNode * cur;
QueueInit(&qu);
QueuePush(&qu, root);
while (!QueueIsEmpty(&qu))
{
cur = QueueTop(&qu);
putchar(cur->_data);
if (cur->_left)
{
QueuePush(&qu, cur->_left);
}
if (cur->_right)
{
QueuePush(&qu, cur->_right);
}
QueuePop(&qu);
}
QueueDestory(&qu);
}
int BinaryTreeComplete(BTNode* root)
{
Queue qu;
BTNode * cur;
int tag = 0;
QueueInit(&qu);
QueuePush(&qu, root);
while (!QueueIsEmpty(&qu))
{
cur = QueueTop(&qu);
putchar(cur->_data);
if (cur->_right && !cur->_left)
{
return 0;
}
if (tag && (cur->_right || cur->_left))
{
return 0;
}
if (cur->_left)
{
QueuePush(&qu, cur->_left);
}
if (cur->_right)
{
QueuePush(&qu, cur->_right);
}
else
{
tag = 1;
}
QueuePop(&qu);
}
QueueDestory(&qu);
return 1;
}