数据结构部分概念整理(栈、队列、链表、二叉树、图、拓扑排序、动态规划)

一、对栈的操作

栈被称为一种后入先出(LIFO,last-in-first-out)的数据结构。

对栈的主要操作:

入栈使用 push() 方法,出 栈使用 pop() 方法。

peek() 方法则只返回栈顶元素,而不删除它。

1、栈的实现

function Stack() {
    this.dataStore = []
    this.top = 0
    this.push = push //入栈
    this.pop = pop  //出栈
    this.peek = peek //返回栈顶元素
    this.length = length //返回长度
}

function push(item){
    this.dataStore[this.top++] = item
}

function pop(){
    return this.dataStore[--this.top]
}

function peek(){
    return this.dataStore[this.top-1]
}
function length(){
    return this.top
}

let basStack = new Stack()
basStack.push('一号车')
basStack.push('二号车')
basStack.push('三号车')
console.log(basStack.pop())
console.log(basStack.dataStore)
console.log(basStack.peek())
console.log(basStack.length())

二、队列

队列是一种先进先出(First-In-First-Out,FIFO)的数据结构。

function Queue(){
    this.dataStore = []
    this.enqueue = enqueue // 向队尾添加元素
    this.delqueue = dequeue // 删除队首的元素
    this.front = front //获取队首元素
    this.back = back //获取队尾元素
    this.toString = toString
    this.empty = empty
}

function enqueue(item){
    this.dataStore.push(item)
}

function delqueue(){
    return this.dataStore.unshift()
}

function front(item){
    return this.dataStore[0]
}

function back(){
    return this.dataStore[this.dataStore.length-1]
}

function toString(){
    let retStr = ""
    for (let i = 0; i < this.dataStore.length; ++i) {
        retStr += this.dataStore[i] + "\n";
    }
    return retStr
}

function empty(){
    this.dataStore.length = 0
}

三、链表

JavaScript 中数组的主要问题是,它们被实现成了对象,与其他语言(比如 C++ 和 Java) 的数组相比,效率很低

如果你发现数组在实际使用时很慢,就可以考虑使用链表来替代它。除了对数据的随机访 问,链表几乎可以用在任何可以使用一维数组的情况中。如果需要随机访问,数组仍然是 更好的选择。

链表是由一组节点组成的集合。每个节点都使用一个对象的引用指向它的后继。指向另一 个节点的引用叫做链。

1、单向链表

function Node(element){
    this.element = element
    this.next = null
}
function LList(){
    this.head = new Node("head")
    this.find = find
    this.remove = remove
    this.insert = insert
    this.display = display
    this.findPrevious = findPrevious
}

function find(item){
    let currNode = this.head 
    while( currNode.next!== null && currNode!==item){
        currNode = currNode.next
    }
    return currNode
}

function remove(item){
    let preNode = this.findPrevious(item)
    // console.log('preNode',preNode)
    preNode.next = preNode.next.next
}

function insert(item){
    let newNode = new Node(item) 
    let currNode = this.head 
    while( currNode.element!== item &&  currNode.next !== null){
        currNode = currNode.next
    }
    newNode.next = currNode.next
    currNode.next = newNode
}

function display(){
    let currNode = this.head 
    let i = 0
    while( currNode.next!== null ){
        currNode = currNode.next
        console.log('currNode',i, currNode)
        i++
    }
}

function findPrevious(item){
    let currNode = this.head 
    let stop = false
    while( currNode.element!== item && !stop && currNode.next!== null ){
        currNode = currNode.next
        if(currNode.next.element === item){
            stop = true
        }
    }
    return currNode
}

let studentList = new LList()
studentList.insert('学生1')
studentList.insert('学生2')
studentList.insert('学生3')
studentList.insert('学生4')
studentList.insert('学生5')
studentList.insert('学生6')
studentList.remove('学生5')

studentList.display()

2、双向链表

function Node(element){
    this.element = element
    this.next = null
    this.previous = null
}

function LList(){
    this.head = new Node('head')
    this.insert = insert
    this.remove = remove
    this.find = find
    this.display = display
}

