在求Dijkstr最短路径的时候,我们会用到索引优先队列,因为堆中的值会随着遍历过程中被更新,这就需要一个支持定点更新的堆数据结构。从而保证log(n)的复杂度需求
public class IndexPriorityQueue<T> where T : IComparable<T>
{
private int MaxSize;
private int n;
private T[] Keys;
private int[] heapToIndex;
private int[] indexToHeap;
public IndexPriorityQueue(int maxLen)
{
MaxSize = maxLen;
Keys = new T[MaxSize + 1];
heapToIndex = new int[MaxSize + 1];
indexToHeap = new int[MaxSize + 1];
for (int i = 0; i < MaxSize; ++i)
indexToHeap[i] = -1;
n = 0;
}
public void Enqueue(int index, T item)
{
if (indexToHeap[index] != -1)
return;
n++;
indexToHeap[index] = n;
heapToIndex[n] = index;
Keys[index] = item;
Swim(n);
}
public T Dequeue()
{
int min = heapToIndex[1];
Swap(1, n--);
Sink(1);
heapToIndex[n + 1] = -1;
indexToHeap[min] = -1;
T item = Keys[min];
Keys[min] = default;
return item;
}
public T RemoveIndex(int index)
{
int k = indexToHeap[index];
Swap(k, n--);
Swim(k);
Sink(k);
indexToHeap[index] = -1;
heapToIndex[n + 1] = -1;
T item = Keys[index];
Keys[index] = default;
return item;
}
public void Change(int index, T item)
{
Keys[index] = item;
Swim(indexToHeap[index]);
Sink(indexToHeap[index]);
}
public int Count => n;
private void Swim(int i)
{
while (i > 1 && Greater(i / 2, i))
{
Swap(i, i / 2);
i = i / 2;
}
}
private void Sink(int i)
{
while (i * 2 <= n)
{
int j = i * 2;
if (j < n && Greater(j, j + 1))
j = j + 1;
if (!Greater(i, j)) break;
Swap(i, j);
i = j;
}
}
private bool Greater(int i, int j)
{
return Keys[heapToIndex[i]].CompareTo(Keys[heapToIndex[j]]) > 0;
}
private void Swap(int i, int j)
{
int temp = heapToIndex[i];
heapToIndex[i] = heapToIndex[j];
heapToIndex[j] = temp;
indexToHeap[heapToIndex[i]] = i;
indexToHeap[heapToIndex[j]] = j;
}
}
算法思路仍然是参考《算法》第四版的。
需要注意一点,如果你需要实现索引最大堆,只需要修改索引T的的比较算法就行。否则你可能需要再额外实现一个最大堆。核心在于如何隔离Greater方法。
书中很多的异常处理,边界检查,这里都没有做。在调用足够正确的情况下,是没问题的。调用不当则会抛出数组越界系统默认的底层异常。