1, 动态规划的本质就是 数学中的递推法. 类似于数列, 例如, 通过找到 f(n) 与 更低维的f(n-1), f(n-2) 之间的关系, 一步步的通过这个关系, 从下往上 得到最终的结果, 注意的是, 同样适用于 f(x,y) 与 f(x-1,y), f(x,y-1), f(x-1,y-1), 这样的
2, 回溯法, 其实也就是一步步试探, 当一条路走不通就回到上一次的选择, 走下一条路, 唯一的一个问题是, 当走不通一条路返回上一次的下一个选择的时候这里, 可能需要保留状态. 做一个记录
3, 有一类问题是, 找出一个长字符串中, 最短的包含另一个字符串中所有字符(包括个数) 的子字符串, 用一般的方法往往超时, 可以使用滑动窗口法. 如下: 用子字符串的左起点和右终点, 不停的滑动来计算, 首先是right 不停地往右, 当这个子字符串OK 的时候, 再从左边往右压缩, 到刚好满足要求的时候, 记录, 然后一直往右这样..
4, 优化算法的关键是充分利用, 解的根本特点进行筛选, 因为符合要求的答案只会出现在符合某种条件的情况下, 所以可以充分地减少运算, 例如求柱状图的最大矩形, 当数字很多的时候, 如果不进行筛选就会运算超时, 可以利用这个特点, 就是说, 解, 肯定是符合 这个解左右两边的值肯定小于整个这一块区域的最小值..
5, 快速排序的核心原理, 就是选择数组中的一个元素做为基准数字, 然后双向滑动比较, 把这个基准数字放到合适的位置, 使得左边的数字全部比这个小(或者等于), 右边的数字全部比这个大(或者等于); 然后再对分割出来的两个小的数组进行递归;
java核心代码如下:
public void quickSort(int[] arr, int s, int t) {
int pivot = arr[(s+t)/2];
int i =s;
int j =t;
while(i<j) {
while(i<j&&arr[j]>pivot) {
j--;
}
while(i<j&&arr[i]<pivot) {
i++;
}
if(i<j) {
if(arr[i]==arr[j]) {
i++;
}else {
arr[i]=arr[j]^arr[i]^(arr[j]=arr[i]);
}
}
}
if(i-1>s) {
quickSort(arr, s, i-1);
}
if(j+1<t) {
quickSort(arr, j+1, t);
}
}
6,当只涉及到存和取的时候, hashset以及hashmap 的算法效率会比arrayList 高很多
7,维护一个递增的栈,这种用于求一堆数字中, 从某一个开始到某一个结束会取得最好的效益的时候使用比较好,
核心如下, 首先一个空栈, 然后添加数字, 每次添加数字的时候, 如果数字比栈顶的数字大, 那就直接添加, 如果小的话, 就弹栈, 直到遇到一个比这个数字小的数字, 在每次弹栈的过程中, 计算弹出的数字跟栈底最小值的差值, 然后更新结果。 一直这样处理, 直到最后返回结果
8, A* 寻路算法的原理就是, 维护一个openList 和一个closeList, openlist代表着所有可能需要做检测的点, closeList代表所有已经检测过的点.
原理就是, 从起点开发往周围扩散, 周围能走的点都加入OpenList待检测, 然后每个待检测的点都会先算出这个点,从起始点到这个点的路径Gcost, 以及从这个点到终点的最小可能路径Hcost, 最小可能路径估计算法有很多,只要能够保证这个算法算出来的路径绝对会比实际上这个点走到目标点所需要经过的路径短就行了. 然后每次从OpenList中选出当前gcost+hcost最小的点, 用这个点去做检测, 一旦开始检测, 那么这个点就可以从openList中移除, 然后添加进closeList, 如果这个点就是目标点,那么恭喜, 这个就是最短路径 接下来就是找出这个点的所有相邻,且可以走,且不在CloseList中的点, 然后计算出从当前检测点通过这个点的gcost+hcost, 如果这个点不在openlist中就加入openList待检测, 如果在Openlist中, 但是通过当前检测点然后到这个点的gocst+hcost比原先的小,那么就更新这个点的gcost, 代表从这个路径走会比原先的路线更短,
算法如下
public ArrayList<Grid> AFind(Grid gStart, Grid gEnd){
ArrayList<Grid> openList = new ArrayList<Grid>();
ArrayList<Grid> closeList = new ArrayList<Grid>();
openList.add(gStart);
while(openList.size()>0){
Grid current = openList.get(0);
float cost = current.gCost+current.hCost;
for (int i=1;i<openList.size();i++){
Grid item = openList.get(i);
float itemCost = item.gCost+item.hCost;
if(itemCost<cost){
current = item;
cost = itemCost;
}
}
if(current==gEnd){
return generatePath(gEnd);
}
openList.remove(current);
closeList.add(current);
List<Grid> list = getNeighbor(current);
for(int i=0;i<list.size();i++){
Grid item = list.get(i);
if(!item.bCanWalk||closeList.contains(item)){
continue;
}
float gcost = current.gCost+getDistance(current, item);
item.gCost= gcost;
if(!openList.contains(item)){
item.hCost= getDistance(item, gEnd);
openList.add(item);
}
}
}
return null;
}
证明如下: 假设最终计算出的路线是M== G1--A--{}-G2, 假设存在一条路线N== G1--A----{}---G2 会比原先的路线更短, 路线N 第一个没有机会作为current被检测的电视S, Fs表示S.Gcost+s.hCost, FG表示G2.gcost+G2.hcost, 因为计算出的最后一条路线是M, 也就是 FG <= Fs, 假设 M路线的最终长度等于 Gm, 路线N 的最终长度是Gn, 然后 Gm= FG<=Fs<=Gn , 证明Gm 一定不会大于Gn,所以 可以验算了上述证明一定可以得出正确答案, 前提是估计函数不会大于实际长度, 也就是GetDistance这个函数返回的值, 一定是理论上的最小值, 不存在现实中会比理论更短的情况
9, floyd 动态规划的算法原理就是, 首先将所有的点编号, 从1 到n, 然后用一个二维数组, arr[i][j] 表示i号点和j号点的距离, 如果两点之间没有直接连接, 那么两点的直接距离就是无穷大, 或者用-1表示, 然后规划如下 dp[k][i][j] 表示 在之允许使用从1到点到k号点的基础下, 从i点到j点的距离, 然后规划方程如下
dp[n][i][j]=min( dp[n-1][i][j], dp[n-1][i][n]+ dp[n-1][n][j] ), 然后就可以求解了
10, 深度优先算法, 也是一种回溯法, 主要的核心代码就是用一个result数组标记当前哪些节点是后续递归中不可以用的, 就好了
int[] result=new int[4];
int[] number =new int[4];
public void DFS(int step){
if(step>4){
System.out.println(number[0]+""+number[1]+""+number[2]+""+number[3]);
return ;
}
for(int i=1;i<=4;i++){
if(result[i-1]==0){
result[i-1]=1;
number[step-1]=i;
DFS(step+1);
result[i-1]=0;
}
}
}
11, 广度优先算法其实跟A* 有那么一点像, A* 算法是可以行走的两点之间的距离并不是固定的, 而广度优先算法是标记相等, 广度优先算法的原理就是, 首先把根节点加入检查点, 然后每一次取出检查列表的第一个点进行检查,然后标记为已检查, 然后把这个点的所有相邻点查找出来, 如果没有检查过就添加进检查队列, 然后逐步找到结果, 其实原理就是, 从根节点逐步往外扩散, 先找到所有第1层的点, 然后找到第二层的点, 然后找到第三层......这样持续下去,直到找到目标点,
12, 判断点在多边形内部的算法是, 以这个点为起始点沿X坐标发出一条射线, 检查这条射线与多边形的每条边的交点, 如果交点数为奇数,则在内部, 当且交点的Y大于等于 边的下沿,且小于边的上沿的时候才算一个合格的交点
13, Dijkstra 算法是用于计算正权图的最短路径, 原理就是, 首先从起点出发, 找到所有可以到达的点, 更新当前状态下到达这些点的最短距离, 把这些点存起来, 然后在所有存起来的点里面找出距离最短的点, 从起点到这个点的最短距离, 肯定就是当前这个距离了, 因为肯定不会存在,因为A= min(数组), 假设存在一条另外的路线到达这个点的距离会更短, 这条路线上第一个没有被检测为最短距离的点为E, 那么很明显, 从起点到这个点的距离肯定是比从起点到E 点的距离更短, 所以这个点的距离才会被检测为最短的。
14, 看一看RVO 算法, VO 算法这些, 动态避障算法
15, 堆排序 就是利用二叉堆进行排序的算法, 首先是对一个完全无序的数组构建堆, 这一步的复杂度是O(n), 然后将堆顶的数值跟堆尾的数值交换, 因为这个堆顶的值肯定是当前最大或者最小的值, 然后再对剩下的数组构建堆, 此时的因为只需要对一个数值进行处理, 复杂度是O(logn) 因为这个需要执行n次, 所以最后的复杂度是O(nlogn), 这种排序的 最差, 最好和平均算法复杂度都是O(nlogn);
16, 归并排序的原理就是 首先将数组经过递归,分割成最小的只有一个元素的数组, 然后对这些排序好的数组, 进行合并, 组后返回一个新的排序后的数组。 时间复杂度 O(nlogn) 但是中途会开辟很多个临时数组所有空间复杂度较高
17, 构造哈夫曼树的核心是保证每一个字符都是这个树的叶子节点, 这样才可以保证, 不会存在一个字符的01 表示被另一个字符所包含. 然后因为是从出现频率最小的字符开始从下往上构造树, 所以出现频率越高的字符就越可能会在树的更上层
18, 最小生成树算法, 是对图生成一个包含所有点的连通图, 且权最小的算法, 算法之一 prim算法: 过程如下, 假设源点集为U, 创建一个点击V, 首先任意取一个点u0 属于U, 加入V, 然后在所有的点v1 属于V 以及 u1 属于U-V中, 选择权最小的边, 然后加入边集, 直到边的size 等于U的长度-1;
19, Kruskal 算法是另一种构造最小生成树的算法, 核心思想是先把所有的点加入, 然后从源边集 中选取权最小的边, 如果这个边, 跟已经选取的边集不构成回路, 就加入, 如果构成回路, 就选择下一条 权重稍多的边进行测试, 直到边的程度等于点集的size-1;
,