下面是本人在看算法书籍时做的笔记。因为前面的比较基础,就从第五章才开始做的笔记
第五章:散列表
个人理解:概念类似于hash索引数据结构,通过散列表函数(hash函数) 计算出int值,然后存储数据 - 复杂度 为 O(1),遇到计算出同样int值时,则将该位置 当做链表来处理(冲突的概念)
1,知识点:
(1),散列函数总是将同样的输入映射到相同的索引位置
(2),散列函数将不同的输入映射到不同的索引, 偶然情况也会将不同的输入映射到相同的索引位置,这时将会把该索引位置当做链表来处理
(3),散列函数知道数组有多大,总会返回有效的索引
(4),散列表 是无序的
2,适用场景:防止重复,web缓存,
3,性能:
(1),一般情况下散列表执行各种操作的时间均为O(1),
O(1) 被称为常量时间,意思指不管散列表多大,查找任意元素的时间都相同
也有最坏情况,即散列表分配不合理,所有元素都存到同一索引位置,故此时操作时间为O(n)
平均情况 最糟情况
查找 O(1) O(n)
插入 O(1) O(n)
删除 O(1) O(n)
(2)查找对比
简单查找的运行时间为 线性时间 ,复杂度为O(n)
二分法的运行时间为 对数时间 ,复杂度为O(log n)
散列表中查找的运行时间为 常量时间 ,复杂度为O(1)
(3)散列表,数组,链表 对比
这里不支持表格,就不列举了
(4),避免冲突,需要 有较低的填装因子和较好的散列函数
填装因子: 散列表包含的元素数 / 位置总数
一般填装因子超过0.7,则需要调整散列表的长度,一般调整散列表的长度时,增大一倍
第六章 广度优先搜索 (breadth first search) BFS
1,图算法 - 广度优先搜索 - 适用于解决最短路径问题 (是否有A到B的路径,如果有,将找到最短路径)
图由节点和边组成, 一个节点可能与多个节点直接相连,这些节点被称为邻居
A-----------------> B
A,B 叫做节点 ,----> 这个叫做边
有向图: A ---> B , A <----- B ,
无向图: A ---- B 意味着两个节点彼此指向对方,即环 A <----> B
2,队列是一种先进先出的数据结构
栈是一种 后进先出的 数据结构
3,广度优先搜索的PHP代码解释:
查找你朋友中有在腾讯上班的人,如果没有则在朋友的朋友中找, 求最短路径,与你关系相对最近
每个人的朋友 - 可以用散列表结构来解决 即 $arr['zhangsan'] = ['lisi','wangwu']; $arr['wangwu'] = ['zhaoliu','songqi'];
逻辑:优先查第一层关系的(你的朋友)
其次查第二层关系,(你朋友的朋友)
再次查第三层关系,(你朋友的朋友的朋友)
。。。。。。
知识点:使用到队列,散列表。 每个人的朋友用散列表存储,查找搜索的列表 用 队列。先进先出
代码表示:
首先关系表
$relation['zhangsan'] = ['lisi','wangwu'];
$relation['wangwu'] = ['zhaoliu','songqi'];
。。。。。
function breadth_first_search($name){
$searchList = array(); //先弄个 搜索队列 - 该出用php数组做队列
$searchList = array_merge($searchList, $relation[$name]); // 把该人的朋友加入到队列中
$searchAlready = array(); // 已搜索过的人,防止死循环
while($searchList){
$people = array_shift($searchList); // 弹出查找人 ,顺序
if(! in_array($searchAlready)){ //未查找过
if( check_in_tencent($people) ){
return $people; // 找到了
}else{
array_push($people, $searchAlready); // 加入已查找列表
$searchList = array_merge($searchList, $relation[$people]); // 将该的朋友 加入搜索队列中
}
}
}
return ""; //没找到
}
function check_in_tencent($name){
// 随便写个逻辑 判断是不是在腾讯,不是此处讲的重点
}
运行时间:
要找到该人,即意味着将要沿着每条边前行,故运行时间为 O(边数)
还用到了队列,要检查每个人。将一人添加入队列的时间是固定的,为0(1), --- 队列数据类型 插入操作的运行时间为O(1)
要对每个人都进行这样的操作。即为 O(人数)
所以:广度优先搜索的运行时间为 O(人数+边数), 即 O(V+E) 其中 V 为顶点数,E为边数
PS: 还 可以用 Python 脚本写。 先不写了。
第七章 狄克斯特拉算法 Dijkstra
个人理解: 图搜索 分为 有向图,无向图,加权图,负权图 等等 狄克斯特拉算法比广度优先搜索 强在 适用于 计算 加权图
1,知识点
(1),狄克斯特拉算法 对于每条边上的相关数字,称为 权重(weight)
(2),带权重的图称为加权图,不带权重的图称为 非加权图
(3),计算非加权图中的最短路径,可使用 广度优先搜索(BFS),计算加权图中的最短路径可使用狄克斯特拉算法
(4),含有负权边的图,要计算最短路径,不能使用狄克斯特拉算法,应该使用 "贝尔曼-福德算法" 。
2,狄克斯特拉算法 分4个步骤
(1),找到 最便宜的节点,即可在最短时间内到达的节点
(2),更新该节点邻居的开销,即 计算邻居节点的最低开销 然后更新(是否有前往邻居节点的更短路径,如果有则更新)
(3),重复这个过程,直到把所有节点的最低开销都统计过
(4),计算最终路径
3,广度优先搜索(BFS) 跟 狄克斯特拉(Dijkstra) 算法的 概念与区别
BFS 概念逻辑: 一层一层的挨个搜索,定义一个队列,然后 优先搜索第一级别节点,不匹配则将其邻居节点 压入队列,保持好子节点与父节点对应关系,直至循环找到目标节点。然后从子父节点对应关系中 计算出 最短路径
Dijkstra 概念逻辑:从节点列表中找到最便宜的节点,循环计算其邻居节点的开销,如若有比之前更低的开销则更新节点列表,更新子父节点关系,循环处理所有节点。最终从子父节点对应关系中统计出最短路径
3个散列表集合
* 1个存 节点以及开销(不可直接到达的节点,默认开销为无穷大)
* 1个存 节点对应 邻居以及开销
* 1个存 已遍历过的节点
4,JavaScript 代码 实现 狄克斯特拉算法 跟 广度优先算法
/**
* 狄克斯特拉算法
* 最短路径 + 加权重
*
* A 为起点
*
*
* Z 为终点
*
* 3个散列表集合
* 1个存 节点以及开销
* 1个存 节点对应 邻居以及开销
* 1个存 已遍历过的节点
* */
var wuxiongda = 99999;
//节点集合 以及各自开销
var allNode = {
'B' : 5,
'C' : wuxiongda,
'D' : wuxiongda,
'E' : 6,
'F' : wuxiongda,
'G' : wuxiongda,
'Z' : wuxiongda,
};
//各节点的父节点 eg B的父节点是A,'B':A
var allParentNode = {
'B' : 'A',
'E' : 'A',
};
//各节点的 邻居节点以及开销 集合
var graphNodeObj = {
'A' : {
'B' : 5,
'E' : 6,
},
'B' : {
'C' : 16,
'D' : 9,
},
'C' : {
'B' : 16,
'G' : 8,
'Z' : 7,
},
'D' : {
'B' : 9,
'G' : 3,
'Z' : 8,
},
'D' : {
'B' : 9,
'G' : 3,
'Z' : 8,
},
'E' : {
'F' : 3,
'G' : 5,
},
'F' : {
'E' : 3,
'G' : 8,
},
'G' : {
'C' : 1,
'E' : 5,
'F' : 8,
'Z' : 4,
},
};
//已遍历过的节点
var usedList = {};
//计算最短路径算法: 狄克斯特拉算法
function getShortest(){
var node = getNewNode();//获取一个未遍历的 最便宜的 节点
while(node != 'Z'){//非终点
var cost = allNode[node];//该节点 的开销成本
var neighbor = graphNodeObj[node];// 该节点的 邻居
for(var neighNodeKey in neighbor){ //遍历邻居节点
var new_cost = cost + neighbor[neighNodeKey]; //计算从起点 到 该邻居节点的 开销成本
if(allNode[neighNodeKey] > new_cost){ //比较 若发现有较低开销出现时,则更新 节点散列表 里的节点开销 以及父节点散列表里的对应关系
allNode[neighNodeKey] = new_cost; //更新节点散列表
allParentNode[neighNodeKey] = node;//更新 父节点散列表
}
}
usedList[node] = 1;//加入已遍历过的节点散列表
node = getNewNode();
}
}
//获取一个没用过的节点
function getNewNode(){
var rtn = '';
var cost = 99999;
for(var k in allNode){
if(k in usedList){
continue;
}
if(allNode[k] < cost){
cost = allNode[k];
rtn = k;
}
}
return rtn
}
function rtnRoute(list, Node){
if(Node == 'A'){
return 'A';
}
for(var n in list){
if(n==Node){
return n += '-->'+rtnRoute(list,list[n]);
}
}
}
//开始计算
getShortest();
//console.log(allNode);
//console.log(allParentNode);
//计算完毕后 整理路线
var finalRoute = '';
var finalNode = 'Z';
//rtnRoute(allParentNode, finalNode);
finalRoute = rtnRoute(allParentNode,finalNode);
console.log('最短路径 = '+finalRoute);
console.log('最短路径开销成本 = '+allNode[finalNode]);
var graphNodeObj_BFS = {
'A' : {
'B' : 5,
'E' : 6,
},
'B' : {
'C' : 16,
'D' : 9,
},
'C' : {
//'B' : 16,
'G' : 8,
'Z' : 7,
},
'D' : {
//'B' : 9,
'G' : 3,
'Z' : 8,
},
'D' : {
'B' : 9,
'G' : 3,
'Z' : 8,
},
'E' : {
'F' : 3,
'G' : 5,
},
'F' : {
//'E' : 3,
'G' : 8,
},
'G' : {
//'C' : 1,
//'E' : 5,
//'F' : 8,
'Z' : 4,
},
};
//广度优先搜索算法
var allParentNode_BFS = {
'B' : 'A',
'E' : 'A',
};
var usedList_BFS = {};
function breadth_first_search(list, start, end){
var graph = graphNodeObj_BFS[start];
while(graph){
var first = '';
for(var n in graph){
first = n;
delete graph[n];
break;
}
if(!(first in usedList_BFS)){
if(first != end){
// 该改点的邻居 压入 要搜索的队列 start -- 相对于php则会很简单 直接合并数组即可 JS 则为对象
var graphChild = graphNodeObj_BFS[first];
if(graphChild){
for(var nchil in graphChild){
graph[nchil] = graphChild[nchil];
if(!(nchil in allParentNode_BFS)){ //不必多搜索重复节点-因为逻辑道理相同。 若重复节点搜索时 子节点父节点 散列表会混乱
allParentNode_BFS[nchil] = first;
}
}
}
// 该改点的邻居 压入 要搜索的队列 end
}else{
graph = {};
console.log('找到了');
break;
}
}
}
}
breadth_first_search(graphNodeObj,'A','Z');
//console.log(allParentNode_BFS);
var finalRoute_BFS = '';
finalRoute_BFS = rtnRoute(allParentNode_BFS,'Z');
console.log('广度优先搜索计算出来最短路径 = '+finalRoute_BFS);
//console.log();
第八章 贪婪算法
1,个人理解:
贪婪算法是一种近似算法。即 接近最终答案,大致解决问题的算法
贪婪算法:每步都采取最优的做法,即 每步都选择局部最优解,最终得到的就是全部最优解
2,集合覆盖问题,近似算法,贪婪算法
eg:覆盖所有州,多个电台,每个电台可能覆盖一个或多个州,计算出最优的选择电台。
方法:选出一个覆盖了最多未覆盖州的 电台,记录进去,然后再重复第一步,直至覆盖完所有的州
贪婪算法的运行时间为 O(n的2次方) n为电台数
3,NP完全问题,最佳的做法是使用近似算法
/*
* 贪婪算法 - 近似完全算法
*
* 事例:
* 美国N个州,M个电台,每个电台可能覆盖1个或多个州,问:最终选最少的电台覆盖所有州
* **/
//所有的州
var allZhou = {
a:1, b:1, c:1, d:1, e:1, f:1, g:1, h:1, i:1, j:1, k:1, l:1, m:1, n:1, o:1, p:1, q:1, r:1, s:1,t:1,
u:1, v:1, w:1, x:1, y:1, z:1,
};
//所有的电台 -- 个电台对应的包含州
var allDj = {
dj1 : ['q','w','e'],
dj2 : ['r'],
dj3 : ['t','y','u','o','p'],
dj4 : ['a','s','f','a','p','v','o'],
dj5 : ['d','f','g','h'],
dj6 : ['j','a','k'],
dj7 : ['l'],
dj8 : ['z','x','c'],
dj9 : ['v','b','n','l'],
dj10 : ['q','a','z','m','d','p'],
dj11 : ['r','o','s'],
dj12 : ['i','d','l'],
dj13 : ['e','k','v','r'],
dj14 : ['u','p'],
};
var resultDj = [];//结果电台
var finishStatus = false;
while(Object.keys(allZhou).length > 0){
var mostPoint = getMostPoint(allDj, allZhou);
if(!mostPoint || mostPoint==''){
finishStatus = true;
break;
}
resultDj.push(mostPoint);
for(var i in allDj[mostPoint]){
delete allZhou[allDj[mostPoint][i]];
}
if(Object.keys(allZhou).length==0){
finishStatus = true;
}
}
if(finishStatus){
console.log('执行结束');
console.log('执行结果:');
console.log(resultDj);
}
//查找覆盖最多的 点
function getMostPoint(obj1, obj2){
var resultP = '';//结果点
var resultPV = 0;//结果点覆盖的量
for(var i in obj1){
if(typeof obj1[i] == 'function'){ continue; }
var tmpPV = 0;
for(var i2 in obj1[i]){
if(obj1[i][i2] in obj2){
tmpPV += 1;
}
}
if(tmpPV > resultPV){
resultPV = tmpPV;
resultP = i;
}
}
return resultP;
}
第九章 动态规划
1,个人理解
动态规划 是将问题 采用画网格的方式 计算出 最优最终的答案,网格行的排列顺序变化 对计算没有影响
2,最长公共子串
3,小结
在给定约束条件下优化某种指标时,动态规划很有用
问题可分解为离散子问题时,可用动态规划
每种动态规划解决方案都涉及 网格
单元格中的值 通常是要优化的值,每个单元格都是一个子问题
第十章 K最近邻算法 (KNN)
1,计算两点的距离,可使用 毕达哥拉斯 算法
2,回归
回归就是预测结果。
3,可适用于 创建推荐系统。
3.1,OCR -- 光学字符识别
即 计算机可识别照片中的内容,文字
语音识别,人脸识别 等也是基于KNN等简单理念的
一般来说,OCR算法提取线段,点,曲线 等特征
OCR的第一步是查看大量的数字图像并提取特征,这被称为 训练
4,小结:
KNN用于分类和回归,需要考虑最近的邻居
分类就是编组
回归就是预测结果(如数字)
能否挑选合适的特征 事关KNN算法的成败