双向更新堆(Prim算法)

时间:2016-03-31 12:25:45

标签: java algorithm heap

在Prim的算法中,建议按以下方式维护不变量:

When a vertice v is added to the MST:
  For each edge (v,w) in the unexplored tree:
      1. Delete w from the min heap.
      2. Recompute the key[w] (i.e. it's value from the unexplored tree
         to the explored one).
      3. Add the value back to the heap.

所以,基本上这涉及从堆中删除(而heapify采用O(logn))然后重新插入(再次O(logn))

相反,如果我使用以下方法:

For each edge (v,w) in the unexplored tree:
    1. Get the position of the node in the heap(array) using HashMap -> O(1)
    2. Update the value in place.
    3. Bubble up or bubble down accordingly. -> O(logn)

它提供了比前一个更好的常量。

有争议的部分是第三部分我应该起泡或下降。

我的实施如下:

public int heapifyAt(int index){
        // Bubble up
        if(heap[index].edgeCost < heap[(int)Math.floor(index/2)].edgeCost){
            while(heap[index].edgeCost < heap[(int)Math.floor(index/2)].edgeCost){
                swap(index, (int)Math.floor(index/2));
                index = (int)Math.floor(index/2);
            }
        }else{
            // Bubble down
            while(index*2 + 2 < size && (heap[index].edgeCost > heap[index*2 + 1].edgeCost|| heap[index].edgeCost > heap[index*2 + 2].edgeCost)){
                if(heap[index*2 + 1].edgeCost < heap[index*2 + 2].edgeCost){
                    //swap with left child
                    swap(index, index*2 + 1);
                    index = index*2 + 1;
                }else{
                    //swap with right child
                    swap(index, index*2 + 2);
                    index = index*2 + 2;
                }
            }
        }
        return index;
    }

我是这样从堆中采摘的:

public AdjNode pluck(){
    AdjNode min = heap[0];
    int minNodeNumber = heap[0].nodeNumber;
    AdjNode toRet = new AdjNode(min.nodeNumber, min.edgeCost);
    heap[0].edgeCost = INF; // set this to infinity, so it'll be at the bottom
                            // of the heap.
    heapifyat(0);
    visited.add(minNodeNumber);
    updatevertices(minNodeNumber); // Update the adjacent vertices
    return toRet;
}

以这种方式更新拔出的顶点:

public void updatevertices(int pluckedNode){
    for(AdjNode adjacentNode : g.list[pluckedNode]){
        if(!visited.contains(adjacentNode.nodeNumber)){  // Skip the nodes that are already visited
            int positionInHeap = map.get(adjacentNode.nodeNumber); // Retrive the position from HashMap
            if(adjacentNode.edgeCost < heap[positionInHeap].edgeCost){
                heap[positionInHeap].edgeCost = adjacentNode.edgeCost; // Update if the cost is better
                heapifyAt(positionInHeap); // Now this will go bottom or up, depending on the value
            }
        }
    }
}

但是当我在大图上执行它时,代码失败了,堆底部有小值,顶部有大值。但是heapifyAt()API似乎工作正常。所以我无法弄清楚我的方法是错误还是我的代码? 此外,如果我用siftDown()替换heapifyAt()API,即构造堆,它可以正常工作,但调用siftDown()并不需要每次更新花费O(n)时间可以在对数时间内处理。

简而言之:是否可以双向更新堆中的值,或算法错误,因为这就是为什么建议首先从堆中删除元素并重新插入

编辑:完整代码:

public class Graph1{
    public static final int INF = 9999999;
    public static final int NEGINF = -9999999;
    static class AdjNode{
        int nodeNumber;
        int edgeCost;
        AdjNode next;

        AdjNode(int nodeNumber, int edgeCost){
            this.nodeNumber = nodeNumber;
            this.edgeCost = edgeCost;
        }
    }

    static class AdjList implements Iterable<AdjNode>{
        AdjNode head;

        AdjList(){
        }

        public void add(int to, int cost){
            if(head==null){
                head = new AdjNode(to, cost);
            }else{
                AdjNode temp = head;
                while(temp.next!=null){
                    temp = temp.next;
                }
                temp.next = new AdjNode(to, cost);
            }
        }

        public Iterator<AdjNode> iterator(){
            return new Iterator<AdjNode>(){
                AdjNode temp = head;

                public boolean hasNext(){
                    if(head==null){
                        return false;
                    }
                    return temp != null;
                }

                public AdjNode next(){
                    AdjNode ttemp = temp;
                    temp = temp.next;
                    return ttemp;
                }

                public void remove(){
                    throw new UnsupportedOperationException();
                }

            };
        }