function insert(item){
    let currNode = this.head
    while(currNode.next!==null){
        currNode = currNode.next
    }
    let newNode = new Node(item)
    currNode.next = newNode
    newNode.previous= currNode
}
function find(item){
    let currNode = this.head
    while(currNode.next!==null && currNode.element !== item){
        currNode = currNode.next
    }
    return currNode
}

function remove(item){
    let currNode = this.head
    while(currNode.next!==null && currNode.element !== item){
        currNode = currNode.next
    }
    if( currNode.next === null){
        currNode.previous.next = null
        return
    }
    currNode.previous.next = currNode.next
    currNode.next.previous = currNode.previous

}

function display(){
    let currNode = this.head
    while(currNode.next!==null){
        currNode = currNode.next
        console.log(currNode)
    }
}


let studentList = new LList()
studentList.insert('学生1')
studentList.insert('学生2')
studentList.insert('学生3')
studentList.remove('学生2')

// studentList.display()

console.log(studentList.find('学生2'))

3、循环链表

function Node(element){
    this.element = element
    this.next = null
}

function LList(){
    this.head = new Node('head')
    this.head.next = this.head
    this.insert = insert
    this.remove = remove
    this.find = find
    this.display = display
    this.findPrevious = findPrevious
}

function insert(item){
    let currNode = this.head
    while( currNode.next.element!=='head'){
        currNode = currNode.next
    }
    let newNode = new Node(item)
    newNode.next = currNode.next
    currNode.next = newNode
}
function find(item){
    let currNode = this.head
    while( currNode.element !== item && currNode.next.element!=='head'){
        currNode = currNode.next
    }
    return currNode
}

function remove(item){
    let currNode = this.findPrevious(item)
    currNode.next = currNode.next.next
}

function display(){
    let currNode = this.head
    while( currNode.next.element!=='head'){
        currNode = currNode.next
        console.log(currNode)
    }
}

function findPrevious(item){
    let currNode = this.head
    let stop = false
    while( currNode.next.element!=='head' &&  currNode.element !== item && !stop){
        currNode = currNode.next
        if(currNode.next.element === item){
            stop = true
        }
    }
    return currNode
}
let studentList = new LList()
studentList.insert('学生1')
studentList.insert('学生2')
studentList.insert('学生3')
studentList.remove('学生2')

studentList.display()

// console.log(studentList.find('学生2'))

传说在公元 1 世纪的犹太战争中,犹太历史学家弗拉维奥·约瑟夫斯和他的 40 个同胞被罗马士兵包围。犹太士兵决定宁可自杀也不做俘虏,于是商量出了一个自杀方案。他们围成一个圈,从一个人开始,数到第三个人时将第三个人杀死,然后再数,直到杀光所有人。约瑟夫和另外一个人决定不参加这个疯狂的游戏,他们快速地计算出了两个位置,站在那里得以幸存。写一段程序将 n 个人围成一圈,并且第 m 个人会被杀掉,计算一圈人中哪两个人最后会存活。使用循环链表解决该问题。

function Node(element){
    this.element = element
    this.next = null
}

function LList(){
    this.head = new Node('head')
    this.head.next = this.head
    this.currentNode = this.head
    this.insert = insert //插入节点
    this.remove = remove //删除节点
    this.find = find    //寻找节点节点
    this.display = display //遍历链表
    this.findPrevious = findPrevious //查找前一个元素
    this.advance = advance //向前查找
    this.back = back //向前查找
    this.show = show
    this.calcSurvivors = calcSurvivors //计算存活两人位置
}

function insert(item){
    let currNode = this.head
    while( currNode.next.element!=='head'){
        currNode = currNode.next
    }
    let newNode = new Node(item)
    newNode.next = currNode.next
    currNode.next = newNode
}
function find(item){
    let currNode = this.head
    while( currNode.element !== item && currNode.next.element!=='head'){
        currNode = currNode.next
    }
    return currNode
}

function remove(item){
    let currNode = this.findPrevious(item)
    currNode.next = currNode.next.next
}

