文章目录
查找
二分查找
m i d = l o w + h i g h 2 = l o w + h i g h − l o w 2 mid=\frac{low+high}{2}=low+\frac{high-low}{2} mid=2low+high=low+2high−low
插值查找
m i d = l o w + k e y − a [ l o w ] a [ h i g h ] − a [ l o w ] ( h i g h − l o w ) mid=low+\frac{key-a[low]}{a[high]-a[low]}(high-low) mid=low+a[high]−a[low]key−a[low](high−low)
mid=low+(key-a[low])*(high-low)/(a[high]-a[low]);
斐波那契(黄金分割)查找
把数组填充到一个合适的F(k) 长度(拷贝一个新数组,末尾待填充的部分可以延拓最后一个值)
h
i
g
h
−
l
o
w
=
F
(
k
)
=
F
(
k
−
1
)
+
F
(
k
−
2
)
high-low = F(k) = F(k-1)+F(k-2)
high−low=F(k)=F(k−1)+F(k−2)
mid位于黄金分割点附近。令
m
i
d
=
l
o
w
+
F
(
k
−
1
)
−
1
mid = low+F(k-1)-1
mid=low+F(k−1)−1
往左是:
high=mid-1;
k--;
往右是:
low=mid+1;
k-=2;
哈希表
树
顺序存储二叉树
特点:
- 完全二叉树
- 序号为n的节点 左子节点 2 ∗ n + 1 2*n+1 2∗n+1
- 右子节点 2 ∗ n + 2 2*n+2 2∗n+2
- 父节点 ( n − 1 ) / 2 (n-1)/2 (n−1)/2
线索二叉树
利用二叉链表的空指针域,存放指向结点在某种遍历次序下的前驱和后继节点的指针。
与遍历次序相对应。
赫夫曼编码
9月3日看到 P121 05:19
二叉排序树 BST
Binary Sort/Search Tree
平衡二叉树AVL
- 一颗空树或左右子树高度差的绝对值不超过 1 。并且左右子树都递归平衡
- 常用实现方法:红黑树、AVL、替罪羊树、Treap、伸展树
AVL实现 代码
左旋:
右旋:
生成二叉树:
多路查找树
例如B树、B+树
- 如果二叉树节点很多(海量数据),就需要进行多次IO操作。效率就较慢。
- 同时二叉树的高度也会很高,查找效率也会较低。
多叉树
-
每个节点有更多的数据项和更多的子节点
-
相比较二叉树,能减少树的高度
-
2-3树(最简单的B树结构)
- 所有叶子节点都在同一层。
- 有两个子叶子的节点叫二节点,二节点要么没有子节点,要么有两个。
- 有三个子叶子的节点叫二节点,三节点要么没有子节点,要么有三个。
- 2-3树是由二节点和三节点构成的树。
-
插入规则
- 当插入一个数到某个节点时,不能满足2-3树的特点要求,就需要拆,先向上拆,如果上层满,则拆本层,拆后仍需满足2-3树的基本特点。
- 例如{16,24,12,32,14,26,34,10,8,28,38,20}
- (1) |16|
- (2) |16 | | 24|
- (3) |16 | --> |12| |14|
B树
Balanced Tree
- B树的阶:节点的最多子节点个数
- B树的搜索,从根节点开始,对节点内的关键字序列进行二分查找,如果命中则结束,否则,进入查询关键字所属范围的子节点。直到对应的子节点指针为空,或已经是叶子节点。
- 关键字集合分布在整个树中,即叶子节点和非叶子节点都存放数据。
B+树
- 所有的关键字都在叶子节点的链表中,且链表中的关键字恰好是有序的。
- 不可能在非叶子节点命中
- 更适合文件索引系统
- 和B树各有自己的应用场景,不能说哪个一定更好。
B*树
- 是B+树的变体,在B+树的非根节点和叶子节点再增加指向兄弟的指针。
- B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而B+树的块的最低使用率为1/2。
- 因此,B*树分配新节点的概率比B+树要低,空间使用率更高
图
图的表示方式
邻接矩阵(二维数组):为每个顶点分配n个边的空间,会造成空间的一定损失。
邻接表(数组+链表):只关心存在的边,不关心不存在的边。
深度优先访问
- 访问初始节点,并标记节点v为已访问
- 查找节点v的第一个邻接节点w
- 若w存在,则继续执行4;若w不存在,则回到第1步,访问v的下一个临界节点
- 若w未被访问,则对w进行深度优先遍历递归;
- 查找节点v的w邻接节点的下一个临界节点,转到第3步。
广度优先访问
分治算法
大整数乘法、棋盘覆盖、合并排序、快排等
- 分解
- 解决
- 合并
动态规划
动态规划求解的问题,经分解得到的子问题往往不是相互独立的,而分治往往子问题之间独立。
KMP算法
暴力匹配
一但失配,回溯
KMP
从头到尾彻底理解KMP(2014年8月22日版)—— v_JULY_v
KMP算法—终于全部弄懂了——DK丶S
KMP算法详解-彻底清楚了(转载+部分原创)——sofu6
贪心算法
解不一定是最优的。
应用——集合覆盖问题
找出覆盖所有地区的广播台的集合
广播台 | 覆盖地区 |
---|---|
K1 | “北京”,“上海”,“天津” |
K2 | “广州”,“北京”,“深圳” |
K3 | “成都”,“上海”,“杭州” |
K4 | “上海”,“天津” |
K5 | “杭州”,“大连” |
- 穷举:共n个广播台,广播台的组合为 2 n − 1 2^n-1 2n−1种
- 贪心:优先找最多未覆盖地区的电台
Prim算法和Kruskal算法
应用场景——修路问题
- 有7个村庄ABCDEFG,需要修路将7个村庄联通
- 各个村庄的距离用边线表示(权)
- 如何修路保证各个村庄能连通,且总里程数最短。
思路
修路问题本质就是最小生成树(MST—— Minimum Cost Spanning Tree)
- N个顶点,一定有N-1条边
- 包含全部顶点
- Prim算法和克鲁斯卡(Kruskal)算法
普利姆算法
步骤
- 设 G = ( V , E ) G=(V,E) G=(V,E)是连通网, T = ( U , D ) T=(U,D) T=(U,D)是最小生成树,V、U是顶点集合,E、D是边的集合
- 若从顶点u开始构造最小生成树,则从集合V中取出顶点u放入结合U中,标记顶点v的state = 1(已访问);
- 若集合U中顶点 u i u_i ui与集合V-U中的顶点 v j v_j vj之间存在边,则寻找这些边中权值最小的边,但不能构成回路,将顶点 v j v_j vj假如集合U中,将边 ( u i , v j ) (u_i,v_j) (ui,vj)加入集合D中,标记 s t a t e [ v j ] = 1 state[v_j]=1 state[vj]=1
- 重复步骤2,直到U和V相等,即所有顶点都被标记为已访问过,此时D中有n-1条边
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值
// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start];
// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
// 将第start个顶点的权值初始化为0。
// 可以理解为"第start个顶点到它自身的距离为0"。
weights[start] = 0;
for (int i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;
int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}
// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}
// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
// 获取prims[i]在mMatrix中的位置
int n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min)
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}
public void prim(int start) {
int num = mVexs.length; // 顶点个数
int index=0; // prim最小树的索引,即prims数组的索引
char[] prims = new char[num]; // prim最小树的结果数组
int[] weights = new int[num]; // 顶点间边的权值
// prim最小生成树中第一个数是"图中第start个顶点",因为是从start开始的。
prims[index++] = mVexs[start];
// 初始化"顶点的权值数组",
// 将每个顶点的权值初始化为"第start个顶点"到"该顶点"的权值。
for (int i = 0; i < num; i++ )
weights[i] = mMatrix[start][i];
// 将第start个顶点的权值初始化为0。
// 可以理解为"第start个顶点到它自身的距离为0"。
weights[start] = 0;
for (int i = 0; i < num; i++) {
// 由于从start开始的,因此不需要再对第start个顶点进行处理。
if(start == i)
continue;
int j = 0;
int k = 0;
int min = INF;
// 在未被加入到最小生成树的顶点中,找出权值最小的顶点。
while (j < num) {
// 若weights[j]=0,意味着"第j个节点已经被排序过"(或者说已经加入了最小生成树中)。
if (weights[j] != 0 && weights[j] < min) {
min = weights[j];
k = j;
}
j++;
}
// 经过上面的处理后,在未被加入到最小生成树的顶点中,权值最小的顶点是第k个顶点。
// 将第k个顶点加入到最小生成树的结果数组中
prims[index++] = mVexs[k];
// 将"第k个顶点的权值"标记为0,意味着第k个顶点已经排序过了(或者说已经加入了最小树结果中)。
weights[k] = 0;
// 当第k个顶点被加入到最小生成树的结果数组中之后,更新其它顶点的权值。
for (j = 0 ; j < num; j++) {
// 当第j个节点没有被处理,并且需要更新时才被更新。
if (weights[j] != 0 && mMatrix[k][j] < weights[j])
weights[j] = mMatrix[k][j];
}
}
// 计算最小生成树的权值
int sum = 0;
for (int i = 1; i < index; i++) {
int min = INF;
// 获取prims[i]在mMatrix中的位置
int n = getPosition(prims[i]);
// 在vexs[0...i]中,找出到j的权值最小的顶点。
for (int j = 0; j < i; j++) {
int m = getPosition(prims[j]);
if (mMatrix[m][n]<min)
min = mMatrix[m][n];
}
sum += min;
}
// 打印最小生成树
System.out.printf("PRIM(%c)=%d: ", mVexs[start], sum);
for (int i = 0; i < index; i++)
System.out.printf("%c ", prims[i]);
System.out.printf("\n");
}
//https://www.cnblogs.com/skywang12345/
//https://blog.csdn.net/sunstar8921/article/details/81203445
Kruskal算法
按照权值从小到大的顺序选择n-1条边,并保证这些边不构成回路(!)。
public void kruskal() {
int index = 0; // rets数组的索引
int[] vends = new int[mEdgNum]; // 用于保存"已有最小生成树"中每个顶点在该最小树中的终点。
EData[] rets = new EData[mEdgNum]; // 结果数组,保存kruskal最小生成树的边
EData[] edges; // 图对应的所有边
// 获取"图中所有的边"
edges = getEdges();
// 将边按照"权"的大小进行排序(从小到大)
sortEdges(edges, mEdgNum);
for (int i=0; i<mEdgNum; i++) {
int p1 = getPosition(edges[i].start); // 获取第i条边的"起点"的序号
int p2 = getPosition(edges[i].end); // 获取第i条边的"终点"的序号
int m = getEnd(vends, p1); // 获取p1在"已有的最小生成树"中的终点
int n = getEnd(vends, p2); // 获取p2在"已有的最小生成树"中的终点
// 如果m!=n,意味着"边i"与"已经添加到最小生成树中的顶点"没有形成环路
if (m != n) {
vends[m] = n; // 设置m在"已有的最小生成树"中的终点为n
rets[index++] = edges[i]; // 保存结果
}
}
// 统计并打印"kruskal最小生成树"的信息
int length = 0;
for (int i = 0; i < index; i++)
length += rets[i].weight;
System.out.printf("Kruskal=%d: ", length);
for (int i = 0; i < index; i++)
System.out.printf("(%c,%c) ", rets[i].start, rets[i].end);
System.out.printf("\n");
}
利用群组的思想判断够不够成回路
/*
* 获取i的终点
*/
private int getEnd(int[] vends, int i) {
while (vends[i] != 0)
i = vends[i];
return i;
}
Dijkstra算法
应用——最短路径问题
计算从一个顶点 v 0 v_0 v0到其它顶点的最短距离。
维护一个距离的数组和一个前驱结点。dis[i]表示从
v
0
v0
v0经过
v
p
r
e
[
i
]
v_{pre[i]}
vpre[i]到
v
i
v_i
vi的最短距离
再比较
v
j
v_j
vj,若
w
e
i
g
h
t
(
v
i
,
v
j
)
+
d
i
s
[
j
]
<
d
i
s
[
i
]
weight(v_i,v_j)+dis[j]<dis[i]
weight(vi,vj)+dis[j]<dis[i],则更新
d
i
s
[
i
]
dis[i]
dis[i]
public int[] dijkstra(Graph g,int beg){
int n = g.size;
// 参数空间准备
boolean[] isVisited = new boolean[n];
int[] dis = new int[n];//最小距离
int[] pre = new int[n];//前驱节点
int[][] w = g.edgeWei;
// 初始化
Arrays.fill(dis,Graph.MAX_WEIGHT);
int pt = beg;
for (int i = 0; i < w.length; i++) {
if (w[pt][i]<Graph.MAX_WEIGHT){
pre[i] = pt;
dis[i] = w[pt][i];
}
}
dis[pt] = 0;
int visits = 0;
while (visits<n){
isVisited[pt] = true;
visits++;
//寻找距离最近的节点
int min = Graph.MAX_WEIGHT;
int mInd = -1;
for (int i = 0; i < w.length; i++) {
if (!isVisited[i] && w[pt][i]<min){
min = w[pt][i];
mInd = i;
}
}
if (mInd==-1) {
break;
}
pt = mInd;
//更新最小距离
for (int i = 0; i < w.length; i++) {
if (w[mInd][i]+dis[mInd]<dis[i]){
pre[i] = mInd;
dis[i] = w[mInd][i]+dis[mInd];
}
}
}
for (int i = 0; i < pre.length; i++) {
System.out.println("dis:"+dis[i]+" pre:"+(char)(pre[i]+'A'));
}
return dis;
}
Floyd算法
应用——最短路径问题
计算出各个顶点之间的最短路径
相比较Dijkstra算法要更新的针对某一顶点的一维数组 d i s [ ] dis[] dis[],Floyd算法要计算各个顶点到其它顶点的最短路径,因此要维护一个二维数组。
public int[][] floyd(Graph g){
int n = g.size;
// 参数空间准备
int[][] dis = new int[n][n];
int[][] w = g.edgeWei;
// 初始化
for (int i = 0; i < g.edgeWei.length; i++) {
dis[i] = Arrays.copyOf(w[i],n);
}
//更新
// if weight[j][k]+dis[k][i] < dis[j][i] so dis[j][i]= weight[j][k]+dis[k][i]
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dis[j][k] == INF|| dis[k][i]==INF) continue;
int tmp = w[j][k] + dis[k][i];
if (tmp <dis[j][i]) {
dis[j][i] = tmp;
dis[i][j] = tmp;
}
}
}
}
return dis;
}
骑士周游问题
- 二维数组表示棋盘
- 当前位置设置为已访问,下一步最多有8种可能
- 判断能否走通,能走通,则继续;否则,回溯
- 结束判断条件:走的总步数是否等于棋盘总格子数(要走遍棋盘)。
- 贪心优化:优先往下一步的可能多的位置走。