数据结构-树型结构
堆
- 是完全二叉树,不适合链式存储的树
大顶堆(大根堆)
- 根节点比左右子树大
小顶堆(小根堆)
- 根节点比左右子树小
数据项:
- 存储数据的首地址
- 容量
- 数量
运算:
- 创建、销毁、添加、删除、空堆、满栈,
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define TYPE int
#define SWAP(a,b) {typeof(a) t=(a);(a)=(b);(b)=t;}
// 大顶堆
typedef struct Heap
{
TYPE* arr;
size_t cal;
size_t cnt;
}Heap;
Heap* create_heap(int cal)
{
Heap* heap = malloc(sizeof(Heap));
heap->arr = malloc(sizeof(TYPE)*cal);
heap->cal = cal;
heap->cnt = 0;
return heap;
}
// 空堆
bool empty_heap(Heap* heap)
{
return 0 == heap->cnt;
}
// 满堆
bool full_heap(Heap* heap)
{
return heap->cnt >= heap->cal;
}
// 添加
bool add_heap(Heap* heap,TYPE data)
{
if(full_heap(heap)) return false;
heap->arr[heap->cnt++] = data;
// 新加入的位置进行调整成大顶堆
int index = heap->cnt; // 编号
while(index > 1)
{
if(heap->arr[index/2-1] < heap->arr[index-1])
{
SWAP(heap->arr[index/2-1],heap->arr[index-1]);
index /= 2;
continue;
}
return true;
}
return true;
}
// 删除堆顶
bool del_top_heap(Heap* heap)
{
if(empty_heap(heap)) return false;
// 堆顶与末尾交换
SWAP(heap->arr[0],heap->arr[heap->cnt-1]);
// 删除末尾
heap->cnt--;
// 从上到下重新调整成堆结构
int index = 1;
while(index <= heap->cnt)
{
// 有右子树,比较左右,谁大再与根比较,根小则交换
if(index*2 < heap->cnt)
{
// 右比左大
if(heap->arr[index*2] > heap->arr[index*2-1])
{
// 根小 交换右和根
if(heap->arr[index*2] > heap->arr[index-1])
{
SWAP(heap->arr[index*2],heap->arr[index-1]);
index = index*2+1;
}
else
return true;
}
else // 左 大于等于 右
{
if(heap->arr[index*2-1] > heap->arr[index-1])
{
SWAP(heap->arr[index*2-1],heap->arr[index-1]);
index = index*2;
}
else
return true;
}
}
else if(index*2-1 < heap->cnt)// 只有左子树
{
if(heap->arr[index*2-1] > heap->arr[index-1])
{
SWAP(heap->arr[index*2-1],heap->arr[index-1]);
index = index*2;
}
else
return true;
}
else
return true;
}
}
// 堆排序
void sort_heap(int* arr,int len)
{
// 把数组调整成大顶堆结构
for(int i=1; i<len; i++)
{
int index = i+1; //编号
while(index > 1)
{
if(arr[index-1] > arr[index/2-1])
{
SWAP(arr[index-1],arr[index/2-1]);
index = index/2;
}
else
break;
}
}
// 交换堆顶与末尾,有效个数-1,重新调整堆结构,直到有效个数为0
while(len > 1)
{
SWAP(arr[0],arr[len-1]);
len--;
//从上往下调整
int index = 1; //编号
while(index-1 < len)
{
if(index*2 < len) //有右
{
// 右最大 交换根和右
if(arr[index*2] > arr[index*2-1])
{
if(arr[index*2] > arr[index-1])
{
SWAP(arr[index*2],arr[index-1]);
index = index*2+1;
}
else
break;
}
else // 左大于等于右
{
if(arr[index*2-1] > arr[index-1])
{
SWAP(arr[index*2-1],arr[index-1]);
index = index*2;
}
else
break;
}
}
else if(index*2-1 < len) //只有左
{
if(arr[index*2-1] > arr[index-1])
{
SWAP(arr[index*2-1],arr[index-1]);
index = index*2;
}
else
break;
}
else
break;
}
}
}
// 从top下标 到end下标 向下调整成堆结构
void _sort_heap(int* arr,int top,int end)
{
if(top >= end) return;
int max = top+1; // 编号
int l = max*2; // 左子树编号
int r = max*2+1; // 右子树编号
if(l-1 <= end && arr[l-1] > arr[max-1]) // 有左
{
max = l;
}
if(r-1 <= end && arr[r-1] > arr[max-1]) // 有右
{
max = r;
}
if(max != top+1)
{
// 值最大的不是根,则交换
SWAP(arr[max-1],arr[top]);
_sort_heap(arr,max-1,end);
}
}
// 堆排序递归实现
void sort_heap_recursion(int* arr,int len)
{
// 把数组调整成大顶堆结构
for(int i=1; i<len; i++)
{
int index = i+1; //编号
while(index > 1)
{
if(arr[index-1] > arr[index/2-1])
{
SWAP(arr[index-1],arr[index/2-1]);
index = index/2;
}
else
break;
}
}
for(int i=len-1; i>0; i--)
{
SWAP(arr[0],arr[i]);
_sort_heap(arr,0,i-1);
}
}
void show_heap(Heap* heap)
{
for(int i=0; i<heap->cnt; i++)
{
printf("%d ",heap->arr[i]);
}
printf("\n");
}
int main(int argc,const char* argv[])
{
Heap* heap = create_heap(10);
for(int i=0; i<10; i++)
{
add_heap(heap,rand()%100);
}
show_heap(heap);
}
堆的应用
堆排序
- 不停地重复执行删除堆顶的操作,直到删除全部元素,最终整个存储元素的内存就有序了
优先队列
- 在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出的行为特征
平衡二叉树
- 前提是一棵有序二叉树,它的左右子树高度差不超过1,同时它的所有子树也要满足该条件
二叉树不平衡的四种基础原因
x y
/ \ / \
y t1 z x
/ \ / \ / \
z t2 以y为轴右旋转 t3 t4 t2 t1
/ \
t3 t4
x y
/ \ / \
t1 y x z
/ \ 以y为轴左旋转 / \ / \
t2 z t1 t2 t3 t4
/ \
t3 t4
x x z
/ \ / \ / \
y t1 z t1 y x
/ \ / \ / \ / \
t2 z y t4 t2 t3 t4 t1
/ \ / \
t3 t4 t2 t3
以z为轴左旋转 以z为轴右旋转
x x z
/ \ / \ / \
t1 y t1 z x y
/ \ / \ / \ / \
z t2 t3 y t1 t3 t4 t2
/ \ / \
t3 t4 t4 t2
以z为轴右旋转 以z为轴左旋转
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct TreeNode
{
int data;
struct TreeNode* left;
struct TreeNode* right;
}TreeNode;
// 创建节点
TreeNode* create_node(int data)
{
TreeNode* node = malloc(sizeof(TreeNode));
node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}
// 求高度
int high_tree(TreeNode* root)
{
if(NULL == root) return 0;
int lh = high_tree(root->left);
int rh = high_tree(root->right);
return lh>rh ? lh+1 : rh+1;
}
// 左右子树高度差
int diff_high(TreeNode* root)
{
return high_tree(root->left) - high_tree(root->right);
}
// 左旋转
TreeNode* left_rotate(TreeNode* x)
{
TreeNode* y = x->right;
TreeNode* t2 = y->left;
y->left = x;
x->right = t2;
return y;
}
// 右旋转
TreeNode* right_rotate(TreeNode* x)
{
TreeNode* y = x->left;
TreeNode* t2 = y->right;
y->right = x;
x->left = t2;
return y;
}
// 自动调整平衡
TreeNode* auto_balance(TreeNode* x)
{
if(NULL == x) return NULL;
int lh = high_tree(x->left);
int rh = high_tree(x->right);
if(lh-rh > 1)
{
if(diff_high(x->left) >= 1)
{
// 情况1 右旋
x = right_rotate(x);
}
else
{
// 情况3 先左旋 再右旋
x->left = left_rotate(x->left);
x = right_rotate(x);
}
}
else if(rh-lh > 1)
{
if(diff_high(x->right) >= 1)
{
// 情况4 先右旋 再左旋
x->right = right_rotate(x->right);
x = left_rotate(x);
}
else
{
// 情况2 左旋
x = left_rotate(x);
}
}
return x;
}
//添加 有序且平衡 返回root 可能会变化也可能不变 优点不用传二级指针
TreeNode* insert_tree(TreeNode* root,int data)
{
// 按有序添加节点
if(NULL == root)
return create_node(data);
if(root->data > data)
root->left = insert_tree(root->left,data);
else
root->right = insert_tree(root->right,data);
// 自动调平衡
root = auto_balance(root);
return root;
}
// 前
void dlr_show(TreeNode* root)
{
if(NULL == root) return;
printf("%d ",root->data);
dlr_show(root->left);
dlr_show(root->right);
}
// 中序
void ldr_show(TreeNode* root)
{
if(NULL == root) return;
ldr_show(root->left);
printf("%d ",root->data);
ldr_show(root->right);
}
/*
删除节点:
1、待删除的是叶子节点,直接删除,重新调整
2、待删除的节点度为1,使用非空子树替换,重新调整
3、待删除的节点度为2,根据左右子树高度,选择高的分支,如果左子树高选择最大节点,如果右子树高选择最小节点替换,重新调整
*/
TreeNode* max_tree_node(TreeNode* root)
{
if(NULL == root) return NULL;
TreeNode* max = root;
while(max->right) max = max->right;
return max;
}
TreeNode* min_tree_node(TreeNode* root)
{
if(NULL == root) return NULL;
TreeNode* min = root;
while(min->left) min = min->left;
return min;
}
TreeNode* del_tree(TreeNode* root,int data)
{
if(NULL == root) return NULL;
if(data == root->data)
{
if(NULL == root->left && NULL == root->right)
{
free(root);
return NULL;
}
if(NULL == root->left)
{
// 左空 替换成右
TreeNode* temp = root->right;
free(root);
return temp;
}
if(NULL == root->right)
{
// 右空 替换成左
TreeNode* temp = root->left;
free(root);
return temp;
}
// 左右非空
int lh = high_tree(root->left);
int rh = high_tree(root->right);
if(lh >= rh)
{
// 找左子树最大节点替换
TreeNode* max_node = max_tree_node(root->left);
root->data = max_node->data;
root->left = del_tree(root->left,root->data);
}
else
{
// 找右子树最小节点替换
TreeNode* min_node = min_tree_node(root->right);
root->data = min_node->data;
root->right = del_tree(root->right,root->data);
}
return root;
}
if(data <root->data)
root->left = del_tree(root->left,data);
else
root->right = del_tree(root->right,data);
root = auto_balance(root);
return root;
}
int main(int argc,const char* argv[])
{
TreeNode* root = NULL;
for(int i=0; i<10; i++)
{
root = insert_tree(root,i+1);
}
root = del_tree(root,10);
dlr_show(root);
printf("\n");
ldr_show(root);
}
红黑树
- 是一棵自平衡的有序二叉树,但是它不是根据高度差调整平衡的,而是给节点设置红色或黑色根据颜色关系调整平衡,最终最长路径不会超过最短路径的两倍,接近平衡
红黑树的特性:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色(这里叶子节点,是指为空(NIL或NULL)的叶子节点)
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
优点:
- 插入、删除节点时,效率比AVL树高
缺点:
-
没有AVL树均匀,查找效率没有AVL树那么高,但也不低
-
红黑树的平均性能是所有平衡二叉树中最优秀的,应用较广
哈夫曼树:
基本概念
路径长度
- 从一个节点到另一个节点之间经过的路径条目数
- 根节点到第L层节点路径长度为 L-1
树的路径长度
- 从根节点到每个节点的路径长度之和
节点的权
- 若将树中的节点赋予一个有某种含义的数值,则称该数值为该节点的权
节点的带权路径长度
- 从根节点出发到该节点之间的路径长度与该节点的权的乘积
树的带权路径长度
-
所有的叶子节点的带权路径长度之和,称为WPL
- WPL是衡量一棵带权二叉树优劣的重要指标
-
哈夫曼树:WPL最小的带权二叉树
示例:
成绩 <60 60~69 70~79 80~89 90~100
等级 E D C B A
比例 5% 15% 40% 30% 10%
普通二叉树的WPL: 5+2* 15+3* 40+4* 30+4*10 = 315
优化后的WPL:3* 5+3* 15+2* 40+2* 30+2*10 = 220
哈夫曼树WPL:40+30* 2+15* 3+5* 4+10*4 = 205
构建哈夫曼树
- 1、把n个带权节点存入一个集合M中,每个节点的左右子树置空
- 2、从M中选择根节点权值最小的两个节点作为左右子树构建成一颗新的二叉树,且新的根节点的权值为左右子树权值之和
- 3、从M中删除刚刚选择的两个节点,把新得到的根节点加入M中
- 4、重复2、3步骤,直到M中只剩下一棵树,即是哈夫曼树
哈夫曼编码:
目的
- 解决当年远距离数据传输(电报)的最优解问题
待发送的文字: BACA DEFE
构建
-
方法1:
- 转成二进制发送A 0000 B 0001 C 0010 共32个字符
-
方法2:
-
假设:A27 B8 C15 D15 E30 F5
-
1、根据字母出现的频率,构建哈夫曼树
-
2、规定哈夫曼树的左分支为0,右分支为1,从根节点到每个叶子节点经过的路径组成的0和1的序列就是该对应字符的哈夫曼编码
-
-
B A C A D E F E
-
1001 01 101 01 00 11 1000 11 总共21个字符
应用
- 数据压缩、文件压缩的其中一种方式