function display(){
    let currNode = this.currentNode
    let stopItem = currNode.element
    while( currNode.next.element!=='head' && currNode.next.element!== stopItem ){
        currNode = currNode.next
        console.log('currNode2',currNode)
    }
}

function findPrevious(item){
    let currNode = this.head
    let stop = false

    while(  !stop){
        // console.log('currNode', currNode)
        currNode = currNode.next
        if(currNode.next.element === item){
            stop = true
        }
    }
    return currNode
}

function advance(n){
    while( n>0 ){
        this.currentNode = this.findPrevious(this.currentNode.element)
        n--
    }
    return this.currentNode
}
function back(n){
    while( n>0 ){
        this.currentNode = this.currentNode.next
        n--
    }
    return this.currentNode
}
function show(){
    console.log(this.currentNode)
    return this.currentNode
}

function calcSurvivors(m,n) {
    let deathLength = 0
    let i = 1
    while( i<n ){
        studentList.insert(i)
        i++
    }
    while(deathLength < n-2){
        let deathOrder = this.back(m).element
        this.currentNode = this.currentNode.next
        console.log('death', deathOrder )
        this.remove(deathOrder) 
        deathLength++
    }
    return this.currentNode
}


let studentList = new LList()
// studentList.insert('学生1')
// studentList.insert('学生2')
// studentList.insert('学生3')
// studentList.insert('学生4')
// studentList.insert('学生5')
// studentList.insert('学生6')
// studentList.remove('学生2')
// studentList.display()

// console.log(studentList.find('学生2'))
// console.log(studentList.back(7))
console.log(studentList.calcSurvivors(10,40))

studentList.display()

四、二叉树和二叉树查找

二叉查找树由节点组成,所以我们要定义的第一个对象就是 Node,

现在可以创建一个类,用来表示二叉查找树(BST)。我们让类只包含一个数据成员:一个 表示二叉查找树根节点的 Node 对象。该类的构造函数将根节点初始化为 null,以此创建 一个空节点。 BST 先要有一个 insert() 方法,用来向树中加入新节点。这个方法有点复杂,需要着重讲 解。首先要创建一个 Node 对象,将数据传入该对象保存。 其次检查 BST 是否有根节点,如果没有,那么这是棵新树,该节点就是根节点,这个方法 到此也就完成了;否则,进入下一步。 如果待插入节点不是根节点,那么就需要准备遍历 BST,找到插入的适当位置。该过程类 似于遍历链表。用一个变量存储当前节点,一层层地遍历 BST。 进入 BST 以后,下一步就要决定将节点放在哪个地方。找到正确的插入点时,会跳出循 环。查找正确插入点的算法如下。 (1) 设根节点为当前节点。 (2) 如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反 之,执行第 4 步。 (3) 如果当前节点的左节点为 null,就将新的节点插入这个位置,退出循环;反之,继续 执行下一次循环。 (4) 设新的当前节点为原节点的右节点。 (5) 如果当前节点的右节点为 null,就将新的节点插入这个位置,退出循环;反之,继续 执行下一次循环。 有了上面的算法,就可以开始实现 BST 类了。

function Node(data, left, right){ //创建节点node
    this.data = data
    this.left = left
    this.right = right
    this.show = show //查看节点内容
}

function show(){
    console.log(data)
    return this.data
}

function BTS(data){ //二叉查找树类
    this.insert = insert
    this.root = null
    this.inOrder = inOrder
}

function insert(data){   
    let newNode = new Node(data, null, null)
    if( this.root === null ){ //如果根节点为空,直接赋值
        this.root = newNode
    }else { //根节点不为空 
        let currNode = this.root
        let parent
        while (true) {
            parent = currNode
            if( data < currNode.data ){ // 如果插入节点数据小于父节点,就和父节点的左节点比较
                currNode = currNode.left 
                if(currNode === null){ //如果父节点为空,则直接在左节点插入数据,否则
                    parent.left = newNode
                    break
                }
            }else {
                currNode = currNode.right 
                if(currNode === null){
                    parent.right = newNode
                    break
                }
            }
        }
    }
}

let myBTS = new BTS()
myBTS.insert(10)
myBTS.insert(11)
myBTS.insert(18)
myBTS.insert(17)
myBTS.insert(16)
myBTS.insert(19)
myBTS.insert(9)
console.log(myBTS.root)

