学习JavaScript数据结构和算法(部分四)

看了 Loiane Groner 著的《学习JavaScript数据结构与算法》一书,自己写篇博客对着敲敲代码:
全文包含十个部分,分别是:数组、栈、队列、链表、集合、字典与散列表、树、图、排序和搜索算法、算法补充知识。


知识点其他部分参考:
学习JavaScript数据结构和算法(部分一)
学习JavaScript数据结构和算法(部分二)
学习JavaScript数据结构和算法(部分三)
学习JavaScript数据结构和算法(部分四)


8、图

这一章的话,学过图论就不难,概念都是书上的。
8.1、创建图类:

function Graph() {
    this.vertices = [];     //存储图中所有点的名字
    this.adjList = new Dictionary();   //存储邻接表(使用顶点名字作为键,邻接顶点作为值)
    this.addVertex = addVertex;  //向图中添加新的顶点
    this.addEdge = addEdge;      //向图中添加新的边 
    this.toString = toString;    //方便输出
}
function addVertex(v){
    this.vertices.push(v);
    this.adjList.set(v,[]);
}
function addEdge(v,w){
    this.adjList.get(v).push(w);
    this.adjList.get(w).push(v); //无向图则两者都需要,有向图可酌情选一边
}
function toString(){
    let s = '';
    for(let i=0; i<this.vertices.length; i++){
        s += this.vertices[i] + '->';
        let neighbors = this.adjList.get(this.vertices[i]);
        for(let j=0; j<neighbors.length; j++){
            s += neighbors[j] + ' ';    
        }   
        s += '\n';
    }
    return s;
}
//测试
var graph = new Graph();
var myVertices = ['A','B','C','D','E','F','G','H','I']; 
myVertices.forEach((i) => { graph.addVertex(i); });
graph.addEdge('A', 'B'); 
graph.addEdge('A', 'C');
graph.addEdge('A', 'D');
graph.addEdge('C', 'D');
graph.addEdge('C', 'G');
graph.addEdge('D', 'G');
graph.addEdge('D', 'H');
graph.addEdge('B', 'E');
graph.addEdge('B', 'F');
graph.addEdge('E', 'I'); 
console.log(graph.toString());
/*输出:
A->B C D 
B->A E F 
C->A D G 
D->A C G H 
E->B I 
F->B 
G->C D 
H->D 
I->E 
*/

8.2、图的遍历:

我们可以访问图中的所有节点。有两种算法可以对图进行遍历:广度优先搜索(BFS)深度优先搜索(DFS)。图遍历可以用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否有环等。

遍历思想:必须追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。对于两种图遍历算法,都需要明确指出第一个被访问的顶点。完全探索一个顶点要求我们查看该顶点的每一条边。对于每一条边所连接的没有被访问过的顶点,将其标注为被发现的,并将其加进待访问顶点列表中。

算法数据结构描述
深度优先搜索通过将顶点存入栈中,顶点是沿着路径被探索的,存在新的相邻顶点就去访问
广度优先搜索队列通过将顶点存入队列中,最先入队列的顶点先被探索

* 广度优先搜索算法会从指定的第一个顶点开始遍历图,先访问其所有的相邻点,就像一次访
问图的一层。换句话说,就是先宽后深地访问顶点,如下图所示:
这里写图片描述

  • 深度优先搜索算法将会从第一个指定的顶点开始遍历图,沿着路径直到这条路径最后一个顶点被访问了,接着原路回退并探索下一条路径。换句话说,它是先深度后广度地访问顶点,如下
    图所示:
    这里写图片描述
    关于二者的应用,可以在牛客网上多刷点题,自然就懂了,其中深度优先遍历需要注意回溯,广度优先遍历需要注意辅助空间。广度优先遍历常用来求最短路经,深度优先遍历用来寻找符合要求的线路。
9、排序算法

关于javascript的排序算法,我专门整理了一篇博客,传送门:十大排序算法—javaScript详解

10、算法

10.1 搜索算法

顺序搜索很简单,for循环就行。我们用的比较多的是二分搜索

这个算法要求被搜索的数据结构已排序。以下是该算法遵循的步骤。
(1) 选择数组的中间值。
(2) 如果选中值是待搜索值,那么算法执行完毕(值找到了)。
(3) 如果待搜索值比选中值要小,则返回步骤1并在选中值左边的子数组中寻找。
(4) 如果待搜索值比选中值要大,则返回步骤1并在选种值右边的子数组中寻找。
以下是其实现:

function binarySearch(array,item){
    let low = 0,
        high = array.length-1;
        mid,element;
    while(low <= high){
        mid = Math.floor((low+high)/2);
        element = array[mid];
        if(element < item){
            low = mid + 1;  
        }else if(element > item){
            high = mid-1;   
        }else{
            return mid; 
        }
    }
    return -1;  //没找到,返回-1
}

二分法应用广泛,很多排序算法就应用二分加递归节省时间,如快排、归并等。递归算法用的太多,都很熟悉,就不单独说了,为何用递归呢?更快吗?递归并不比普通版本更快,反倒更慢。但要知道,递归更容易理解,更容易解决问题,并且它所需的代码量更少。

10.2 动态规划

动态规划(Dynamic Programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化技术。要注意动态规划和分而治之(归并排序和快速排序算法中用到的那种)是不同的方法。分而治之方法是把问题分解成相互独立的子问题,然后组合它们的答案,而动态规划则是将问题分解成相互依赖的子问题。

用动态规划解决问题时,要遵循三个重要步骤:
(1) 定义子问题;
(2) 实现要反复执行而解决子问题的部分(这一步要参考前一节讨论的递归的步骤);
(3) 识别并求解出边界条件。

能用动态规划解决的一些著名的问题如下。
* 背包问题:给出一组项目,各自有值和容量,目标是找出总值最大的项目的集合。这个
问题的限制是,总容量必须小于等于“背包”的容量。
* 最长公共子序列:找出一组序列的最长公共子序列(可由另一序列删除元素但不改变余
下元素的顺序而得到)。
* 矩阵链相乘:给出一系列矩阵,目标是找到这些矩阵相乘的最高效办法(计算次数尽可
能少)。相乘操作不会进行,解决方案是找到这些矩阵各自相乘的顺序。
* 硬币找零:给出面额为d1…dn的一定数量的硬币和要找零的钱数,找出有多少种找零的
方法。
* 图的全源最短路径:对所有顶点对(u, v),找出从顶点u到顶点v的最短路径。

10.3 贪心算法
贪心算法遵循一种近似解决问题的技术,期盼通过每个阶段的局部最优选择(当前最好的解),从而达到全局的最优(全局最优解)。它不像动态规划那样计算更大的格局。比起动态规划算法而言,贪心算法更简单、更快。然而,如我们所见,它并不总是得到最优答案。但是综合来看,它相对执行时间来说,输出了一个可以接受的解。例如0-1背包问题等。

算法原理讲了反而容易糊涂,只能依靠多刷题,自己总结规律,然后对题目培养敏锐度,知道什么时候该用什么算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值