        public void printList(){
            AdjNode temp = head;
            if(head==null){
                System.out.println("List Empty");
                return;
            }
            while(temp.next!=null){
                System.out.print(temp.nodeNumber + "|" + temp.edgeCost + "-> ");
                temp = temp.next;
            }
            System.out.println(temp.nodeNumber + "|" + temp.edgeCost);
        }

    }


    static class Heap{
        int size;
        AdjNode[] heap;
        Graph g;
        int pluckSize;
        Set<Integer> visited = new HashSet<Integer>();
        HashMap<Integer, Integer> map = new HashMap<>();
        Heap(){

        }
        Heap(Graph g){
            this.g = g;
            this.size = g.numberOfVertices;
            this.pluckSize = size - 1;
            heap = new AdjNode[size];
            copyElements();
            constructHeap();
        }

        public void copyElements(){
            AdjList first = g.list[0];
            int k = 0;
            heap[k++] = new AdjNode(0, NEGINF); //First entry
            for(AdjNode nodes : first){
                heap[nodes.nodeNumber] = nodes;
            }

            for(int i=0; i<size; i++){
                if(heap[i]==null){
                    heap[i] = new AdjNode(i, INF);
                }
            }
        }

        public void printHashMap(){
            System.out.println("Priniting HashMap");
            for(int i=0; i<size; i++){
                System.out.println(i + " Pos in heap :" + map.get(i));
            }
            line();
        }

        public void line(){
            System.out.println("*******************************************");
        }

        public void printHeap(){
            System.out.println("Printing Heap");
            for(int i=0; i<size; i++){
                System.out.println(heap[i].nodeNumber + " | " + heap[i].edgeCost);
            }
            line();
        }

        public void initializeMap(){
            for(int i=0; i<size; i++){
                map.put(heap[i].nodeNumber, i);
            }
        }

        public void swap(int one, int two){
            AdjNode first = heap[one];
            AdjNode second = heap[two];
            map.put(first.nodeNumber, two);
            map.put(second.nodeNumber, one);

            AdjNode temp = heap[one];
            heap[one] = heap[two];
            heap[two] = temp;
        }

        public void constructHeap(){
            for(int i=size-1; i>=0; i--){
                int temp = i;
                while(heap[temp].edgeCost < heap[(int)Math.floor(temp/2)].edgeCost){
                    swap(temp, (int)Math.floor(temp/2));
                    temp = (int)Math.floor(temp/2);
                }

            }
            initializeMap();
        }

        public void updatevertices(int pluckedNode){
            for(AdjNode adjacentNode : g.list[pluckedNode]){
                if(!visited.contains(adjacentNode.nodeNumber)){
                    int positionInHeap = map.get(adjacentNode.nodeNumber);
                    if(adjacentNode.edgeCost < heap[positionInHeap].edgeCost){
                        // //System.out.println(adjacentNode.nodeNumber + " not visited, Updating vertice " + heap[positionInHeap].nodeNumber + " from " + heap[positionInHeap].edgeCost + " to " + adjacentNode.edgeCost);
                        // heap[positionInHeap].edgeCost = INF;
                        // //heap[positionInHeap].edgeCost = adjacentNode.edgeCost;
                        // int heapifiedIndex = heapifyAt(positionInHeap);             // This code follows my logic
                        // heap[heapifiedIndex].edgeCost = adjacentNode.edgeCost;      // (which doesnt work)
                        // //heapifyAt(size - 1);
                        heap[positionInHeap].edgeCost = adjacentNode.edgeCost;
                        //heapifyAt(positionInHeap);
                        constructHeap();                                                // When replaced by SiftDown, 
                    }                                                                   // works as charm
                }
            }
        }

        public void printSet(){
            Iterator<Integer> it = visited.iterator();
            System.out.print("Printing set : [");
            while(it.hasNext()){
                System.out.print((int)it.next() + ", ");
            }
            System.out.println("]");
        }

        public AdjNode pluck(){
            AdjNode min = heap[0];
            int minNodeNumber = heap[0].nodeNumber;
            AdjNode toRet = new AdjNode(min.nodeNumber, min.edgeCost);
            heap[0].edgeCost = INF;
            constructHeap();
            visited.add(minNodeNumber);
            updatevertices(minNodeNumber);
            return toRet;
        }

