一种对二叉堆删除最小的优化算法的C++实现

对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)的算法耗时均最小)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值