dijkstra算法的堆加速和暴力递归算法

Posted 爱了爱了就离谱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dijkstra算法的堆加速和暴力递归算法相关的知识,希望对你有一定的参考价值。

1、Dijkstra算法

    #1、堆加速

      不遍历。头节点更新完数组,将其他节点(出去无穷大和锁死节点)构成小根堆,选出值最小的(同时删掉)。更新节点的值,继续构成小根堆,选出值最小的(同时删掉),更新节点,循环。(小根堆的一个作用就是选最小的值,即堆顶。但是这里无法用系统提供的堆,需要自己写代码,因为系统无法更新确切节点的值并重新构成堆)

    #2、代码

    //改进后的dijkstra算法
    //从head出发,所有能到达的节点,生成到达每个节点的最小路径记录并返回
    public static HashMap<Node, Integer> dijkstra2(Node head, int size) {     //返回哈希表
        NodeHeap nodeHeap = new NodeHeap(size);                               //1443
        nodeHeap.addOrUpdateOrIgnore(head, 0);
        HashMap<Node, Integer> result  = new HashMap<>();
    
        while (~nodeHeap.isEmpty()) {
            NodeRecord record = nodeHeap.pop();
            Node cur = record.node;
            int distance = record.distance;
            for (Edge edge : cur.edges) {        //堆顶后续的节点建立记录
                nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
            }
            result.put(cur, distance);          //弹出堆顶就加记录
        }
        return result;
    }

      NodeHeap方法为:

    public static class NodeHeap {
        private Node[] nodes;
        private HashMap<Node, Integer> heapIndexMap;   //用来查node在堆上的位置
        private HashMap<Node, Integer> distanceMap;      //当前最小距离表
        private int size;

        public NodeHeap(int size) {
            nodes = new Node[size];
            heapIndexMap = new HashMap<>();
            distanceMap = new HashMap<>();
            this.size = 0;
        public boolean isEmpty() {
            return size == 0;
        }

        public void addOrUpdateOrIgnore(Node node, int distance) {
            if (inHeap(node)) {                                                      //如果在堆上
                distanceMap.put(node, Math.min(distanceMap.get(node), distance));
                insertHeapify(node, heapIndexMap.get(node));
            }
            if (!isEntered(node)) {                                  //如果节点未进过堆
                nodes[size] = node;
                heapIndexMap.put(node, distance);
                insertHeapify(node, size++);
            }                                                                  //进来过但是现在不在堆上,忽略
        }
        
        public NodeRecord pop() {
            NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
            swap(0, size - 1);                 //最后一个元素在堆顶
            heapIndexMap.put(nodes[size - 1], -1);
            distanceMap.remove(nodes[size - 1]);
            nodes[size - 1] = null;         //C++需要free()
            heapify(0, --size);
            return nodeRecord;
        }
        
        private void insertHeapify(Node node, int index) {
            while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index-1)/2];
                swap(index, (index-1)/2);
                index = (index - 1)/2;
            }
        }

        private void heapify(int index, int size) {
            int left = index*2 + 1;
            while (left < size) {
                int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distance /*/缺失一部分*/ ? left + 1 : left;
                smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index  /*缺失*/
                if (smallest == index) {
                    break;
                }
                swap(smallest, index);
                index = smallest;
                left = index*2 + 1;
            }
        }

        private boolean isEntered(Node node) {         //node是否进过堆
            return heapIndexMap.containsKey(node);
        }

        private boolean inHeap(Node node) {            //node进过堆标记为-1(现在不在堆)
            reeturn isEntered(node) && heapIndexMap.get(node) != -1;
        }

        private void swap(int index1, int index2) {  //在堆上的node交换,map和堆都交换
            heapIndexMap.put(nodes[index1], index2);
            heapIndexMap.put(nodes[index2], index1);
            Node tmp = nodes[index1];
            nodes[index1] = nodes[index2];
            nodes[index2] = tmp;
        }
    }

 

 

 

2、暴力递归(就是尝试)

    #1、暴力递归

      1.把问题转化为规模缩小了的同类问题的子问题

      2.有明确的不需要继续进行递归的条件(base case)

      3.有得到子问题的结果之后的决策过程

      4.不记录每一个子问题的解

     一定要学会怎么去尝试,因为这是动态规划的基础。

  (1)汉诺塔问题

    #1、典型形式:三根杆子,三个大小不等圆盘(大的不能压小的),移动三个圆盘。

    #2、抽象:有start,end,other,把1~i-1个圆盘移动到other,第 i 个据能到to上了。代码见下:

package class08;

public class Code01_Hanoi {
    public static void hanoi(int n) {
        if (n > 0) {
            func(n, "左", "右", "中");
        }
    }

    public static void func(int i, String start, String end, String other) {
        if (i == 1) {                                                 //base case
            System.out.println("Moce 1 from " + start + " to " + end);
        } else {
            func(i - 1, start, other, end);
            System.out.println("Move " + i + " from " + start + " to " + end);
            func(i - 1, other, end, start);
        }
    }
    