添加遍历方法

function inOrder(node){ //中序遍历
    if(node!==null){
        inOrder(node.left)
        console.log(' '+node.data)
        inOrder(node.right)
    }
}
function preOrder(node){ // 先序遍历
    if(node!==null){
        console.log(' '+node.data)
        preOrder(node.left)
        preOrder(node.right)
    }
}

function postOrder(node){ // 后序遍历
    if(node!==null){
        postOrder(node.left)
        postOrder(node.right)
        console.log(' '+node.data)
    }
}

查找 BST 上的最小值和最大值非常简单。因为较小的值总是在左子节点上,在 BST 上查 找最小值,只需要遍历左子树,直到找到最后一个节点。

在 BST 上查找最大值,只需要遍历右子树,直到找到最后一个节点,该节点上保存的值即 为最大值。


// 获取最小值
function getMin(){
    let currNode = this.root
    while (currNode.left !== null) {
        currNode = currNode.left
    }
    return currNode
}

function getMax(){
    let currNode = this.root
    while (currNode.right !== null) {
        currNode = currNode.right
    }
    return currNode
}

获取指定值

// 获取指定值
function find(item){
    let currNode = this.root
    while( item !== currNode.data){
        if(item < currNode.data){
            currNode = currNode.left
        }else{
            currNode = currNode.right
        }
    }
    return currNode
}

删除指定节点

 // 查找最小节点
 var findMinNode = function (node) {
    if(node) {
      while (node && node.left !== null) {
        node = node.left            
      }
      return node
    }
    return null
  }
//删除节点
function removeNode(node, data){
    if(node === null){
        return null
    }
    if(data < node.data){ //在左子节点范围 递归左子节点
        node.left = removeNode(node.left,data) 
        return node
    }else if(data > node.data){ //在右子节点范围 递归左子节点
        node.right = removeNode(node.right,data)
        return node
    }
   
    if( data == node.data ){
        if(node.left === null && node.right === null){ //没有左右子节点
            return null
        }
        if(node.left == null ){
            return node.right
        }
        if(node.right == null ){
            return node.left
        }
        if(node.left !== null && node.right !== null){  //有两个子节点
            //因为本二叉树大小是按照左节点<根节点<右节点赋值 
            //所以取右节点
            let currNode = findMinNode( node.right ) 
            // 不可直接赋值节点,不然会丢失原节点的左节点
            // error:node = currNode
            node.data = currNode.data
            //续上右子节点
            node.right = removeNode(node.right, currNode.data)
            return node
        }
    }
}

五、图和图的运算

图由边的集合及顶点的集合组成。

1、图类

乍一看,图和树或者二叉树很像,你可能会尝试用树的方式去创建一个图类,用节点来表 示每个顶点。但这种情况下,如果用基于对象的方式去处理就会有问题,因为图可能增长 到非常大。用对象来表示图很快就会变得效率低下,所以我们要考虑表示顶点和边的其他 方案。

1.1表示顶点

创建图类的第一步就是要创建一个 Vertex 类来保存顶点和边。这个类的作用与链表和二叉 搜索树的 Node 类一样。Vertex 类有两个数据成员:一个用于标识顶点,另一个是表明这 个顶点是否被访问过的布尔值。它们分别被命名为 label 和 wasVisited。这个类只需要一 个函数,那就是为顶点的数据成员设定值的构造函数。Vertex 类的代码如下所示:

function vertex(label){
    this.label = label
}

1.2表示边

function vertex(label){
    this.label = label
}

function Graph(v){
    this.vertices = v
    this.adj = []
    this.edges = 0
    this.addEdge = addEdge
    this.toString = toString
    for(let i = 0;i<this.vertices;i++){
        this.adj[i] = []
        this.adj[i].push("")
    }
}

function addEdge(m,n){
    this.adj[m].push(n)
    this.adj[n].push(m)
}

function toString(m,n){
    for(let i=0;i<this.vertices;i++){
        console.log(i+'=>')
        for(let j=0;j<this.vertices;j++){
            if( this.adj[i][j] != undefined){
                console.log(this.adj[i][j])
            }
        }
    }
}