        public int heapifyAt(int index){
            if(heap[index].edgeCost < heap[(int)Math.floor(index/2)].edgeCost){
                while(heap[index].edgeCost < heap[(int)Math.floor(index/2)].edgeCost){
                    swap(index, (int)Math.floor(index/2));
                    index = (int)Math.floor(index/2);
                }
            }else{
                if(index*2 + 2 < size){
                    while(index*2 + 2 < size && (heap[index].edgeCost > heap[index*2 + 1].edgeCost|| heap[index].edgeCost > heap[index*2 + 2].edgeCost)){
                        if(heap[index*2 + 1].edgeCost < heap[index*2 + 2].edgeCost){
                            //swap with left child
                            swap(index, index*2 + 1);
                            index = index*2 + 1;
                        }else{
                            //swap with right child
                            swap(index, index*2 + 2);
                            index = index*2 + 2;
                        }
                    }
                }
            }
            return index;
        }
    }

    static class Graph{
        int numberOfVertices;
        AdjList[] list;

        Graph(int numberOfVertices){
            list = new AdjList[numberOfVertices];
            for(int i=0; i<numberOfVertices; i++){
                list[i] = new AdjList();
            }
            this.numberOfVertices = numberOfVertices;
        }

        public void addEdge(int from, int to, int cost){
            this.list[from].add(to, cost);
            this.list[to].add(from, cost);
        }

        public void printGraph(){
            System.out.println("Printing Graph");
            for(int i=0; i<numberOfVertices; i++){
                System.out.print(i + " = ");
                list[i].printList();
            }
        }

    }


    public static void prims(Graph graph, Heap heap){
        int totalMin = INF;
        int tempSize = graph.numberOfVertices;
        while(tempSize>0){
            AdjNode min = heap.pluck();
            totalMin += min.edgeCost;
            System.out.println("Added cost : " + min.edgeCost);
            tempSize--;
        }
        System.out.println("Total min : " + totalMin);
    }

    public static void main(String[] args) throws Throwable {
        Scanner in = new Scanner(new File("/home/mayur/Downloads/PrimsInput.txt"));
        Graph graph = new Graph(in.nextInt());
        in.nextInt();
        while(in.hasNext()){
            graph.addEdge(in.nextInt() - 1, in.nextInt() - 1, in.nextInt());
        }
        Heap heap = new Heap(graph);
        prims(graph, heap);
    }
}

1 个答案:

答案 0 :(得分:1)

通过正确实现堆,您应该可以向下冒泡。 Heap使用适用于两个方向的顺序保留一组元素,并且上下冒泡基本上是相同的,除了您移动的方向。

至于你的实施,我相信你是正确的但是一个看似很小的问题:索引。

如果你四处寻找堆的数组实现,你会注意到在大多数情况下根位于索引1而不是0.原因是,在1索引数组中你保留了以下关系父p和子c 1 和c 2

heap[i] = p
heap[2 * i] = c1
heap[2 * i + 1] = c2

在一张纸上画一个数组是很简单的,看看这个关系是否成立,如果你在堆上有根[1]。索引1处的根的子节点位于索引2和3处。索引2处的节点的子节点位于索引4和1处。在图5中,索引3处的节点的子节点在索引6和6处。 7,等等。

此关系可帮助您到达i的任何节点的子节点或父节点,而无需跟踪它们的位置。 (即父母在场(i / 2),孩子在2i和2i + 1)

您似乎尝试过的是堆的0索引实现。因此,对于父p和子c 1 和c 2 ,你必须使用下面给出的稍微不同的关系。

heap[i] = p
heap[2 * i + 1] = c1
heap[2 * i + 2] = c2

访问孩子时似乎没问题。例如,索引为0的根的子节点位于索引1和2处。索引1处的节点的子节点位于索引3和2处。如图4所示,索引2处的节点的子节点在索引5&amp; 6,依此类推。但是,访问节点的父节点时会有一个pickle。如果您考虑节点3并占用楼层(3/2),则会获得索引1,其中 是1的父节点。但是,如果您将索引处的节点放在楼层(4/2) )给你索引2,它是索引4的节点的父节点。

显然,父母与子女之间的指数关系的这种适应对两个孩子都不起作用。与1索引堆实现不同,访问父项时不能同时处理两个子项。因此,问题特别在于你的冒泡部分,而不一定与冒泡操作有关。事实上,虽然我还没有测试你的代码,但是冒泡的部分是冒泡的。 heapifyAt函数似乎是正确的。(即除了索引,当然)

现在,您可以继续使用0索引堆并调整代码,以便每当您查找节点的父节点时,您都​​会隐式检查它是否是正确(即不是正确的,而是与左边相反)父母的孩子并使用楼层((i-1)/ 2)如果是的话。检查节点是否是正确的子节点是微不足道的:只要看它是否是偶数。 (即当你用2i + 2索引正确的孩子时,他们将永远是偶数)

但是我建议您采用不同的方法,而不是使用堆的<1>索引数组实现。堆的数组实现的优雅之处在于,您可以将每个节点视为相同,并且您不必根据其索引或位置执行任何不同的操作,堆的根可能是唯一可能的例外。