目录
一 ,树的概念
树(Tree)是n(n>=0)个结点有限集。在任意一颗非空树中:
1) 有且仅有一个特定的称为根(root)的结点;
2) 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,....Tm,其中每个集合本身
又是一颗树,并且称为根的子树(SubTree)。
树的结点包含一个数据元素及若干指向相关子树的分支。
结点的度(Degree):
结点拥有的子树的数量称为结点的度。度为0的结点称为叶子(Leaf)结点或终端结点;
度不为0的结点称为非终端结点或分支结点。
结点的层次(Level):
从根开始定义起,根为第一层,根的子树为第二层,树中最大层次称为该树的深度(Depth)或高度。
二 ,二叉树
1.二叉树的概念
二叉树(Binary Tree)是一种树型结构,它的特点是每个结点至多只有两根子树,即二叉树中不存在
度大于2的结点。并且二叉树的子树有左、右之分,其次序不能任意颠倒。
二叉树的五种形态:
(1)空二叉树
(2)只有一个根结点的二叉树
(3)只有左子树
(4)只有右子树
(5)完全二叉树
2.二叉树的性质
1) 在二叉树的第i层上至多有 2^(i-1)个结点 (i>=1)
2) 深度为K的二叉树至多有 2^k-1个结点
3) 对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,
则 n2 = n0-1
满二叉树: 一棵深度为K且具有2^K-1个结点的二叉树,称为满二叉树
(在不改变其深度的情况下,不能再往这棵树上增加结点了)
完全二叉树: 满足以下两个条件的二叉树
a. 去除最后一层后为满二叉树
b. 最后一层的结点必须依次从左往右挂载
4) 具有n个结点的完全二叉树的深度为 (log2 N)+1 向下取整
5) 具有n个结点的完全二叉树的,如果自上而下,自左到右,从1到n编号,那么:
编号为i的结点,它的左子结点(如果有)的编号为2i,其右子结点(如果有)的
编号为2i+1,它的父结点(如果有)编号为i/2。
3.二叉树的存储结构
(1)顺序存储结构
把一棵普通的二叉树,填补成一棵完全二叉树,然后按自上而下,从左到右,用1~n编号,
保存在一个数组里。
#define MAX_TREE_SIZE 1024
typedef char TElemType;
TElemType SqBiTree[MAX_TREE_SIZE];
(2)链式存储结构
typedef struct BiTNode
{
TElemType data;//数据域
struct BiTNode *lchild, *rchild;//指针域
}BiTNode;
4.二叉树的遍历
按某条搜索路径访问树中的每个结点,使得每个结点均被访问一次,且只访问一次
创建二叉树
typedef char TElemType;
typedef struct BiTNode
{
TElemType data;//数据域
struct BiTNode *lchild, *rchild;//指针域
}BiTNode;
先序遍历: 根、左、右
/*用递归的方法,实现二叉树的先序遍历:根左右*/
void pre_order(BiTNode *r)
{
if(r == NULL)
return;
/*step1: 先访问根*/
printf("%c ",r->data);
/*step2: 用先序的方式去访问左子树*/
pre_order(r->lchild);
/*step3: 用先序的方式去访问右子树*/
pre_order(r->rchild);
}
中序遍历: 左、根、右
void mid_order(BiTNode *r)
{
if(r == NULL)
return;
/*step1: 用中序的方式去访问左子树*/
mid_order(r->lchild);
/*step2: 访问根*/
printf("%c ",r->data);
/*step3: 用中序的方式去访问右子树*/
mid_order(r->rchild);
}
后序遍历: 左、右、根
/*用递归的方法,实现二叉树的后序遍历:左右根*/
void post_order(BiTNode *r)
{
if(r == NULL)
return;
/*step1: 用后序的方式访问左子树*/
post_order(r->lchild);
/*step2: 用后序的方式访问右子树*/
post_order(r->rchild);
/*step3: 访问根*/
printf("%c ",r->data);
}
层次遍历
/*
level_traverse: 把r指向的二叉树按层次遍历
*/
void level_traverse(BiTNode *r)
{
CircleQueue * cq = InitQueue();
/*step1: 把根结点入队*/
EnQueue(cq, r);
while(!QueueIsEmpty(cq))
{
/*step2: 出队*/
BiTNode *p = DeQueue(cq);
/*step3: 访问该结点,并把该结点的子树入队*/
printf("%c ",p->data);
if(p->lchild)
EnQueue(cq, p->lchild);
if(p->rchild)
EnQueue(cq, p->rchild);
}
putchar('\n');
DestroyQueue(cq);
}
5.二叉排序树(Binary Sort Tree)
二叉排序树有名二叉查找树,它要么是一棵空树,要么是具有下列性质的二叉树
1) 若它的左子树不为空,则左子树上所有的结点的值均小于它的根结点;
2) 若它的右子树不为空,则右子树上所有的结点的值均大于它的根结点;
3) 它的左右子树若存在,也分别为二叉排序树。
BiTNode *insert_node(BiTNode *r, TElemType d)
{
/*step1: 创建一个新的结点*/
BiTNode *pnew = malloc(sizeof(*pnew));
pnew->data = d;
pnew->lchild = NULL;
pnew->rchild = NULL;
/*step2: 把新结点插入到树中*/
if(r == NULL)//r不存在,新的结点就是根结点
{
return pnew;
}
else
{
BiTNode *p = r;//定义一个遍历指针
while(p)
{
if(pnew->data < p->data)//往左子树找
{
if(p->lchild == NULL)//找到位置了,插入
{
p->lchild = pnew;
break;
}
else
{
p = p->lchild;
}
}
else if(pnew->data > p->data)//往右子树找
{
if(p->rchild == NULL)//找到位置了,插入
{
p->rchild = pnew;
break;
}
else
{
p = p->rchild;
}
}
else
{
printf("数据不能重复\n");
break;
}
}
}
return r;
}
6.平衡二叉树(Balanced Binary Tree)
平衡二叉树又称为AVL树。它要么是一棵空树,要么是具有以下性质的二叉排序树:
1) 它的左子树或右子树都是平衡二叉树
2) 左子树和右子树的深度之差的绝对值不超过1 (1,0,-1)
若将二叉树的结点的平衡因子BF(Balance Factor)定义为该结点的左子树的深度减去
右子树的深度,则平衡二叉树的所有结点的平衡因子只可能是 -1,0,1。只要二叉树
上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
typedef struct AVLNode
{
TElemType data;
struct AVLNode *Left,*Right;
int Height;//空树的高度为0
}AVLNode;
建立AVL树的过程中,把不平衡的树转换成一个平衡树有四种方式:
(1) 单向右旋平衡处理 SingleRotateWithRight
(2) 单向左旋平衡处理 SingleRotateWithLeft
(3) 双向旋转(先左后右)平衡处理 DoubleRotateLeftRight
(4) 双向旋转(先右后左)平衡处理 DoubleRotateRightLeft
代码实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
typedef char TElemType;
typedef struct AVLNode
{
TElemType data;
struct AVLNode *Left,*Right;
int Height;//空树的高度为0
}AVLNode;
/*
Height: 返回一棵树的深度,空树的深度为0
@r: 要获得深度的树
@retval: 返回该树的深度
*/
int Height(AVLNode *r)
{
if(r == NULL)
return 0;
return r->Height;
}
/*
SingleRotateWithRight: 对某棵二叉树进行单向右旋平衡处理
*/
AVLNode *SingleRotateWithRight(AVLNode *K2)
{
AVLNode *K1 = K2->Left;
K2->Left = K1->Right;
K1->Right = K2;
K2->Height = MAX(Height(K2->Left), Height(K2->Right))+1;
K1->Height = MAX(Height(K1->Left), Height(K1->Right))+1;
return K1;
}
/*
SingleRotateWithLeft: 对某棵二叉树进行单向左旋平衡处理
*/
AVLNode *SingleRotateWithLeft(AVLNode * K2)
{
AVLNode *K1 = K2->Right;
K2->Right = K1->Left;
K1->Left = K2;
K2->Height = MAX(Height(K2->Left), Height(K2->Right))+1;
K1->Height = MAX(Height(K1->Left), Height(K1->Right))+1;
return K1;
}
/*
DoubleRotateLeftRight: 对某棵二叉树进行双向旋转(先左后右)平衡处理
*/
AVLNode *DoubleRotateLeftRight(AVLNode *K3)
{
K3->Left = SingleRotateWithLeft(K3->Left);
return SingleRotateWithRight(K3);
}
/*
DoubleRotateRightLeft: 对某棵二叉树进行双向旋转(先右后左)平衡处理
*/
AVLNode *DoubleRotateRightLeft(AVLNode *K3)
{
K3->Right = SingleRotateWithRight(K3->Right);
return SingleRotateWithLeft(K3);
}
/*
Insert_Node: 往T指向的AVL树中,插入一棵值为x的结点,且还保持平衡
*/
AVLNode *Insert_Node(AVLNode *T, TElemType x)
{
if(T == NULL)//如果T为空,则返回要插入的结点作为根结点
{
T = malloc(sizeof(*T));
T->data = x;
T->Left = NULL;
T->Right = NULL;
T->Height = 1;
return T;
}
if(x > T->data)//x插在T的右子树上
{
T->Right = Insert_Node(T->Right, x);//保证了T的右子树是平衡的
T->Height = MAX(Height(T->Left), Height(T->Right))+1;//更新T的深度
if(Height(T->Right)-Height(T->Left) > 1)//T不平衡了,需要进行平衡处理
{
if(x > T->Right->data)//右深右插
{
T = SingleRotateWithLeft(T);
}
else //右深左插
{
T = DoubleRotateRightLeft(T);
}
}
}
else if(x < T->data)//x插在T的左子树上
{
T->Left = Insert_Node(T->Left, x);//保证了T的左子树是平衡的
T->Height = MAX(Height(T->Left), Height(T->Right))+1;//更新T的深度
if(Height(T->Left)-Height(T->Right) > 1)//T不平衡了,需要进行平衡处理
{
if(x > T->Left->data)//左深右插
{
T = DoubleRotateLeftRight(T);
}
else //左深左插
{
T = SingleRotateWithRight(T);
}
}
}
return T;
}
/*用递归的方法,实现二叉树的先序遍历:根左右*/
void pre_order(AVLNode *r)
{
if(r == NULL)
return;
/*step1: 先访问根*/
printf("%c ",r->data);
/*step2: 用先序的方式去访问左子树*/
pre_order(r->Left);
/*step3: 用先序的方式去访问右子树*/
pre_order(r->Right);
}
/*用递归的方法,实现二叉树的中序遍历:左根右*/
void mid_order(AVLNode *r)
{
if(r == NULL)
return;
/*step1: 用中序的方式去访问左子树*/
mid_order(r->Left);
/*step2: 访问根*/
printf("%c ",r->data);
/*step3: 用中序的方式去访问右子树*/
mid_order(r->Right);
}
/*用递归的方法,实现二叉树的后序遍历:左右根*/
void post_order(AVLNode *r)
{
if(r == NULL)
return;
/*step1: 用后序的方式访问左子树*/
post_order(r->Left);
/*step2: 用后序的方式访问右子树*/
post_order(r->Right);
/*step3: 访问根*/
printf("%c ",r->data);
}
int main()
{
AVLNode *T=NULL;
char str[32];
gets(str);
for(int i=0;i<strlen(str);i++)
{
T = Insert_Node(T, str[i]);
}
printf("先序遍历: ");
pre_order(T);
printf("\n中序遍历: ");
mid_order(T);
printf("\n后序遍历: ");
post_order(T);
putchar('\n');
}
7.哈弗曼树
1,基本概念
哈弗曼树又称最优二叉树。它是n个带权叶子结点构成的所有二叉树中,带权路径长度WPL最小的二叉树。
(1)一棵树的带权路径长度定义为: 树中所有叶子结点的带权路径长度之和。
(2)结点的带权路径长度规定为从树根结点到该结点之间的路径长度与该结点上的权的乘积
2,哈夫曼编码
在电报通信中,电文是以二进制0/1序列传送的,每个字符对应一个二进制编码。为了缩短电文
的总长度,采用不等长的编码方式,使用频率高的用短编码,使用频率低的用长编码。
我们把使用频率作为权值,每个字符作为叶子结点来构建哈夫曼树,然后每个分支结点的左右分支
分别用0和1编码,这样就得到了每个叶子结点的二进制编码:
a: 00
b: 01
c: 100
d: 1010
e: 1011
f: 11
发送bcd的编码是 011001010,且只有唯一的解读方式
3,构建哈夫曼树
假设有n个权值分别为w1,w2,w3,...wn的叶子结点,则哈夫曼树的构造规则为:
第一步: 构建森林
我们把每一个叶子,都当做一颗独立的树(只有根结点),这样就形成了一个森林。
我们把这片森林存放到一个优先队列中,方便取出最小的结点。
第二步:
从优先队列中取出权值最小的两个结点,然后再创建一个新的结点作为他们的父结点,
父结点的权值就是这两个结点权值之和
第三步: 把新的创建的父结点加入到优先队列。
重复第二、三步,直到优先队列中只剩下一个结点为止,该结点就是哈夫曼树的根结点
代码实现如下:
#ifndef __HUFFMANTREE_H__
#define __HUFFMANTREE_H__
typedef struct TNode
{
int weight;
struct TNode *left,*right;
}TNode;
extern TNode *CreateHuffman(int weights[], int len);
extern void pre_order(TNode * r);
extern void mid_order(TNode * r);
extern void post_order(TNode * r);
#endif
#include <stdio.h>
#include <stdlib.h>
#include "PriorityQueue.h"
#include "HuffmanTree.h"
struct lnode
{
int code;
struct lnode *next;
};
struct List
{
struct lnode *first;
struct lnode *last;
int nodeNum;
int weight;//叶子结点的权
struct List *next;
};
struct List *add_node(struct List *list, int data)
{
struct lnode *pnew = malloc(sizeof(*pnew));
pnew->code = data;
pnew->next = NULL;
if(list->first == NULL)
{
list->first = pnew;
list->last = pnew;
}
else
{
list->last->next = pnew;
list->last = pnew;
}
list->nodeNum++;
}
/*
add_head: 新创建一个头结点,并链在list的后面。返回的新创建头结点
*/
struct List *add_head(struct List *list, int weight)
{
struct List *list1 = malloc(sizeof(*list1));
list1->first = NULL;
list1->last = NULL;
list1->next = NULL;
list1->nodeNum = 0;
list1->weight = weight;
if(list == NULL)
{
return list1;
}
else
{
list->next = list1;
}
return list1;
}
/*
CreateHuffman: 把长度为lend的weights数组里的权,构建成一棵哈夫曼树
*/
TNode *CreateHuffman(int weights[], int len)
{
/*step1: 构建森林*/
TNode *r = NULL;
TNode *nodes = malloc(sizeof(*nodes)*len);
PriorityQueue *pq = InitQueue();//初始化一个优先队列
for(int i=0;i<len;i++)
{
nodes[i].weight = weights[i];
nodes[i].left = NULL;
nodes[i].right = NULL;
struct node * pnew = malloc(sizeof(*pnew));
pnew->data = nodes+i;
pnew->next = NULL;
EnQueue(pq, pnew);//把这片森林入队
}
while(QueueLen(pq) >= 2)
{
/*step2: 从优先队列中取出权值最小的两个结点,然后再创建一个新的结点作为他们的父结点,
父结点的权值就是这两个结点权值之和*/
struct node *p1 = DeQueue(pq);
struct node *p2 = DeQueue(pq);
TNode *Left = p1->data;
TNode *Right = p2->data;
free(p1);
free(p2);
TNode *father = malloc(sizeof(*father));
father->weight = Left->weight + Right->weight;
father->left = Left;
father->right = Right;
/*step3: 把新的创建的父结点加入到优先队列*/
struct node * pnew = malloc(sizeof(*pnew));
pnew->data = father;
pnew->next = NULL;
EnQueue(pq, pnew);//把父结点入队
}
struct node *p = DeQueue(pq);
r = p->data;
free(p);
DestroyQueue(pq);
return r;
}
/*用递归的方法,实现二叉树的先序遍历:根左右*/
void pre_order(TNode *r)
{
if(r == NULL)
return;
/*step1: 先访问根*/
printf("%d ",r->weight);
/*step2: 用先序的方式去访问左子树*/
pre_order(r->left);
/*step3: 用先序的方式去访问右子树*/
pre_order(r->right);
}
/*用递归的方法,实现二叉树的中序遍历:左根右*/
void mid_order(TNode *r)
{
if(r == NULL)
return;
/*step1: 用中序的方式去访问左子树*/
mid_order(r->left);
/*step2: 访问根*/
printf("%d ",r->weight);
/*step3: 用中序的方式去访问右子树*/
mid_order(r->right);
}
/*用递归的方法,实现二叉树的后序遍历:左右根*/
void post_order(TNode *r)
{
if(r == NULL)
return;
/*step1: 用后序的方式访问左子树*/
post_order(r->left);
/*step2: 用后序的方式访问右子树*/
post_order(r->right);
/*step3: 访问根*/
printf("%d ",r->weight);
}
struct List *listHead;
struct List *p_head;
void HuffmanCoding(TNode *r, int len)
{
static int arr[20];
int i;
if(r == NULL)
{
return;
}
if(r->left==NULL && r->right==NULL)//这是终端结点,打印编码
{
printf("结点权值为%d的编码: ",r->weight);
p_head = add_head(p_head, r->weight);//增加头结点
for(i=0;i<len;i++)
{
printf("%d ",arr[i]);
p_head = add_node(p_head, arr[i]);//往该头结点里增加数据结点
}
putchar('\n');
}
else
{
arr[len]=0;
HuffmanCoding(r->left, len+1);
arr[len]=1;
HuffmanCoding(r->right, len+1);
}
}
int main()
{
int weights[6];
for(int i=0;i<6;i++)
{
scanf("%d",&weights[i]);
}
TNode *r = CreateHuffman(weights, 6);
printf("先序遍历: ");
pre_order(r);
printf("\n中序遍历: ");
mid_order(r);
printf("\n后序遍历: ");
post_order(r);
printf("\n");
listHead = malloc(sizeof(*listHead));
listHead->first = NULL;
listHead->last = NULL;
listHead->next = NULL;
listHead->nodeNum = 0;
p_head = listHead;
HuffmanCoding(r, 0);
struct List *p = listHead->next;//第一个头结点是没有数据结点的
while(p)
{
printf("%d: ",p->weight);
struct lnode *p1 = p->first;
while(p1)
{
printf("%d",p1->code);
p1 = p1->next;//横着走的
}
putchar('\n');
p = p->next;//竖着走的
}
}
三,练习
1.判断r指向的二叉树是否是完全二叉树
int judge_CompleteBiTree(BiTNode *r)
{
int ret=1;
int flag=0;//标志位,当出现空子树该标志位置1
CircleQueue * cq = InitQueue();
/*step1: 把根结点入队*/
EnQueue(cq, r);
while(!QueueIsEmpty(cq))
{
/*step2: 出队*/
BiTNode *p = DeQueue(cq);
/*step3: 访问该结点,并把该结点的子树入队*/
//printf("%c ",p->data);
if(p->lchild)
{
if(flag)//如果已经有空子树了,再出现子树,则就不是完全二叉树
{
ret = 0;
break;
}
EnQueue(cq, p->lchild);
}
else
{
flag = 1;
}
if(p->rchild)
{
if(flag)//如果已经有空子树了,再出现子树,则就不是完全二叉树
{
ret = 0;
break;
}
EnQueue(cq, p->rchild);
}
else
{
flag = 1;
}
}
//putchar('\n');
DestroyQueue(cq);
return ret;
}
2.判断r是不是pa和pb的共同父结点
/*
FindBothFather: 判断r是不是pa和pb的共同父结点,是返回r,不是则在子树去找
*/
BiTNode *FindBothFather(BiTNode *r, BiTNode *pa, BiTNode *pb)
{
if(r == NULL)
{
return NULL;
}
if(pa==r || pb==r)
{
return r;
}
BiTNode *left = FindBothFather(r->lchild, pa, pb);
BiTNode *right = FindBothFather(r->rchild, pa, pb);
if(left == NULL)
{
return right;
}
else if(right == NULL)
{
return left;
}
else //左右子树里都找到了结点,则r就是共同的父结点
{
return r;
}
}