let g = new Graph(5)
g.addEdge(0,1)
g.addEdge(0,2)
g.addEdge(1,3)
g.addEdge(2,4)
// g.toString()

1.3深度优先搜索算法

深度优先搜索算法比较简单:访问一个没有访问过的顶点,将它标记为已访问,再递归地 去访问在初始顶点的邻接表中其他没有访问过的顶点。 要让该算法运行,需要为 Graph 类添加一个数组,用来存储已访问过的顶点,将它所有元 素的值全部初始化为 false。
 

    this.marked = [] // 是否遍历过
    for(let i=0;i<this.vertices;i++){
        this.marked = false
    }

深度优先搜索包括从一条路径的起始顶点开始追溯,直到到达最后一个顶点,然后回溯, 继续追溯下一条路径,直到到达最后的顶点,如此往复,直到没有路径为止。这不是在搜 索特定的路径,而是通过搜索来查看在图中有哪些路径可以选择

function dfs(v){ //深度优先遍历
    // console.log('this.marked',this.marked)
    this.marked[v] = true 
    if(this.adj[v] !=undefined ){
        console.log('访问节点:',v,this.adj[v])
    }
    for(let i in this.adj[v]){
        let w = this.adj[v][i]
        if(!this.marked[w]){
            this.dfs(w)
        }
    }
}

1.4广度优先搜索

广度优先搜索从第一个顶点开始,尝试访问尽可能靠近它的顶点。本质上,这种搜索在图 上是逐层移动的,首先检查最靠近第一个顶点的层,再逐渐向下移动到离起始顶点最远的层。

广度优先搜索算法使用了抽象的队列而不是数组来对已访问过的顶点进行排序。

其算法的 工作原理如下:

  1. 查找与当前顶点相邻的未访问顶点,将其添加到已访问顶点列表及队列中;
  2. 从图中取出下一个顶点 v,添加到已访问的顶点列表;
  3. 将所有与 v 相邻的未访问顶点添加到队列。

广度优先搜索对应的最短路径

在执行广度优先搜索时,会自动查找从一个顶点到另一个相连顶点的最短路径。例如,要 查找从顶点 A 到顶点 D 的最短路径,我们首先会查找从 A 到 D 是否有任何一条单边路径, 接着查找两条边的路径,以此类推。这正是广度优先搜索的搜索过程,因此我们可以轻松 地修改广度优先搜索算法

1.4.1要查找最短路径,需要修改广度优先搜索算法来记录从一个顶点到另一个顶点的路径。这 需要对 Graph 类做一些修改。

this.edgeTo = []; //保存从一个顶点到下一个顶点的所有边

1.4.2需要一个数组来保存从一个顶点到下一个顶点的所有边。我们将这个数组命名为 edgeTo。因为从始至终使用的都是广度优先搜索函数,所以每次都会遇到一个没有标记的 顶点,除了对它进行标记外,还会从邻接列表中我们正在探索的那个顶点添加一条边到这 个顶点。这是新的 bfs() 函数,

function bfs(s){  //广度优先遍历
    let queue = [] //已访问顶点
    this.marked[s] = true
    queue.push(s) //添加到队列
    while( queue.length > 0 ){
        let v = queue.shift()
        if(v == undefined){
            console.log("访问节点: ",v)
        }
        for(let i in this.adj[v]){
            let w = this.adj[v][i]
            if(!this.marked[w]){
                this.edgeTo[w] = v
                this.marked[w] = true
                console.log('this.adj[v]w',this.adj[v][w])
                queue.push(w)
            }
        }
    }
}

1.4.3用于展示图中连接到不同顶点的路径。函数 pathTo() 创建了一个 栈,用来存储与指定顶点有共同边的所有顶点。以下是 pathTo() 函数的代码,以及一个简 单的辅助函数:

//查找最短路径
function pathTo(s,v){  //用于展示图中连接到不同顶点的路径
    let source = s // 定义起点
    if(!this.hasPathTo(v)){
        return undefined
    }
    let path = []
    console.log('edgeTo',this.edgeTo)
    for (var i = v; i != source; i = this.edgeTo[i]) {
        path.push(i)
    }    
    path.push(s)
    return path
}

