红黑树:
是一棵自平衡的有序二叉树,但是它不是根据高度差调整平衡的,而是给节点设置红色或黑色,根据颜色关系调整平衡,最终最长路径不会超过最短路径的两倍,接近平衡
红黑树的优点:插入、删除节点时,效率比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:
1、根据字母出现的频率,构建哈夫曼树
假设:A27 B8 C15 D15 E30 F5
2、规定哈夫曼树的最分支为0,右分支为1,从根节点到每个叶子节点经过的路径组成的0和1的序列就是该对应字符的哈夫曼编码
BACA DEFE
100101101010011100011 总共21个字符
应用:数据压缩、文件压缩的其中一种方式
图型结构:Graph
什么是图型结构:由有穷且非空的顶点和顶点之间的边组成的集合
通常表示:G(V,E) G表示一个图结构,V是图中顶点vertex(元素、节点)的集合,E是图中边edge(元素之间的关系)的集合
简单图:不存在顶点到自身的边,且一条边不会重复,这种图称为简单图,数据结构中只研究简单图
无向图:
边用 (A,B) 方式表示顶点A与顶点B之间有边,且图的边是互通的
完全无向图:
在无向图中,任意两个顶点之间都有边,如果有n个顶点的完全无向图中,有 n*(n-1)/2 条边
有向图:
边用 <A,B> 方式仅表示从顶点A到顶点B有边,有向图的边是单向的,且边也称为弧,A叫做弧尾,B叫做弧头
完全有向图:
在有向图中,任意两个顶点都存在方向相反的两条弧,如果有n个顶点的完全有向图中,有 n*(n-1) 条边
稠密图:顶点少边多的图
稀疏图:顶点多边好的图
顶点的度:
依附于某个顶点的边或者弧的数量称为该顶点的度
在有向图中度又分为出度和入度
出度:从顶点出发的弧的数量
入度:其他顶点出发,指向该顶点的弧的数量
路径:顶点到顶点之间经过的边称为路径,边的数量称为路径长度
环:图中某个顶点能最终绕回到自己,那该顶点在环上
回路:专指有向图中,从某点出发,最终能回来
简单路径:所有顶点中不会重复出现相同的路径
图的存储结构:
邻接矩阵:
用一个一维数组存储n个顶点,用一个n*n二维数组存储边
char V[n] = {A,B,C,D,E,F,G,H};
A B C D E F G H
A 0 1 1 1 0 0 0 0
B 1 0 1 0 0 0 0 0
C 0 1 0 0 0 1 1 0
D 0 0 1 0 0 1 0 1
E 0 0 0 0 0 1 1 0
F 0 0 0 1 1 0 1 0
G 0 0 1 0 1 1 0 0
H 0 0 0 1 0 0 0 0
二维数组中E[i][j]的值为1,则表示V[i]顶点到V[j]顶点有边
注意:因为不存在自己到自己的边,左对角线的值为0
如果存储的是无向图,则边数组中数据沿对角线对称,形成对称矩阵,可以考虑使用一维数组进行压缩(参考矩阵压缩
优点:计算出入度很方便
缺点:当图越稀疏时,会非常浪费存储内存
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "list_queue.h"
// 邻接矩阵
typedef struct Graph
{
char* v; // 顶点数组
char* e; // 边的二维数组
int cnt; // 顶点数量
}Graph;
// 创建图
Graph* create_graph(const char* str)
{
// 申请图结构内存
Graph* graph = malloc(sizeof(Graph));
// 计算顶点数量
graph->cnt = strlen(str);
// 申请存储顶点的内存
graph->v = malloc(graph->cnt);
// 存储顶点
strcpy(graph->v,str);
// 申请存储边的内存并且清零
graph->e = calloc(graph->cnt,graph->cnt);
return graph;
}
// 添加边 v1与v2的边
bool add_edge(Graph* graph,char v1,char v2)
{
int x = -1, y = -1;
for(int i=0; i<graph->cnt; i++)
{
if(graph->v[i] == v1) x = i;
if(graph->v[i] == v2) y = i;
}
if(-1==x || -1==y) return false;
graph->e[x*graph->cnt+y]= 1;
// graph->e[y*graph->cnt+x]= 1;// 如果是有向图则注释
return true;
}
void show_graph(Graph* graph)
{
printf(" ");
for(int i=0; i<graph->cnt; i++)
{
printf("%c ",graph->v[i]);
}
printf("\n");
for(int i=0; i<graph->cnt; i++)
{
printf("%c ",graph->v[i]);
for(int j=0; j<graph->cnt; j++)
{
printf("%hhd ",graph->e[i*graph->cnt+j]);
}
printf("\n");
}
}
// 计算顶点的入度
int in_degree(Graph* graph,char v)
{
for(int y=0; y<graph->cnt; y++)
{
if(graph->v[y] == v)
{
int in_cnt = 0;
for(int x=0; x<graph->cnt; x++)
{
if(graph->e[x*graph->cnt+y]) in_cnt++;
}
return in_cnt;
}
}
return -1;
}
// 计算顶点的出度
int out_degree(Graph* graph,char v)
{
for(int x=0; x<graph->cnt; x++)
{
if(graph->v[x] == v)
{
int out_cnt = 0;
for(int y=0; y<graph->cnt; y++)
{
if(graph->e[x*graph->cnt+y]) out_cnt++;
}
return out_cnt;
}
}
return -1;
}
// 对顶点v[i] DFS
void _DFS(Graph* graph,char* vflag,int i)
{
if(vflag[i]) return; // 已被遍历
printf("%c ",graph->v[i]);
vflag[i] = 1;
for(int j=0; j<graph->cnt; j++)
{
// 把顶点v[i]的所有出度顶点进行DFS
if(graph->e[i*graph->cnt+j])
{
_DFS(graph,vflag,j);
}
}
}
// 深度优先
void DFS_show(Graph* graph)
{
char vflag[graph->cnt]; // 顶点标志位 变长数组
memset(vflag,0,graph->cnt); // 清零
for(int i=0; i<graph->cnt; i++)
{
_DFS(graph,vflag,i);
}
}
// 广度优先
void BFS_show(Graph* graph)
{
char vflag[graph->cnt]; // 顶点标志位 变长数组
memset(vflag,0,graph->cnt); // 清零
ListQueue* queue = create_list_queue();
for(int i=0; i<graph->cnt; i++)
{
if(!vflag[i])
{
push_list_queue(queue,i);
vflag[i] = 1;
}
while(!empty_list_queue(queue))
{
int j = head_list_queue(queue);
printf("%c ",graph->v[j]);
pop_list_queue(queue);
for(int y=0; y<graph->cnt; y++)
{
if(graph->e[j*graph->cnt+y] && 0 == vflag[y])
{
push_list_queue(queue,y);
vflag[y] = 1;
}
}
}
}
destroy_list_queue(queue);
}
int main(int argc,const char* argv[])
{
Graph* graph = create_graph("ABCDEFGH");
add_edge(graph,'A','B');
add_edge(graph,'A','D');
add_edge(graph,'A','C');
add_edge(graph,'B','C');
add_edge(graph,'C','F');
add_edge(graph,'D','F');
add_edge(graph,'H','D');
add_edge(graph,'C','G');
add_edge(graph,'G','E');
add_edge(graph,'E','F');
add_edge(graph,'F','G');
show_graph(graph);
printf("in_d:%d\n",in_degree(graph,'F'));
printf("out_d:%d\n",out_degree(graph,'A'));
DFS_show(graph);
printf("\n");
BFS_show(graph);
}
邻接表:
边:
顶点下标
下一条边的地址
顶点:
数据
指向该顶点的第一条边的指针
图:
由顶点组成的数组
顶点数量
优点:节约内存,计算出度方便
缺点:计算入度比较麻烦
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
// 边
typedef struct Edge
{
int index; // 该边到index下标的顶点
struct Edge* next;
}Edge;
// 创建一条到index顶点的边
Edge* create_edge(int index)
{
Edge* edge = malloc(sizeof(Edge));
edge->index = index;
edge->next = NULL;
return edge;
}
// 顶点
typedef struct Vertex
{
char vertex; // 顶点数据
Edge* first; // 从该顶点出发的第一条边
}Vertex;
// 图
typedef struct Graph
{
Vertex* v; // 顶点表
int cnt; // 顶点数量
}Graph;
// 创建图
Graph* create_graph(const char* str)
{
// 申请图所需内存
Graph* graph = malloc(sizeof(Graph));
// 计算顶点数量
graph->cnt = strlen(str);
// 申请存储顶点表所需内存
graph->v = malloc(sizeof(Vertex)*graph->cnt);
// 初始化顶点
for(int i=0; i<graph->cnt; i++)
{
graph->v[i].vertex = str[i];
graph->v[i].first = NULL;
}
return graph;
}
// 添加边
bool add_edge(Graph* graph,char v1,char v2)
{
for(int i=0; i<graph->cnt; i++)
{
if(graph->v[i].vertex == v1)
{
for(int j=0; j<graph->cnt; j++)
{
if(graph->v[j].vertex == v2)
{
// 添加v1 到 v2的边
Edge* edge = create_edge(j);
// 头添加edeg到v1的边链表中
edge->next = graph->v[i].first;
graph->v[i].first = edge;
return true;
}
}
}
}
return false;
}
void show_graph(Graph* graph)
{
for(int i=0; i<graph->cnt; i++)
{
printf("index:%d v:%c e:",i,graph->v[i].vertex);
for(Edge* e=graph->v[i].first; NULL!=e; e=e->next)
{
printf("%c ",graph->v[e->index].vertex);
}
printf("\n");
}
}
// 出度
int out_degree(Graph* graph,char v)
{
for(int i=0; i<graph->cnt; i++)
{
if(graph->v[i].vertex == v)
{
int out_cnt = 0;
for(Edge* e=graph->v[i].first; NULL!=e; e=e->next)
{
out_cnt++;
}
return out_cnt;
}
}
return -1;
}
// 入度
int in_degree(Graph* graph,char v)
{
for(int i=0; i<graph->cnt; i++)
{
if(v == graph->v[i].vertex)
{
int in_cnt = 0;
for(int j=0; j<graph->cnt; j++)
{
for(Edge* e=graph->v[j].first; NULL!=e; e=e->next)
{
e->index == i && in_cnt++;
}
}
return in_cnt;
}
}
return -1;
}
void _DFS(Graph* graph,char* vflag,int i)
{
if(vflag[i]) return;
printf("%c ",graph->v[i].vertex);
vflag[i] = 1;
for(Edge* e=graph->v[i].first; e; e=e->next)
{
_DFS(graph,vflag,e->index);
}
}
void DFS_show(Graph* graph)
{
char vflag[graph->cnt];
memset(vflag,0,graph->cnt);
for(int i=0; i<graph->cnt; i++)
{
_DFS(graph,vflag,i);
}
printf("\n");
}
// BFS
void BFS_show(Graph* graph)
{
}
int main(int argc,const char* argv[])
{
Graph* graph = create_graph("ABCDEFGH");
add_edge(graph,'A','B');
add_edge(graph,'A','D');
add_edge(graph,'A','C');
add_edge(graph,'B','C');
add_edge(graph,'C','F');
add_edge(graph,'D','F');
add_edge(graph,'H','D');
add_edge(graph,'C','G');
add_edge(graph,'G','E');
add_edge(graph,'E','F');
add_edge(graph,'F','G');
show_graph(graph);
printf("out:%d\n",out_degree(graph,'A'));
printf("in:%d\n",in_degree(graph,'X'));
DFS_show(graph);
}
十字链表:
专门用于存储有向图的链式结构
优点:节约内存,计算出入度都方便
邻接多重表:
专门存储无向图的一种数据结构
图的遍历:
深度优先遍历:DFS
每个顶点都要进行DFS,从该顶点出发,不停地往下一个顶点遍历,一直遍历到没有下一个顶点为止,该路径遍历完后,如果还有其他路径继续DFS
DFS需要递归实现
广度优先遍历:BFS
借助队列进行,把每个顶点的每个出度依次进行遍历,再把每个出度的顶点继续进行BFS
类似于树的层序遍历