二分查找
- 需先进行排序再进行查找,非递归形式
- 代码实现
var arr = [1,2,3,4,5,6,7,8];
function binarySearch(arr,target){
var left = 0;
var right = arr.length-1;
while(left<=right){
let mid = parseInt((left+right)/2);
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
right = mid - 1;
}else{
left = mid + 1;
}
}
return -1;
}
console.log(binarySearch(arr,9))
分治算法
- 把复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后的小问题可以简单地直接求解,原问题的解即子问题的解的合并
- 常见的由分治算法解决的问题:二分搜索、大整数乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、最接近点对问题、循环日程表、汉诺塔
- 汉诺塔问题
- 实现思路
- n个盘的情况,将(n-1)个盘子移动到B
- 把最下边的盘子移动到C
- 把B的盘子移动到C
- 代码实现
- 实现思路
function hanoiTower(num,a,b,c){
if(num==1){
document.write("第1个盘子从"+a+" => "+c +"<br />");
}else{
hanoiTower(num-1,a,c,b);
document.write("第"+num+"个盘子从"+a+" => "+c +"<br />");
hanoiTower(num-1,b,a,c);
}
}
hanoiTower(3,"a","b","c")
动态规划
- 将问题划分小问题进行解决从而一步步优化获取最优解的处理方法
- 与分治算法不同的是动态规划适用于动态求解的问题,经分解得到的子问题往往不是相互独立的(即下一个子阶段的基础上进一步求解)
- 背包问题
- 01背包是在M件物品取出若干件放在空间为W的背包里,每件物品的体积为W1,W2至Wn,与之相对应的价值为P1,P2至Pn。01背包是背包问题中最简单的问题。01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。
- 主要思想
- 每次遍历得到的第i个物品根据w[i]和v[i]来确定是否需要将该物品放入背包,即对于给定n个商品设v[i]w[i]分别为第i个物品的价格和重量,bag为背包重量,再令vAll[i][j]表示前i个物品中能够装入重量为i的背包中最大价值
- vAll[i][0]=vAll[0][j]=0
- 当w[i]>j时,vAll[i][j]=vAll[i-1][j](若准备加入的新增商品的容量大于当前背包的容量时,就直接使用上一单元格的值)
- 当j>=w[i]时,vAll[i][j]=max(vAll[i-1][j],v[i]+vAll[i-1][j-w[i]])
- 代码实现
var w = new Array(4);
w[1]=1;w[2]=4;w[3]=3;
var v = new Array(4);
v[1]=1500;v[2]=3000;v[3]=2000;
function solve01Bag(w,v,bag){//bag表示背包的重量
var vAll = new Array(w.length);
//第一行和第一列均为0
for(let i=0;i<vAll.length;i++){
vAll[i]= new Array(bag+1);
vAll[i][0] = 0;
}
for(let i=0;i<bag+1;i++){
vAll[0][i] = 0;
}
// console.log(vAll)
//遍历各个物品和背包容量
for(let i=1;i<vAll.length;i++){
for(let j=1;j<=bag;j++){
if(w[i]>j){
vAll[i][j]=vAll[i-1][j];
}else{
vAll[i][j]=Math.max(vAll[i-1][j],v[i]+vAll[i-1][j-w[i]]);
}
}
}
var max = vAll[vAll.length-1][bag]
document.write("最大价值为"+max);
return max;
}
solve01Bag(w,v,4)
KMP算法
- 字符串查找算法,在一个文本中查找一个模式串第一次出现的位置
- KMP方法利用之前判断信息,通过一个next数组保存模式串中前后最长公共子序列的长度,每次回溯时通过next数组找到前面模式匹配的位置省去了大量的计算时间
- 部分匹配值,前缀后缀的最长公共共有元素长度
- 算法思想
- 先得到模式串的部分匹配表
- 使用方法匹配表完成KMP匹配
- 代码实现
var str1 = "AABCSDJLKBXBDJSBCJK";
var str2 = "AJLKBX";
function kmpNext(str){
var next = new Array(str.length);
next[0]=0;
for(let i=1,j=0;i<str.length;i++){
while(j>0&&str[i]!=str[j]){
j=next[j-1];
}
if(str[i]==str[j]){
j++;
}
next[i]=j;
}
console.log(next)
return next;
}
function kmpMatch(str1,str2,next){
for(let i=0,j=0;i<str1.length;i++){
while(j>0&&str1[i]!=str2[j]){
j=next[j-1];
}
if(str1[i]==str2[j]){
j++;
}
if(j==str2.length){
return i-j+1;
}
}
return -1;
}
var next = kmpNext(str2);
var result = kmpMatch(str1,str2,next);
console.log(result)
贪心算法
-
对问题求解时,在每一步选择中都选择最好或者最优(即最有利的)的选择,从而希望能够导致结果是最好的或者最优
-
所得结果不一定是最优的但都是相对接近最优解的结果
-
集合覆盖问题
- 图解
- 图解
-
代码实现
var broadcast = new Map();
var set1 = new Set();
set1.add("北京");
set1.add("上海");
set1.add("天津");
var set2 = new Set();
set2.add("广州");
set2.add("北京");
set2.add("深圳");
var set3 = new Set();
set3.add("成都");
set3.add("上海");
set3.add("杭州");
var set4 = new Set();
set4.add("上海");
set4.add("天津");
var set5 = new Set();
set5.add("杭州");
set5.add("大连");
broadcast.set("K1",set1);
broadcast.set("K2",set2);
broadcast.set("K3",set3);
broadcast.set("K4",set4);
broadcast.set("K5",set5);
// console.log(broadcast)
var allAreas = new Set();
broadcast.forEach((set)=>{
set.forEach((area)=>{
allAreas.add(area);
})
})
var selects = new Array();
var maxKey = null;
var temp = new Set();//存放key所遍历的广播覆盖的城市和allAreas的交集
while(allAreas.size){
broadcast.forEach((set,key)=>{
temp.clear();
maxKey=null;
set.forEach((area)=>{
temp.add(area);
})
let intersect = new Set([...allAreas].filter(x => temp.has(x)));
console.log(intersect);
//当前maxkey指向的广播站为空或者key指向的数量没有当前大
if(intersect.size>0&&(maxKey==null||intersect.size>broadcast.get(maxKey).size)){
maxKey=key;
}
if(maxKey!=null){
selects.push(maxKey);
broadcast.forEach((set,key)=>{
if(key==maxKey){
set.forEach((area)=>{
allAreas.delete(area);
})
}
})
}
})
}
console.log(selects)
最小生成树(MST)
- 给定一个带权的无向连通图,如何选取一课生成树,使树上所有边的权总和最小
- n个顶点一定有n-1条边,包含全部顶点,n-1条边都在图中
普利姆算法(prim)
- 找到最小连通子图(n个顶点只有n-1条边)
- 算法思想
- 设G=(V ,E)是连通图,T=(U,I)是最小生成树,V,U 是顶点结合,E,D是边的集合
- 若从顶点u开始构造最小生成树,则从集合v中取出顶点u放入集合中,标记顶点v的visited[u]=1;
- 若集合U中顶点ui与集合VU中的顶点vj之间存在边则寻找这些边中权值最小的边,但不能构成回路,将顶点vj放入集合U中,将边(ui,vj)加入到集合中,标记visited[vj]=1;
- 重复以上步骤直至找到n-1条边
- 图示
- 代码实现
class Gragh{
constructor(n,arr) {
this.vertexList = arr ;
this.edges = new Array(n);
for(let i=0;i<n;i++){
this.edges[i]= new Array(n);
for(let j=0;j<n;j++){
this.edges[i][j]=10000;
}
}
this.edgesNum = 0;
}
insertEdge(v1,v2,weight){
this.edges[v1][v2]=weight;
this.edges[v2][v1]=weight;
this.edgesNum++;
}
showGragh(){
for(let i=0;i<this.vertexList.length;i++){
for(let j=0;j<this.vertexList.length;j++){
if(this.edges[i][j]!=undefined){
document.write(this.edges[i][j]+" ");
}else{
document.write("0 ");
}
}
document.write("<br />");
}
}
}
var gragh = new Gragh(7,['A','B','C','D','E','F','G']);
gragh.insertEdge(0,1,5);
gragh.insertEdge(0,6,2);
gragh.insertEdge(0,2,7);
gragh.insertEdge(1,6,3);
gragh.insertEdge(1,3,9);
gragh.insertEdge(2,4,8);
gragh.insertEdge(3,5,4);
gragh.insertEdge(4,5,5);
gragh.insertEdge(4,6,4);
gragh.insertEdge(5,6,6);
// gragh.showGragh();
function prim(gragh,v){//v表示从哪个节点开始
var n = gragh.vertexList.length;
var visited = new Array(n);
for(let i=0;i<n;i++){
visited[i]=0;
}
visited[v]=1;
var h1=-1;//存放临时的最小边的两个节点
var h2=-1;
var minWeight = 10000;
for(let k=1;k<n;k++){
for(let i=0;i<n;i++){//访问过的节点
for(let j=0;j<n;j++){//未访问过的节点
if(visited[i]==1&&visited[j]==0&&gragh.edges[i][j]<minWeight){
minWeight=gragh.edges[i][j];
h1=i;h2=j;
}
}
}
document.write("边<"+gragh.vertexList[h1]+"-"+gragh.vertexList[h2]+">的权值为"+minWeight+"<br />");
visited[h2]=1;
minWeight=10000;
}
}
prim(gragh,0)