function hasPathTo(v){ //
    return this.marked[v]
}

图部分完整代码

function Vertex(label){
    this.label = label
}

function Graph(v){
    this.vertices = v
    this.adj = []
    this.edges = 0 //边数
    this.marked = [] // 是否遍历过
    this.dfs = dfs //深度遍历
    this.bfs = bfs //广度遍历
    this.edgeTo = []; //保存从一个顶点到下一个顶点的所有边
    for(let i=0;i<this.vertices;i++){
        this.adj[i] = []
        this.marked[i] = false
    }
    this.addEdge = addEdge
    this.toString = toString
    this.pathTo = pathTo;
    this.hasPathTo = hasPathTo;
}

function addEdge(m, n){
    // console.log('this.adj', this.adj[m])
    this.adj[m].push(n)
    this.adj[n].push(m)
    this.edges++
}

function toString(){
    for(let i=0;i<this.vertices;i++){
        console.log("adj"+i+":")
        for(let j=0;j<this.adj[i].length;j++){
            console.log(this.adj[i][j])
        }
    }
}

function dfs(v){ //深度优先遍历
    // console.log('this.marked',this.marked)
    this.marked[v] = true 
    if(this.adj[v] !=undefined ){
        console.log('访问节点:',v,this.adj[v])
    }
    for(let i in this.adj[v]){
        let w = this.adj[v][i]
        if(!this.marked[w]){
            this.dfs(w)
        }
    }
}

function bfs(s){  //广度优先遍历
    let queue = [] //已访问顶点
    this.marked[s] = true
    queue.push(s) //添加到队列
    while( queue.length > 0 ){
        let v = queue.shift()
        if(v == undefined){
            console.log("访问节点: ",v)
        }
        for(let i in this.adj[v]){
            let w = this.adj[v][i]
            if(!this.marked[w]){
                this.edgeTo[w] = v
                this.marked[w] = true
                console.log('this.adj[v]w',this.adj[v][w])
                queue.push(w)
            }
        }
    }
}

//查找最短路径
function pathTo(s,v){  //用于展示图中连接到不同顶点的路径
    let source = s // 定义起点
    if(!this.hasPathTo(v)){
        return undefined
    }
    let path = []
    for (var i = v; i != source; i = this.edgeTo[i]) {
        console.log(i,source)
        path.push(i)
    }    
    path.push(s)
    return path
}

function hasPathTo(v){ //
    return this.marked[v]
}

let g = new Graph(5)
g.addEdge(0,1)
g.addEdge(0,4)
g.addEdge(1,2)
g.addEdge(2,3)
g.addEdge(3,4)
g.toString()
console.log('g.dfs(3)',g.bfs(0))

// console.log('g.marked',g.marked)
console.log('pathTo',g.pathTo(0,4))

2、拓扑排序

拓扑排序会对有向图的所有顶点进行排序,使有向边从前面的顶点指向后面的顶点。

拓扑排序算法与深度优先搜索类似。不同的是,拓扑排序算法不会立即输出已访问的顶 点,而是访问当前顶点邻接表中的所有相邻顶点,直到这个列表穷尽时,才将当前顶点压 入栈中。

2.1实现拓扑排序算法

拓扑排序算法被拆分为两个函数。第一个函数 topSort(),会设置排序进程并调用一个辅 助函数 topSortHelper(),然后显示排序好的顶点列表。

主要工作是在递归函数 topSortHelper() 中完成的。这个函数会将当前顶点标记为已访问, 然后递归访问当前顶点邻接表中的每个相邻顶点,标记这些顶点为已访问。最后,将当前顶点压入栈。

七、动态规划

动态规划有时被认为是一种与递归相反 的技术。

递归是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整 个问题。动态规划解决方案从底部开始解决问题,将所有小问题解决掉,然后合并成一个 整体解决方案,从而解决掉整个大问题。

