二叉树的存储结构 :
-
顺序存储结构 :
完全二叉树:按从上至下、从左到右顺序存储
n个结点的完全二叉树的结点父子关系:
-
非根结点(序号 i > 1)的父结点的序号是 i / 2;
-
结点(序号为 i )的左孩子结点的序号是 2i,(若2 i <= n,否则没有左孩子);
-
结点(序号为 i )的右孩子结点的序号是 2i+1,(若2 i +1<= n,否则没有右孩子);
但是一般不用该方式,空间浪费严重
-
2.链表存储 :(儿子-兄弟表示法 )
typedef struct tree_node* tree;
struct tree_node{
elementtype data;
tree left;
tree right;
};
二叉树的遍历 :
1. 递归遍历:
- 先序遍历:(遍历过程为: ① 访问根结点; ② 先序遍历其左子树; ③ 先序遍历其右子树)
void preorder_traversal(tree bt){
if (bt){
cout << bt->data ; //无非是该语句位置不同
preorder_traversal(bt->left);
preorder_traversal(bt->right);
}
}
//如果想仅输出叶子节点,则仅需对cout语句添加判断if ( !bt->left && !bt->right).
//求二叉树的高度://
int binary_height(tree bt){
int hl,hr,maxh;
if (bt){
hl=binary_height(bt->left); //左子树深度
hr=binary_height(bt->right); //右子树深度
maxh=(hl>hr)?hl:hr; //取左右子树最大深度
return (maxh+1); //树高为左右子树最大深度+1
}
else return 0; //空树深度为0
}
- 后序遍历:(遍历过程为: ① 后序遍历其左子树; ② 后序遍历其右子树; ③ 访问根结点)
void postorder_traversal(tree bt){
if (bt){
postorder_traversal(bt->left);
postorder_traversal(bt->right);
cout << bt->data ; //^_~
}
}
- 中序遍历 :(遍历过程为: ① 中序遍历其左子树; ② 访问根结点; ③ 中序遍历其右子树)
void inorder_traversal(tree bt){
if (bt){
inorder_traversal(bt->left);
cout << bt-<data ; //^_~
inorder_traversal(bt->right);
}
}
2. 非递归遍历:
- 先序遍历:
void preorder_traversal(tree bt){
stack s=create_stack(maxsize); //创建,初始化堆栈s
while(bt || !is_empty(s)){
while(bt){
cout << bt->data; //打印结点^_~
push(s,bt); //一直向左并将沿途结点压入堆栈
bt=bt->left;
}
if (!is_empty){
bt=pop(s); //结点弹出堆栈
bt=bt->right; //转向右子树
}
}
}
- 后序遍历:
/*由于后续遍历比较特殊,需要更换思路,如下:
先把根节点压栈两次,然后出栈,判断是否是第一次出栈,
若是,存入儿子节点,每个节点压栈两次,每次出栈的时候,进行判断*/
void postorder_traversal(tree bt){
stack s=create_stack(maxsize); //创建,初始化堆栈s
if (bt){
push(s,bt); //根节点压栈两次
push(s,bt);
tree temp=top(s); //top(s)为获取栈顶元素,不出栈
}
while (!is_empty(s)){ //根节点第二次出栈后,跳出循环
temp=pop(s);
if ( !is_empty(s) && temp==top(s) ){ //栈不空,第一次出栈
if (temp->right){
push(s,temp->right); //右儿子压栈两次
push(s,temp->right);
}
if (temp->left){
push(s,temp->left); //左儿子压栈两次
push(s,temp->left);
}
}
else cout << temp->data ; //第二次出栈,输出
}
}
- 中序遍历:
void inorder_traversal(tree bt){
stack s=create_stack(maxsize); //创建,初始化堆栈s
while(bt || !is_empty(s)){
while(bt){
push(s,bt); //一直向左并将沿途结点压入堆栈
bt=bt->left;
}
if (!is_empty){
bt=pop(s); //结点弹出堆栈
cout << bt->data; //打印结点^_~
bt=bt->right; //转向右子树
}
}
}
3. 层序遍历 :
基本过程:先根结点入队,然后从队列中取出一个元素; 访问该元素所指结点;
若该元素所指结点的左、右孩子结点非空,则将其左、右孩子的指针顺序入队。
void levelorder_traversal(tree bt){
queue q=create_queue();
if (bt) add(q,bt);
while(!is_empty(q)){
bt=delete(q);
cout << bt->data ;
if (bt->left) add(q,bt->left);
if (bt->right) add(q,bt->right);
}
}
二叉树同构判别:
int isomorphism(tree a,tree b){
if ( !a && !b ) return 1; //两个都空
if ( (!a&&b) || (a&&!b) ) return 0; // 一个空,一个不空
if (a->data!=b->data) return 0; //都不空,但数据不同
if ( !a->left && !b->left ) return(a->right,b->right); //左子树都空,判断右子树
//左子树数据相同,判断右子树
if ( (a->left&&b->left) && (a->left->data==b->left->data) )
return ( isomorphism(a->left,b->left) && isomorphism(a->right,b->right) );
//左子树一个空,一个不空;或者左子树数据不同;交换左右子树,进行判断
else
return ( isomorphism(a->left,b->right) && isomorphism(a->rght,b->left) );
}
数据查找:
1. 静态查找:集合是固定的 ,没有插入和删除操作,只有查找
- 顺序查找:
struct table{
int length;
elementtype element[length+1];
};
int sequential_find(elementtype x,struct table* tb){
/*在表tb[1]~tb[n]中查找关键字为x的数据元素*/
int i;
tb->element[0]=x;//建立哨兵
for (i=tb->length;tb->element[i]!=x;i--);
return i;
}
//顺序查找算法的时间复杂度为O(n)
- 二分查找:(数据必须有序存放,比如从小到大)
struct table{
int length;
elementtype element[length+1];
};
int binary_find(elementtype x,struct table* tb){
int left=0,mid,right=tb->length;//初始化左右边界
while(left<=right){
mid=(left+right)/2;//计算中间元素坐标
if (tb->element[mid]<x) left=mid+1;//调整左边界
else if (tb->element[mid]>x) right=mid-1;//调整右边界
else return mid;//找到,返回x的坐标位置
}
return -1;//未找到
}
//注意在调整边界时,不能left=mid或者right=mid,这样做在某些情况下会让while成为死循环
//二分查找算法具有对数的时间复杂度O(logN)
2. 动态查找:集合是动态变化的 ,除查找,还可能发生插入和删除
以二叉搜索树为例:
//结构如下:
typedef struct binary_tree* tree;
struct binary_tree{
elementtype data;
tree left;
tree right;
};
二叉搜索树:一棵二叉树,可以为空;如果不为空,满足以下性质:
- 非空左子树的所有键值小于其根结点的键值。
- 非空右子树的所有键值大于其根结点的键值。
- 左、右子树都是二叉搜索树。
-
查找:(返回x节点,失败返回NULL)
------------------查找元素X的位置---------------- //尾递归: tree find(elementtype x,tree bt) { if( !bt ) return NULL; /*查找失败*/ if( x > bt->data ) return find(x,bt->right); /*在右子树中继续查找*/ else if( x < bt->data ) return find(x,bt->left); /*在左子树中继续查找*/ else return bt; /*查找成功,返回结点的地址*/ } //由于非递归函数的执行效率高,下面将尾递归改为迭代: tree find(elementtype x,tree bt){ while(bt){ if (x>bt->data) bt=bt->right; /*向右子树中移动,继续查找*/ else if (x<bt->data) bt=bt->left; /*向左子树中移动,继续查找*/ else return bt; /*查找成功,返回找到结点的地址*/ } return NULL; /*查找失败*/ } ------------------查找最小元素的位置---------------- tree find_min(tree bt){ if (bt) while (bt->left) bt=bt->left; return bt; } ------------------查找最大元素的位置---------------- tree find_max(tree bt){ if (bt) while (bt->right) bt=bt->right; //沿右分支继续查找,直到最右叶结点 return bt; }
-
插入:(返回结果树根节点)
tree insert(elementtype x,tree bt){ if (!bt){ /*若原树为空,生成并返回一个结点的二叉搜索树*/ bt=(tree)malloc(sizeof(struct binary_tree)); bt->data=x; bt->left=bt->right=NULL; return bt; } else{ /*开始找要插入元素的位置*/ tree top=bt; int flag=0; //判断在left插入还是right插入 tree temp=NULL; //记录父节点 while (bt){ temp=bt; if (x>bt->data){ //右子树寻找插入位置 bt=bt->right; flag=1; } else if (x<bt->data){ //左子树寻找插入位置 bt=bt->left; flag=0; } else return true; //x已存在,直接返回插入成功 } if (flag){ //flag==1,在temp的right下新建节点 temp->right=(tree)malloc(sizeof(struct binary_tree)); temp->right->data=x; temp->rigth->left=temp->right->right=NULL; } else{ //flag==0,在temp的left下新建节点 temp->left=(tree)malloc(sizeof(struct binary_tree)); temp->left->data=x; temp->left->left=temp->left->right=NULL; } return top; } }
-
删除:(返回结果树根节点)
//递归: tree delete(elementtype x,tree bt){ if (!bt) reeturn NULL; //空树 else if (x<bt->data) bt->left=delete(x,bt->left);//左子树递归删除 else if (x>bt->data) bt->right=delete(x,bt->right);//右子树递归删除 else{ //找到要删除的结点 tree temp=NULL; if (bt->left && bt->right){ //待删除节点有左,右子树 //在右子树中找最小的元素填充删除结点 temp=find_min(bt->right); bt->data=temp->data; bt->right=delete(bt->data,bt->right); } else{ //待删除节点仅有左子树,或仅有右子树,或无子节点 temp=bt; if (bt->left) //有左节点 bt=bt->left; else //有右节点,或无子节点 bt=bt->right; free(temp); } } return bt; } //非递归:(稍微复杂一些) tree delete(elementtype x,tree bt){ if (!bt) reeturn NULL; //空树 tree top=bt; //记录根节点 tree parent=bt; //记录父节点 while(bt){ //寻找x位置 if (x>bt->data){ parent=bt; bt=bt->right; } else if (x<bt->data){ parent=bt; bt=bt->left; } else break; //找到,退出循环 } if (!bt) return top; //未找到 if (bt->left && bt->right){ //待删除节点有左,右子树 tree temp=bt->right; //在右子树中找最小的元素填充删除结点 while(temp->left){ parent=temp; temp=temp->left; } bt->data=temp->data; if (temp==parent->right)//说明待删除节点的右子树为斜右二叉树(包括一个节点情况) parent->right=temp->right; else parent->left=temp->right;//待删除节点的右子节点有左子树 free(temp); return top; } else{ //待删除节点仅有左子树,或仅有右子树,或无子节点 tree temp=bt; if (parent==bt){ //删除根节点(包括最后一个节点情况) if (bt->left) bt=bt->left; else bt=bt->right; free(temp); return bt; } else{ //根节点不变 if (bt==parent->left){ if (bt->left) parent->left=bt->left; else parent-left=bt->right; } else{ if (bt->left) parent->right=bt->left; else parent->right=bt->right; } free(temp); return top; } } }
平衡二叉树:
平衡二叉树 (Balanced Binary Tree) (AVL树) :
空树,或者任一结点左、右子树高度差的绝对值不超过1
设 n h 高 度 为 h 的 平 衡 二 叉 树 的 最 少 结 点 数 。 结 点 数 最 少 时 : n h = n h − 1 + n h − 2 + 1 设n_h高度为h的平衡二叉树的最少结点数。结点数最少时: n_h = n_{h-1} + n_{h-2} + 1 设nh高度为h的平衡二叉树的最少结点数。结点数最少时:nh=nh−1+nh−2+1
typedef struct avl_tree* tree;
struct avl_tree{
tree left; //指向左子树
tree right; //指向右子树
elementtype data; //结点数据
int height; //树高
};
int max(int a, int b)
{
return a > b ? a : b;
}
tree left_rotation(tree a){ //左单旋
/* 注意:a必须有一个左子结点b */
/* 将a与b做左单旋,更新a与b的高度,返回新的根结点b */
tree b=a->left;
a->left=b->right;
b->right=a;
a->height=max(a->left->height,a->right->height)+1;
b->height=max(b->left->height,b->right->height)+1;
return b;
}
tree right_rotation(tree a){ //右单旋
/* 把left,right对换即可 */
tree b=a->right;
a->right=b->left;
b->left=a;
a->height=max(a->left->height,a->right->height)+1;
b->height=max(b->left->height,b->right->height)+1;
return b;
}
tree left_right_rotation(tree a){ //左-右双旋
/* 注意:a必须有一个左子结点b,且b必须有一个右子结点c */
/* 将a、b与c做两次单旋,返回新的根结点c */
a->left=right_rotation(a->left); //将B与C做右单旋,C被返回
return left_rotation(a); //将A与C做左单旋,C被返回
}
tree right_left_rotation(tree a){ //右-左双旋
/* 同理,把left,right对换即可 */
a->right=left_rotation(a->right);
return right_rotation(a);
}
/* 如果不使用递归,将会使程序变得非常复杂,比如:
插入时需要判断在left还是right,插入后还需要判断是否仍是平衡二叉树,
而判断又需求树高,除此之外,还需要记录“麻烦节点”和“发现者”,因此下面采用递归实现:*/
tree insert(elementtype x,tree bt){
if (!bt){ //若插入空树,则新建包含一个结点的树
bt=(tree)malloc(sizeof(struct avl_tree));
bt->height=0;
bt->left=bt->right=NULL;
bt->data=x;
return bt; //插入空树结束
}
else if (x < bt->data){
bt->left=insert(x,bt->left); //插入bt的左子树
if (bt->left->height - bt->right->height == 2){ //如果需要左旋
if (x < bt->left->data) bt=left_rotation(bt); //左单旋
else bt=bt->left_right_rotation(bt); //左-右双旋
}
}
else if (x > bt->data){ //插入bt的右子树
bt->right=insert(bt->right);
if (bt->right->height - bt->left->height == 2){ //如果需要右旋
if (x > bt->right->data) bt=right_rotation(bt); //右单旋
else bt=right_left_rotation(T); //右-左双旋
}
}
//else x==bt->data,无须操作
bt->height=max(bt->left,bt->right)+1; //更新树高
return bt;
}
}
堆:
优先队列(Priority Queue):特殊的“队列”,取出元素的顺序是 依照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
堆的两个特性 :
结构性:用数组表示的完全二叉树;
有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
“最大堆(MaxHeap)”,也称“大顶堆”:最大值
“最小堆(MinHeap)”,也称“小顶堆” :最小值
下面以最大堆(数组实现)为例,实现基本操作:
typedef struct priority_queue* heap;
struct priority_queue(int max){
elementtype* data;
int size;
int capacity;
};
-
创建:
heap create_maxheap(int max){ heap h=(heap)malloc(sizeof(struct priority_queue)); h->data=(elementtype*)malloc(sizeof(elementtype)*(max+1)); h->size=0; h->capacity=max; }
-
插入:
int is_full(heap h){ //判断堆是否满 return (h->size==h->capacity); } bool insert(elementtype x,heap h){ if (is_full(h)) return false; //最大堆已满 int i=++(h->size); //取插入位置,当前容量+1 while (i>1 && x>h->data[i/2]){ //未到达堆顶,且大于i的父亲节点 h->data[i]=h->data[i/2]; //相当于给x腾出空间 i/=2; } h->data[i]=x; //插入x return true; }
-
删除:
int is_empty(heap h){ //判断堆是否空 return (h->size==0); } elementtype delete_max(heap h){ //删除堆顶元素并返回 if (is_empty(h)) return false; //堆空 elementtype top=h->data[0]; //记录堆顶元素,待return elementtype temp=h->data[(h->size)--]; //记录最后一个元素,当前容量-1 int parent=1,child; for (; parent*2<=h->size; parent=child){ child=2*parent; if ( (child!=h->size) && (h->data[child]<h->data[child+1]) ) child++; //找到最大子节点(其中第一个条件判断是否有右儿子) if (temp>=h->data[child]) break; //让temp补位 else h->data[parent]=h->data[child]; //给temp腾出位置 } h->data[parent]=temp; //temp插入 return top; }
哈夫曼树与哈夫曼编码:
哈夫曼树的定义:
带权路径长度
(
W
P
L
)
(WPL)
(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值
w
k
w_k
wk,从根结点到每个叶子结点的长度为
l
k
l_k
lk,则每个叶子结点的带权路径长度之和就是:
W
P
L
=
∑
k
=
1
n
w
k
l
k
WPL=\sum_{k=1}^n w_kl_k
WPL=∑k=1nwklk
最优二叉树或哈夫曼树:
W
P
L
WPL
WPL最小的二叉树
哈夫曼树的构造 :
每次把权值最小的两棵二叉树合并
typedef struct TreeNode *HuffmanTree;
struct TreeNode{
int Weight;
HuffmanTree Left, Right;
}
HuffmanTree Huffman( MinHeap H ) {
/* 假设H->Size个权值已经存在H->Elements[]->Weight里 */
int i;
HuffmanTree T;
BuildMinHeap(H); /*将H->Elements[]按权值调整为最小堆*/
for (i = 1; i < H->Size; i++) { /*做H->Size-1次合并*/
T = malloc( sizeof( struct TreeNode) ); /*建立新结点*/
T->Left = DeleteMin(H); /*从最小堆中删除一个结点,作为新T的左子结点*/
T->Right = DeleteMin(H); /*从最小堆中删除一个结点,作为新T的右子结点*/
T->Weight = T->Left->Weight+T->Right->Weight;/*计算新权值*/
Insert( H, T ); /*将新T插入最小堆*/
}
T = DeleteMin(H);
return T;
}
//转自https://www.icourse163.org/learn/ZJU-93001?tid=1206471203#/learn/content?type=detail&id=1211167094&cid=1213729254
集合及运算:
typedef struct{
elementtype data;
int parent;
}gather;
int find(elementtype x,gather s[]){ //在数组s中查找值为x的元素所属的集合
int i=0;
while(i<maxsize && s[i].data!=x) i++;
if (i>=maxsize) return -1; //未找到
while(s[i].parent>=0) i=s[i].parent;
return i; //找到x所属集合,返回树根结点在数组s中的下标
}
int combination(elementtype x,elementtype y,gather s[]){
int a=find(x,s);
int b=find(y,s);
if (a!=b){
if (-s[a].parent >= -s[b].parent){//如果集合a比较大,集合b并入集合a
s[a].parent+=s[b].parent;
s[b].parent=a;
}
else{
s[b].parent+=s[a].parent;//如果集合b比较大,集合a并入集合b
s[a].parent=b;
}
}
}