How to implement O(logn) decrease-key operation for min-heap based Priority Queue?

AlgorithmHeapPriority QueueDecrease Key

Algorithm Problem Overview


I am working on an application that demonstrates the Djikstra's algorithm, and to use it, I need to restore the heap property when my elements' value is decreased.

The problem regarding the complexity is that when the algorithm changes the value of an element, that element's index in the internal structure (heap in this case) used for the priority queue is unknown. As such, I currently need to do an O(n) search, in order to recover the index, before I can perform an actual decrease-key on it.

Moreover, I am not exactly sure about the actual code needed for the operation. I am using the D-Heap here for my Priority Queue. Pseudocode would help, but I would prefer an example in Java on how this should be done.

Algorithm Solutions


Solution 1 - Algorithm

You can do following: store a hashmap inside your heap that maps your heap values to heap indexes. Then you should extend your usual heap-logic just a bit:

on Swap(i, j): 
    map[value[i]] = j; 
    map[value[j]] = i;

on Insert(key, value): 
    map.Add(value, heapSize) in the beginning;

on ExtractMin: 
    map.Remove(extractedValue) in the end;

on UpdateKey(value, newKey): 
    index = map[value]; 
    keys[index] = newKey; 

BubbleUp(index) in case of DecreaseKey, and BubbleDown/Heapify(index) in case of IncreaseKey, to restore min-heap-property.

Here's my C# implementation: http://pastebin.com/kkZn123m

Insert and ExtractMin call Swap log(N) times, when restoring heap property. And you're adding O(1) overhead to Swap, so both operations remain O(log(n)). UpdateKey is also log(N): first you lookup index in a hashmap for O(1), then you're restoring heap property for O(log(N)) as you do in Insert/ExtractMin.

Important note: using values for index lookup will require that they are UNIQUE. If you're not ok with this condition, you will have to add some uniqueue id to your key-value pairs and maintain mapping between this uniqueue id and heap index instead of value-index mapping. But for Dijkstra it's not needed, as your values will be graph nodes and you don't want duplicate nodes in your priority queue.

Solution 2 - Algorithm

Per this SO question it is unnecessary to have a decrease-key method in order to implement Dijkstra's Algorithm.

You can simply add an item to the priority queue as many times as necessary and keep track of which nodes you have visited to weed out the duplicates. The first time you actually visit a node by popping it off of the queue, you have found the shortest path to that node and can ignore all future occurrences of it on the priority queue.

Having many extra nodes on the priority queue is not much of a problem because it is an O(log N) structure. (You have to do about 20 comparisons for 1 million items and 30 comparison for 1 billion items.)

Edit: Following up on this question later, I'm a bit disappointed by my answer: all of those things will have to come off of the queue unless you do some special canipulations later. Like many things in life, it comes down to how you manage your memory and the costs associated with doing so. But the general point remains: decrease-key is not necessary, even if it might be desirable.

Solution 3 - Algorithm

If you are using c++ stl make_heap()/pop_heap()/push_heap(), there is no way to keep an index from node id to index in the underline heap vector, I think you should implement your own heap functions to achieve O(logn) in Increase-Key/Decrease-key operation.

Solution 4 - Algorithm

I had implemented the same thing. In the MinHeap class, I had added a dictionary that is used to access the item in O(1). And on decrease key, it will be bubbled up in O(logn) time.

class MinHeap:
def __init__(self, array):
    self.heap = self.buildHeap(array)
    self.idx_of_element = {}

def getParentIdx(self, idx):
    return (idx - 1) // 2

def getLeftChildIdx(self, idx):
    return idx * 2 + 1

def getRightChildIdx(self, idx):
    return idx * 2 + 2

def buildHeap(self, array):
    # Write your code here.
    lastIdx = len(array) - 1
    startFrom = self.getParentIdx(lastIdx)
    for i in range(startFrom, -1, -1):
        self.siftDown(i, array)
    return array

# this is min-heapify method
def siftDown(self, idx, array):
    while True:
        l = self.getLeftChildIdx(idx)
        r = self.getRightChildIdx(idx)

        smallest = idx
        if l < len(array) and array[l] < array[idx]:
            smallest = l
        if r < len(array) and array[r] < array[smallest]:
            smallest = r
        
        if smallest != idx:
            array[idx], array[smallest] = array[smallest], array[idx]
            self.idx_of_element[self.heap[idx]], self.idx_of_element[self.heap[smallest]] = self.idx_of_element[self.heap[smallest]], self.idx_of_element[self.heap[idx]]
            idx = smallest
        else:
            break

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjathanasiouView Question on Stackoverflow
Solution 1 - AlgorithmptkvskView Answer on Stackoverflow
Solution 2 - AlgorithmRichardView Answer on Stackoverflow
Solution 3 - AlgorithmzachView Answer on Stackoverflow
Solution 4 - AlgorithmRajView Answer on Stackoverflow