使用递归去解决问题虽然简洁,但效率不高。包括 JavaScript 在内的众多语言,不能高效 地将递归代码解释为机器代码,尽管写出来的程序简洁,但是执行效率低下。但这并不是 说使用递归是件坏事,本质上说,只是那些指令式编程语言和面向对象的编程语言对递归 的实现不够完善,因为它们没有将递归作为高级编程的特性。(凡尔赛的感觉,泪目)

许多使用递归去解决的编程问题,可以重写为使用动态规划的技巧去解决。动态规划方案 通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完 毕,最终的解将会在这个表中很明显的地方被找到,接下来看看斐波那契数列的例子。

7.1斐波那契数列

function dynFib(n) {
    var val = [];
    for (var i = 0; i <= n; ++i) {
        val[i] = 0;
    }
    if (n == 1 || n == 2) {
        return 1;
    }
    else {
        val[1] = 1;
        val[2] = 2;
        for (var i = 3; i <= n; ++i) {
            val[i] = val[i-1] + val[i-2];
        }
        return val[n-1];
    }
}

7.2寻找最长公共子串

该函数的第一部分初始化了两个变量以及一个二维数组。

第二部分构建了用于保存字符匹配记录的表。数组的第一个元素总是被设置为 0。如果两 个字符串相应位置的字符进行了匹配,当前数组元素的值将被设置为前一次循环中数组元 素保存的值加 1。比如,如果两个字符串 "back" 和 "cace",当算法运行到第二个字符处 时,那么数值 1 将被保存到当前元素中,因为前一个元素并不匹配,0 被保存在那个元素 中(0+1)。接下来算法移动到下一个位置,由于此时两个字符仍被匹配,当前数组元素将 被设置为 2(1+1)。由于两个字符串的最后一个字符不匹配,所以最长公共子串的长度是 2。最后,如果变量 max 的值比现在存储在数组中的当前元素要小,max 的值将被赋值给这 个元素,变量 index 的值将被设置为 i 的当前值。这两个变量将在函数的最后一部分用于 确定从哪里开始获取最长公共子串。

function recFn(word1, word2){
    let index = 0 //最大值结束坐标
    let max = 0 //最大长度
    let len1 = word1.length
    let len2 = word2.length
    let lscArr = [] // 匹配数组,缓存已有匹配结果
    
    for( let i = 0;i <= len1; i++){    
        lscArr.push(new Array( len2+ 1)) 
        for( let j = 0;j <= len2; j++){    
            lscArr[i][j] = 0 
        }        
    }
    for(let i=0; i<=len1; i++){
        for( let j = 0;j <= len2; j++){    
            if( i===0 || j === 0 ){
                lscArr[i][j] = 0
            }else if( word1[i-1] === word2[j-1] ){
                lscArr[i][j] = lscArr[i-1][j-1] + 1
                if( max <  lscArr[i][j]){
                    index = i
                    max = lscArr[i][j]
                }
            }
        }   
    }

    let str = ""
    for(let i = index-max; i<index; i++){
        str += word1[i]
    }
    return str
}

背包问题:动态规划方案 使用递归方案能解决的问题,都能够使用动态规划技巧来解决,而且还能够提高程序的执 行效率。背包问题绝对可以用动态规划的方式来重写,要做的只是使用一个数组来保存临 时解,直到获得最终的解为止。

function max(a,b){
    console.log(a,b)
    return (a>b)?a:b
}

function dknapsack(capacity,n,size,val){
    let res = []
    for(let i=0;i<=capacity;i++){
        res[i] = []
    }
    for(let i = 0;i<=n;i++){
        for(let w=0; w<=capacity; w++){
            if( i == 0 || w == 0 ){
                res[i][w] = 0
            }else if(size[i-1] <= w ){
                res[i][w] = max(val[i-1] + res[i-1][w-size[i-1]], res[i-1][w])
            }else{
                res[i][w] = res[i-1][w]
            }
        }
    }
    console.log('res',res)
}


let size = [2,2,6,5,4]  //尺寸
let val = [6,3,5,4,6] //货物价值
let capacity = 10  //箱子大小
let n= 5  //货物数量

dknapsack(capacity,n,size,val)


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值