    public static void main(String[] args) {
        int n = 3;
        hanoi(n);
    }
}

 

  (2)打印字符串问题

    #1、打印一个字符串的全部子序列包括空串

      1.其实就是指向任意一个字符时都可以选择要或者不要。代码见下(看第二幅里的方法就行)

                     

 

 

    #2、打印一个字符串的全部排列

      1.取出单个字符进行排列(加上注释的部分就是下面不重复的代码)

                      

    #3、打印一个字符串的全部排列且不出现重复的排列

      1.可以利用上面去掉注释的代码(这一部分居然也称为剪枝),也可以对上面的结果清除重复(慢一些)。

  

  (3)博弈论-取纸牌问题

    #1、懒了,直接粘贴题目吧:

                     

 

     #2、分析

      1.先手的情况 int f (arr, L, R)尝试所有的可能:

//伪代码
int f(arr, L, R)
    if (L == R)   retuurn arr[L]
    max {arr[L] + s(arr, L + 1,R), arr[R] + s(arr, L, R-1) }      //s为后手函数

int s(arr, L, R)
    L == R   return 0;
     min{ f(arr, L + 1, R), f(arr, L, R - 1) }                        //注意这里是min,因为它由对方决定

 

 

  (4)给一个栈,逆序这个栈,不能申请额外的数据结构,只能使用递归函数,如何实现?

    #1、思想:

      1.先定义一个函数,它实现移除栈底元素并返回。

    public static int f(Stack<<Integer> stack) {   //栈进入f
        int result = stack.pop();                           //弹出一个变量到临时变量result 
        if (stack.isEmpty()) {
            return result;
        } else {
            int last = f(stack);                              //其实弹出第二个  又调用原来的f
            stack.push(result);                           
            return last;
        }
    }

      2.逆序

package class08;
import java.util.Stack;    

public class Code04_ReverseStackUsingRecursive {
    
    public static void reverse(Stack<Integer> stack) {
        if (stack.isEmpty()) {
            return;
        }
        int i = f(stack);     //出来最后一个
        reverse(stack);
        stack.push(i);      //压回去
    
    }
    
    public static int f(Stack<Integer> stack) {        //见上文
    //...

    public static void main(String[] args) {
        Stack<Integer> test = new Stack<Integer>();
        test.push(1);
        test.push(2);
        test.push(3);
        reverse(test);
        while (!test.isEmpty()) {
            System.ou.println(test.pop());
        }
    }
}

 

  (5)规定1和A对应,2和B对应,3和C对应...那么数字字符串可以转化为字母字符串,给定一个只有数字字符组成的字符串str,返回有多少种转化结果(注意,有多种解读方案,如111可解读为AAA、KA等)。

     #1、利用数学归纳思想,不管前面的 i 个(假设已经确定),那么当前只考虑编号 i 的译码方式。

      1. i 位置的值为0,那么根本译码不下去,return 0;

      2. i 的值不是零而是1或者2,那么可能转换 i 本身或者 i, i+1一起转换。(其实还可以分,如果i是2,i+1 可以分为 0~6和7~9)

      3. i 位置的值是 3~9 ,只能译码自己。

    #2、代码实现

package class08;

public class Code06_ConvertToLetterString {
  public static int number(String str) {
    
if (str == null || str.length() == 0) {       return 0;     }     return process(str.toCharArray(),0);
  }
//i 位置之前如何转化是确定的 public static int process(char[] chs, int i) { if (i == chs.length) { return 1; } if ( chs[i] == \'0\') { return 0; } if (chs[i] == \'1\') { int res = process(chs, i + 1); //i 自己作为独立部分 if (i + 1 < chs.length) { res += process(chs, i + 2); // i 和 i+1构成整体 } return res; } if (chs[i] == \'2\') { int res = process(chs, i + 1); if (i + 1 < chs.length && (chs[i + 1] >= \'0\' && chs[i + 1] <= \'6\')) { res += process(chs, i + 2); } return res; } return process(chs, i+1); //实际上是 i 为三到九的情况 } public static void main(String[] args) { System.out.println(number("1111233498")); } }

 

  (6)给定两个长度都为N的数组weights和values,weights[i] 和 values[i] 分别代表 i 号 物品的质量和价值。给定一个正数bag表示载重,返回能装下的物品的最大价值。

    #1、暴力算法就是每遇到一个选择要或者不要(可变参数越少越好,便于改成动态规划问题)

                    

 

以上是关于dijkstra算法的堆加速和暴力递归算法的主要内容,如果未能解决你的问题,请参考以下文章

C++分段错误中的堆算法

Dijkstra最短路算法详解

数据结构与算法-暴力递归与回溯

Dijkstra 最短路径算法 秒懂详解

配对堆优化Dijkstra算法小记

使用递归的迷宫深度优先路径算法