目录
线索二叉树
原理 :利用树节点的n+1个左右空指针指向其遍历序列的前驱和后继(线索)
优点:简化遍历,不需要额外的栈空间,快速访问前驱的后继
哈夫曼树
哈夫曼树定义 :在含有n个带权叶节点的二叉树中,其中带权路径(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树
带权路径长度 :叶子节点×路径长度 的总和
构建方法 :每次选取根节点最小的集合进行两两组合
并查集
用法:判断图中是否有环,判断是否在一个集合中
思想:使用一个parent[]数组存储集合关系,对集合进行并查操作
查 :找x所属集合(返回x所属根节点)
并 :将两个集合合并为一个
为了优化,出现最坏的情况,在合并集合的时候可以按秩合并
cpp
if(rank[x_root]>rank[y_root]){//将x作为根节点合并
parent[y_root]=x_root;
}else{//将y作为根节点合并
rank[x_root]=y_root;
if(rank[x_root]==rank[y_root]){//当秩相等时
rank[y_root]++;
}
}
进一步优化,路径压缩,查找过程中将一个集合路径下的所有节点都挂到集合根节点下面
cpp
int find(int x){//找根节点
if(x==parent[x])return x;//返回根节点
return parent[x]=find(parent[x]);//路径压缩
}
并查集模板代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int parent[10005],rank[10005];
//找根节点
int find(int x){
if(x==parent[x])return x;//返回根节点
return parent[x]=find(parent[x]);//路径压缩
}
//集合合并
void unionset(int x,int y){
int x_root=find(x);
int y_root=find(y);
if(x_root==y_root)return;//在同一个集合中
//按秩合并
if(rank[x_root]>rank[y_root]){
parent[y_root]=x_root;
}
else{
parent[x_root]=y_root;
if(rank[x_root]=rank[y_root]){
rank[y_root]++;
}
}
}
//打印关系函数
void selectdots(){
for(int i=1;i<=5;i++){
printf("%d的根节点=%d\n",i,parent[i]);
}
}
int main(){
//初始化
for(int i=0;i<100;i++){
parent[i]=i;
rank[i]=0;
}
//初始化两个集合 A{1 ←2 ←3} B{4 ←5} ;
int con[3][2]={{2,1},{3,2},{5,4}};
for(int i=0;i<3;i++){
unionset(con[i][0],con[i][1]);//建立集合
}
printf("A{1 ←2 ←3} B{4 ←5}两个集合没有合并:\n");
selectdots();
printf("2和4点是否有关系:");
if(find(2)==find(4)){
cout<<true<<endl;;
}else{
cout<<false<<endl;
}
printf("\n\n增加3 ←5关系:\n");
unionset(5,3);
selectdots();
printf("2和4点是否有关系:");
if(find(2)==find(4)){
cout<<true<<endl;;
}else{
cout<<false<<endl;
}
printf("\n\n查询一次5的根节点:\n");
find(5);
selectdots();
return 0;
}
最小生成树
生成树 :包含图中全部顶点的一个极小联通子图
最小生成树 :边权值之和最小的生成树
普利姆算法(选点,适合边稠密):
克鲁斯卡尔(选边,适合边稀疏):
最短路径
Dijkstra算法(带权图,无权图,不适用有负权值的图)
时间复杂度:O(|V|^2)
思想:
1.每次从未标记节点中选择距离出发点最近的节点,标记,收录到最优路径集合中。
2.计算刚加入节点A的邻近节点B的距离(不包含标记的节点),若(节点A的距离+节点A到节点B的边长)<节点B的距离,就进行松弛操作,更新节点B的距离
cpp
if((dis[A]+e[A][B])<dis[B]) dis[B] = dis[A] + e[A][B];
Floyd算法(带权图,无权图,负权图,不能解决负权回路的图)
时间复杂度:O(|V|^3)
算法思想 :最开始允许经过1号中转,求任意两点最短距离中转,接下来只允许经过2号顶点中转...允许经过1~n号顶点中转
一句话概括:从i号顶点到j号顶点只经过前k号顶点的最短路径
cpp
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(e[i][k]+e[k][j]<e[i][j]){
e[i][j]=e[i][k]+e[k][j];
}
}
}
}
拓扑排序
思想,每次选择入度为0的顶点,加入排序序列,并删除所有出边
下面拓扑排序为:1,2,3,4,5
二叉排序树
定义:左子树节点值<根节点<右子树节点值
查找效率:最好O(logn) 最坏O(n)
平衡二叉树
定义:二叉排序树上任一节点的左子树和右子树的高度之差不超过1
缺点 :插入/删除 很容易破坏"平衡"特性,需要频繁调整树的形态。如:插入操作导致不平衡,则需要先计算平衡因子,找到最小不平衡子树(时间开销大),再进行LL/RR/LR/RL调整
红黑树
平衡二叉树:适用于以查为主 ,很少插入/删除的场景
红黑树:适用于频繁插入、删除的场景,实用性更强
折半查找
折半查找时间复杂度:O(log2n)
又称二分查找,仅适用于有序的顺序表,链表不具备随机访问特性,不能使用二分查找
大部分情况下折半查找更优,但是有的情况顺序查找更优(比如待查找元素在第一个位置)
思想:
1.使用双指针low
和high
分别指向有序表头和尾
2.计算 mid = (low+high)/2
将有序表一分为二,判断mid
位置元素和待查找元素temp
的大小关系,通过移动low
和high
保留temp
可能的区间
3.重复第二步,直到low=high
且此时指针指向的值等于temp
查找成功(当low>high查找失败)
散列表
定义 :一直数据结构,特点是数据元素的关键字与其存储地址直接相关,通过散列函数(哈希函数):Addr=H(key)
建立关键字与存储地址的联系
散列查找是一种空间换时间的方法
增删改时间复杂度:O(1)
散列函数:
处理冲突的方法:
堆排序
空间复杂度:O(1)
时间复杂度:O(nlogn)
物理结构:使用顺序存储
逻辑 结构:大根堆根节点大于左子树和右子树,小根堆根节点小于左子树和右子树
1.建立大根堆:
**思路:**把所有非终端节点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整
调整方式 :检查当前节点是否满足 根>=左、右 若不满足,将当前节点与更大的一个孩子互换,若元素互换破坏了下一级的堆,则采用相同的方法继续向下调整(小元素不断"下坠")
2.基于大根堆排序
方法:每一趟将堆顶元素加入有序子序列(与待排序序列中的最后一个元素交换),并将待排序元素序列再次调整为大根堆(小元素不断下坠)