对binary heap进行deletemin操作,是O(logN)的。基于下滤的deletemin,最坏情况下每次操作需要比较2logN次。下面介绍一种算法,最坏情况下每次操作仅需要比较logN+loglogN+O(1)次,然而,数据移动操作的复杂度是相同的:
从root开始,找到一条由最小child构成的路径,直到底部。对这个路径进行binary search,找到能插入最后一个元素的位置,进行与下滤类似的数据移动操作。
这种算法中,找到一条由最小child构成的路径需要logN次比较,对路径进行binary search需要loglogN次比较。但是,这种算法需要额外的logN个元素位置的记忆空间。其一种实现如下:
void deleteMin()
{
int lastEle = currentSize--;
int hole = 1; int child; int level = 0;
path[level] = 1;
for (; hole * 2 <= currentSize; hole = child) //找到由最小的child构成的路径,并存储在path数组中
{
child = hole * 2;
if (child != currentSize && array[child + 1] < array[child])
++child;
path[++level] = child;
}
int low = 0;
int mid; int high = level;
while (low<high) // 对该路径进行binary search
{
mid = (low + count) / 2;
if (array[path[mid]] < array[lastEle])
low = mid + 1;
else if (array[path[mid]] > array[lastEle])
count = mid - 1;
else
break;
}
int target;
if ((array[path[low]] < array[out]) || low == 0) // 对找到的目标位置进行判断
target = low;
else
target = low - 1;
for (int i = 0; i < target; i++) // 进行数据移动
array[path[i]] = std::move(array[path[i + 1]]);
array[path[target]] = std::move(array[lastEle]);
}
尽管每次deleteMin节省了logN-loglogN-O(1)次比较操作,却多了logN次赋值操作,因此,这种算法能否节约运行时间,依赖于整数赋值操作是否比对元素进行比较操作更耗时。运行下面的代码中deleteMin部分,这种算法可以比下滤算法节约23%的时间(在笔者的电脑上,运行时间分别为16255ms和13210ms),而如果把数据类型换成int,则只节约约10%的时间(5560ms与5056ms)。
BinaryHeap<string> bh;
vector<string> data;
const int START = 0;
const int LIMIT = 200000;
for (int i = START; i < LIMIT; i++)
data.push_back(toString(i));
random_shuffle(data.begin(), data.end());
for (auto i : data)
bh.insert(i);
while (!bh.isEmpty())
{
bh.deleteMin();
}
这种算法尚有进一步的改进空间。如果我们每次只找前logN-loglogN层元素的最小路径,然后判断是在这层上还是在这层下进行binary search,则每次deleteMin只消耗logN+logloglogN+O(1)次比较。进一步的,我们可以把这种方法推广到每次deleteMin只需logN+log*N+O(1)次比较。(log*N是让logN=1的迭代次数,例如,log*2=1,log*16=3)。上述改进的实现如下:
void deleteMin()
{
int lastEle = currentSize--;
int hole = 1; int child; int level = 0;
int logN = Log2(currentSize);
int destiLev = logN - Log2(logN);
path[level] = 1;
while( level <= destiLev) //对前logN-log(logN)层寻找最小child路径
{
child = hole * 2;
if (child != currentSize && array[child + 1] < array[child])
++child;
path[++level] = child;
hole = child;
}
int low, high, mid;
int levelOld = level;
if (array[path[levelOld]] > array[lastEle]) //如果目标位置在此层之上,对此层之上的路径进行binary search
{
low = 0; high = levelOld;
}
else //否则,找到此层之下的最小child路径直到底部,并对这一区间的路径进行binary search
{
for (hole = path[levelOld]; hole * 2 <= currentSize; hole = child)
{
child = hole * 2;
if (child != currentSize && array[child + 1] < array[child])
++child;
path[++level] = child;
}
low = levelOld; high = level;
}
while (low<high)
{
mid = (low + high) / 2;
if (array[path[mid]] < array[lastEle])
low = mid + 1;
else if (array[path[mid]] > array[lastEle])
high = mid - 1;
else
break;
}
int target;
if ((array[path[low]] < array[lastEle]) || low == 0)
target = low;
else
target = low - 1;
for (int i = 0; i < target; i++)
array[path[i]] = std::move(array[path[i + 1]]);
array[path[target]] = std::move(array[lastEle]);
}
比较次数为logN+log*N+O(1)的算法,在运行前面提到的代码时,与比较次数为logN+loglogN+O(1)的算法比,节约了2.4%的时间;与比较次数为logN+logloglogN+O(1)的算法比,节约了约0.8%的时间。(分别为12892ms和12990ms,多次试验,logN+log*N+O(1)的算法耗时均最小)