树是由n个节点(或元素)组成的有限集合。
(1)如不为空树,有且只有一个根节点。
(2)左右子树递归定义。
(1)树形表示法
(2)文氏图表示法
(3)凹入表示法
(4)括号表示法
(1)节点的度:树中某个节点的子树的个数。
(2)树的度:树中所有节点的度中的最大值称为树的度。
(3)分支节点:树中度不为零的节点(也叫非终端节点)。
(4)叶子节点:树中度为零的节点。
(5)路径:两节点之间前后位置的抽象表示。
(6)路径长度:该路径所通过的节点数目减一。
(7)孩子节点:每个节点的后继节点称为该节点的孩子节点。
(8)双亲节点:一个节点的前驱节点称为该节点的双亲节点(唯一)。
(9)兄弟节点:具有同一双亲节点的孩子节点互为兄弟节点。
(10)节点层次:从根节点开始定义,根为第一层,其孩子为第二层,以此类推。
(11)树的高度:树中节点的最大层次称为树的高度(或树的深度)。
(12)森林:n个互不相交的树的集合。
性质1:树中的节点数等于所有节点的度数之和加1。
原因:在一棵树中除根节点外,每个节点有且仅有一个前驱节点。
性质2:度为m的树中第i层上最多有m^(i-1)个节点。
性质3:高度为h的m叉树至多有(m^h-1)/(m-1)个结点。
性质4:具有n个结点的m叉树的最小高度为⌈logm(n*(m-1)+1)⌉
(1)查找
(2)插入
(3)遍历
遍历的顺序都是从左到右,区别在于对根的访问次序。
先序遍历:根—>左—>右
中序遍历:左—>根—>右
后续遍历:左—>右—>根
层次遍历:从根节点开始按从上到下,从左到右的次序访问树中的每一个节点。
二叉树(binary tree)是有限的节点集合,这个集合或者为空,或者由一个根节点和两棵互不相交的称为左子树和右子树的二叉树组成。
满二叉树:所有分支节点都有左孩子和右孩子,并且所有叶子节点都集中在二叉树的最下一层。
完全二叉树:最下一层叶子节点依次排列在该层靠左的位置上。
性质1:非空二叉树上的叶子结点数等于双分支节点数加1。
说明:叶子节点数n0,单分支节点数n1,双分子节点数n2,则有n0 = n2+1。(推导过程略)
性质2:非空二叉树的第i层上最多有2^(i-1)个节点。
性质3:高度为h的二叉树最多有2^h-1个节点。
性质4:具有n个结点的完全二叉树的深度为|log(2^n)+1|或者|log2(n)|+1
一、一棵树转换为二叉树
(1)将树的根节点直接作为二叉树的根节点。
(2)将树的根节点的第一个子节点作为二叉树根节点的左指针,若该子节点存在兄弟节点,则将该子节点的第一个兄弟节点(方向从左往右)作为该子节点的右指针。
(3)树中的剩余节点按照上一步的方式(左孩子,右兄弟),依序添加到二叉树中。直到树中所有的节点都在二叉树中。
口诀:
将 节点的孩子 放在左子树;
将 节点的兄弟 放在右子树。
二、森林转换为二叉树
(1)首先将森林中所有的普通树各自转化为二叉树;
(2)将森林中第一棵树的树根作为整个森林的树根,其他树的根节点看作是第一棵树根节点的兄弟节点,采用孩子兄弟表示法将所有树进行连接;
四、二叉树还原为森林
二叉树也有顺序存储和链式存储结构。
二叉树的顺序存储结构就是用一组地址连续的存储单元来存放二叉树的数据元素。
顺序存储的类型声明:
#define MaxSize 100
typedef ElemType SqBinTree[MaxSize];
二叉树的链式存储结构是指用一个链表来存储一棵二叉树,二叉树中的每一个节点用链表中的一个节点来存储。
二叉树存储节点类型的声明:
typedef struct node
{
ElemType data; //数据域
struct node *lchild; //指向左孩子指针
struct node *rchild; //指向右孩子指针
}BTNode,*BitTree;
算法思路:为T分配结点空间–生成根节点–递归创建左子树–递归创建右子树
递归创建二叉树[完整代码]
#include
#include
typedef struct BitNode { //定义二叉树存储结构
char data;
struct BitNode* lchild, * rchild;
}BitNode, * BitTree;
//函数声明
void PreOrder(BitTree T);
BitTree CreateBitTree();
int main()
{
BitTree T = CreateBitTree();
PreOrder(T);
return 0;
}
BitTree CreateBitTree() {
BitTree T = NULL;
char ch;
ch = getchar();
if (ch == '#') {
T = NULL;
return T;
}
else
{
T = (BitNode*)malloc(sizeof(BitNode));
if (T == NULL)
return NULL;
T->data = ch;
T->lchild = CreateBitTree();
T->rchild = CreateBitTree();
return T;
}
}
void PreOrder(BitTree T) {
if (T != NULL) {
printf("%c", T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
按照前序输入:
输入:ABD##E##C#F##
输出:ABDECF
void DestroyBTree(BitTree T)
{
if(T != NULL)
{
DestroyBTree(T->lchild); //销毁左子树
DestroyBTree(T->rchild); //销毁右子树
}
}
在二叉树中查找值为x的节点,找到后返回其地址,否则返回NULL。
BitTree FindNode(BitTree T, char x) {
if (!T)
return NULL; //树为空返回NULL
if (T->data == x)
return T; //找到x返回此时的地址
if (FindNode(T->lchild, x))
return FindNode(T->lchild, x); //在左子树中查找
else
return FindNode(T->rchild, x); //在右子树中查找
}
int BTHeight(BitTree T)
{
int lchild, rchild;
if (T == NULL)
return (0);
else
{
lchild = BTHeight(T->lchild);
rchild = BTHeight(T->rchild);
return (lchild > rchild) ? (lchild + 1) : (rchild + 1);
}
}
递归算法(根—>左—>右)
(1)访问根节点
(2)先序遍历左子树
(3)先序遍历右子树
void PreOrder(BitTree T) {
if (T != NULL) {
printf("%c", T->data);
PreOrder(T->lchild);
PreOrder(T->rchild);
}
}
非递归算法
非递归算法需要用到顺序栈辅助
typedef struct Stack
{
BitTree data[MaxSize];
int top;
}Stack,*SqStack;
核心代码:
void PreOrder1(BitTree T,Stack *st)
{
BitTree p = T; //复制一份二叉树,不对原来的树产生影响
while (p || !StackEmpty(st)) //树不为空或栈不为空时进入循环
{
if (p)
{
printf("%c ", p->data);
push(st, p); //访问到节点时入栈,并尝试访问该节点的左孩子
p = p->lchild;
}
else
{
p = pop(st,p); //若p为NULL或者p->lchild为NULL,栈中元素出栈,并尝试访问右孩子
p = p->rchild;
}
}
}
完整代码:
#include
#include
#define MaxSize 100
//二叉树存储结构
typedef struct BitNode {
char data;
struct BitNode* lchild, * rchild;
}BitNode, * BitTree;
栈存储结构
typedef struct Stack
{
BitNode* data[MaxSize];
int top;
}Stack,*SqStack;
//函数声明
BitTree CreateBitTree(); //二叉树创建
void PreOrder1(BitTree T,Stack st); //前序遍历非递归函数
BitTree pop(SqStack s, BitTree x); //出栈
bool push(SqStack s, BitTree x); //入栈
bool StackEmpty(SqStack s); //判断栈是否为空
void InitStack(SqStack s); //初始化栈
int main()
{
BitTree T = CreateBitTree(); //先序遍历的形式创建一棵二叉树
SqStack st = (Stack*)malloc(sizeof(Stack)); //开辟栈空间
InitStack(st); //栈初始化
PreOrder1(T,st);
return 0;
}
BitTree CreateBitTree() {
BitTree T = NULL;
char ch;
ch = getchar();
if (ch == '#') {
T = NULL;
return T;
}
else
{
T = (BitNode*)malloc(sizeof(BitNode));
if (T == NULL)
return NULL;
T->data = ch;
T->lchild = CreateBitTree();
T->rchild = CreateBitTree();
return T;
}
}
void PreOrder1(BitTree T,Stack *st)
{
BitTree p = T; //复制一份二叉树,不对原来的树产生影响
while (p || !StackEmpty(st)) //树不为空或栈不为空时进入循环
{
if (p)
{
printf("%c ", p->data);
push(st, p); //访问到节点时入栈,并尝试访问该节点的左孩子
p = p->lchild;
}
else
{
p = pop(st,p); //若p为NULL或者p->lchild为NULL,栈中元素出栈,并尝试访问右孩子
p = p->rchild;
}
}
}
void InitStack(SqStack s) {
s->top = -1;
}
bool StackEmpty(SqStack s) {
return s->top == -1;
}
//进栈
bool push(SqStack s,BitTree x) {
if (s->top == MaxSize - 1)
return false;
s->data[++s->top] = x;
return true;
}
//出栈
BitTree pop(SqStack s, BitTree x) {
if (s->top == -1)
return NULL;
x = s->data[s->top--];
return x;
}
输入:ABD##E##C#E##
输出:A B D E C E
递归算法(左—>根—>右)
(1)中序遍历左子树
(2)访问根节点
(3)中序遍历右子树
void InOrder(BitTree T) {
if (T != NULL) {
InOrder(T->lchild);
printf("%c", T->data);
InOrder(T->rchild);
}
}
非递归算法:参照前序遍历的非递归算法
递归算法(左—>右—>根)
(1)后序遍历左子树
(2)后续遍历右子树
(3)访问根节点
void PostOrder(BitTree T) {
if (T != NULL) {
PostOrder(T->lchild);
PostOrder(T->rchild);
printf("%c", T->data);
}
}
非递归算法:参照前序遍历的非递归算法
层次遍历是非递归的,用于一层一层访问二叉树中的所有节点。层次遍历采用环形队列来实现。
(1)访问根节点
(2)从左到右访问第二层节点
(3)从左到右访问第三层节点,依次类推。
环形队列存储结构:
typedef struct Queue
{
BitNode* data[MaxSize];
int front, rear;
}Queue,*SqQueue;
核心代码:
void LevelOrder(BitTree T,SqQueue q)
{
BitTree p;
enQueue(q, T);
while (!QueueEmpty(q))
{
p = deQueue(q); //存储临时节点,出队列
printf("%c ", p->data);
if (p->lchild != NULL) //左孩子不为空,左孩子进队列
enQueue(q, p->lchild);
if (p->rchild != NULL) //右孩子不为空,右孩子进队列
enQueue(q, p->rchild);
}
}
完整代码:
#include
#include
#define MaxSize 100
//二叉树的存储结构
typedef struct BitNode {
char data;
struct BitNode* lchild, * rchild;
}BitNode, * BitTree;
//队列的存储结构
typedef struct Queue
{
BitNode* data[MaxSize];
int front, rear;
}Queue,*SqQueue;
//函数声明
BitTree CreateBitTree();
void LevelOrder(BitTree T, Queue* q);
void InitQueue(SqQueue q);
bool QueueEmpty(SqQueue q);
bool enQueue(SqQueue q, BitTree e);
BitTree deQueue(SqQueue q);
BitTree CreateBitTree() {
BitTree T = NULL;
char ch;
ch = getchar();
if (ch == '#') {
T = NULL;
return T;
}
else
{
T = (BitNode*)malloc(sizeof(BitNode));
if (T == NULL)
return NULL;
T->data = ch;
T->lchild = CreateBitTree();
T->rchild = CreateBitTree();
return T;
}
}
//队列初始化
void InitQueue(SqQueue q)
{
q = (Queue*)malloc(sizeof(Queue));
q->front = q->rear = 0;
}
//队列判空
bool QueueEmpty(SqQueue q)
{
return (q->front == q->rear);
}
//进队列
bool enQueue(SqQueue q, BitTree e)
{
if ((q->rear + 1) % MaxSize == q->front)
return false;
q->rear = (q->rear + 1) % MaxSize;
q->data[q->rear] = e;
return true;
}
//出队列
BitTree deQueue(SqQueue q)
{
if (q->front == q->rear)
return NULL;
q->front = (q->front + 1) % MaxSize;
return q->data[q->front];
}
void LevelOrder(BitTree T,SqQueue q)
{
BitTree p;
enQueue(q, T);
while (!QueueEmpty(q))
{
p = deQueue(q); //出队列
printf("%c ", p->data);
if (p->lchild != NULL) //左孩子不为空,左孩子进队列
{
enQueue(q, p->lchild);
}
if (p->rchild != NULL) //右孩子不为空,右孩子进队列
{
enQueue(q, p->rchild);
}
}
}
int main()
{
BitTree T = CreateBitTree(); //创建二叉树
SqQueue q = (Queue*)malloc(sizeof(Queue)); //开辟循环队列空间
InitQueue(q); //初始化循环队列
LevelOrder(T, q);
return 0;
}
当我们使用二叉链存储结构时,每个节点有两个指针域,则n个节点有2n个指针域,因为叶子结点的指针并不存储元素,只有只有n-1个节点被有效指针所指(n个节点中只有根节点没有被指针指),空出来的指针域有n+1个。可以利用这些空的指针域存放指向节点的前驱节点和后继节点的地址,这样就可以充分利用内存空间,增加二叉树的便利性。
(1)左指针为空,令该指针指向该节点的前驱节点。
(2)右指针为空,令该指针指向该节点的后继节点
这样的指针称为线索。
注意:这里的前驱和后驱节点是相对于遍历的顺序而言的,因此线索二叉树分为:
(1)先序线索树
(2)中序线索树
标识域:
线索二叉树存储结构声明:
typedef struct ThreadNode{
char data;
struct ThreadNode *lchild,*rchild; //左右孩子指针
int ltag,rtag; //左右标志位
}ThreadNode,*ThreadTree;
创建中序线索二叉树:
//创建线索二叉树
ThreadNode *CreateInThread(ThreadNode *T) {
ThreadTree root;
root = (ThreadNode*)malloc(sizeof(ThreadNode));
root->ltag = 0;
root->rtag = 1;
root->rchild = T;
if (T == NULL)
root->lchild = root;
else {
root->lchild = T;
pre = root;
InThread(T);
pre->rchild = root;
pre->rtag = 1;
root->rchild = pre;
}
return root;
}
二叉树的线索化:
///线索二叉树的线索化
void InThread(ThreadTree p) {
if (p != NULL) { //p是当前节点,pre是刚访问的节点
InThread(p->lchild); //递归,优化左子树
if (p->lchild == NULL) { //左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 1;
}
else
{
p->ltag = 0;
}
if (pre->rchild == NULL) {
pre->rchild = p; //右子树为空,就建立前驱节点的后及线索
pre->rtag = 1;
}
else
{
pre->rtag = 0;
}
pre = p; //p刚刚访问过了,则pre = p
InThread(p->rchild); //递归,优化右子树
}
}
中序线索二叉树完整代码:
#include
#include
typedef struct ThreadNode {
char data;
struct ThreadNode* lchild, * rchild;
int ltag, rtag;//0为不建立前驱或后驱,1为建立
}ThreadNode, * ThreadTree;
///二叉树的建立
ThreadTree InitThreadTree() {
char ch;
ThreadTree T;
//scanf_s("%c", &ch);
ch = getchar();
if (ch == '#') T = NULL;
else {
T = (ThreadTree)malloc(sizeof(ThreadNode));
T->data = ch;
T->lchild = InitThreadTree();
T->rchild = InitThreadTree();
}
return T;
}
ThreadTree pre = NULL;
///线索二叉树的线索化
void InThread(ThreadTree p) {
if (p != NULL) { //p是当前节点,pre是刚访问的节点
InThread(p->lchild); //递归,优化左子树
if (p->lchild == NULL) { //左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 1;
}
else
{
p->ltag = 0;
}
if (pre->rchild == NULL) {
pre->rchild = p; //右子树为空,就建立前驱节点的后及线索
pre->rtag = 1;
}
else
{
pre->rtag = 0;
}
pre = p; //p刚刚访问过了,则pre = p
InThread(p->rchild); //递归,优化右子树
}
}
//创建线索二叉树
ThreadNode *CreateInThread(ThreadNode *T) {
ThreadTree root;
root = (ThreadNode*)malloc(sizeof(ThreadNode));
root->ltag = 0;
root->rtag = 1;
root->rchild = T;
if (T == NULL) //二叉树为空
root->lchild = root;
else { //二叉树不为空
root->lchild = T;
pre = root;
InThread(T);
pre->rchild = root;
pre->rtag = 1;
root->rchild = pre;
}
return root;
}
///线索二叉树的遍历
void ThInOrder(ThreadTree Th) {
ThreadTree T = Th->lchild; //Th指向的是头结点,T指向根节点
while (T != Th) {
while (T->ltag == 0)
T = T->lchild;
printf("%c ", T->data);
while (T->rtag == 1 &&T->rchild != Th)
{
T = T->rchild;
printf("%c ", T->data);
}
T = T->rchild;
}
}
int main()
{
ThreadNode* T = (ThreadNode*)malloc(sizeof(ThreadNode));
T = InitThreadTree(); //创建普通二叉树
T = CreateInThread(T); //二叉树线索化
ThInOrder(T); //线索二叉树的遍历
return 0;
}
在日常应用中,经常将树中的节点赋予某种有意义的数值,称此数值为权值。
带权路径长度(Weighted Path Length):根节点到该节点的路径长度(即边的数量)与该点上权值的乘积。
哈夫曼树:在权值相同且数量相等的n个叶子节点组成的二叉树中,带权路径长度WPL最小的二叉树称为哈夫曼树或者最优二叉树。
左边的二叉树的外部路径长度为:(2 + 3 + 6 + 9) * 2 = 38
右边的二叉树的外部路径长度为:9 + 6 * 2 + (2 + 3) * 3 = 36
(1)从给定的n棵树组成的森林中,选出权值最小的两个子树分别作为左右子树构造一个新的二叉树,并且新的二叉树的根节点的权值为其左右子树上根的权值之和。
(2)在森林中,用新得到的二叉树代替这两棵树。
(3)重复(1)和(2),直到森林中只剩下一棵树为止,这棵树便是哈夫曼树。
哈夫曼树可用于构造使电文编码的代码长度最短的编码方案。
字符集{d1,d2,d3,……,dn} ----->作为节点
字符频率{w1,w2,w3,……,wn} ----->作为权值
哈夫曼编码:规定哈夫曼树的左分支为0,右分支为1,则从根节点到每个叶子结点所经过的分支对应的0和1组成的序列便是该节点对应字符的编码。
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
二叉排序树(Binary Sort Tree,简称 BST )又叫二叉查找树和二叉搜索树。其最大的优点就是查找的速度特别快,是二分查找,将在查找一章中详细说明。
(1)对于树中的每个结点,如果它有左子树,那么左子树上所有结点的值都比该结点小;
(2)对于树中的每个结点,如果它有右子树,那么右子树上所有结点的值都比该结点大。
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。