数据结构--最小堆

最小堆

总体表述

开发过程,经常需要对数据集合进行维护。
维护数据结合的数据结构可统称为容器。
最小堆是维护数据集合的容器的一种,
最小堆具备如下性质:
设V[i]为位置i的元素值
1. 对维护的n个元素,顺序存储于索引为1-n的数组位置
2. 对索引i,
若索引2*i <= n,有V[i] <= V[2*i],
若索引2*i+1 <= n,有V[i] <= V[2*i+1]。
最小堆为具备特殊性质的容器。

接口设计

template <typename T>
class MinHeap
{
public:
	MinHeap();
	MinHeap(const Array::DynArray<T>& arrElements_);
	~MinHeap();
	MinHeap(const MinHeap& mhA_);
	MinHeap operator=(const MinHeap& mhA_);

	void Add(const T& nT_);
	void Delete(int nIndex_);
	void Set(int nIndex_, const T& nV_);
	T Get(int nIndex_);
	int Find(std::function<bool(const T& nT_)> fun_);
	int GetSize();
private:
	void BuildHeap(const Array::DynArray<T>& arrElements_);
	void Adjust(int nIndex_);
	void Smaller(int nPos_);
	void Bigger(int nPos_);

private:
	Array::DynArray<T> m_arrElements;
};

实现

构造

MinHeap()

template <typename T>
MinHeap<T>::MinHeap()
{
}

MinHeap(const Array::DynArray& arrElements_)

template <typename T>
MinHeap<T>::MinHeap(const Array::DynArray<T>& arrElements_)
{
	BuildHeap(arrElements_);
}

template <typename T>
void MinHeap<T>::BuildHeap(const Array::DynArray<T>& arrElements_)
{
	m_arrElements.DeleteAll();
	m_arrElements.Add(T());
	int _nSize = arrElements_.GetSize();
	for (int _i = 0; _i < _nSize; _i++)
	{
		m_arrElements.Add(arrElements_[_i]);
	}
	
	for (int _i = _nSize; _i >= 1; _i--)
	{
		Adjust(_i);
	}
}

template <typename T>
void MinHeap<T>::Adjust(int nIndex_)
{
	T _nV = m_arrElements[nIndex_];
	int _nL = nIndex_ * 2;
	int _nR = _nL + 1;
	T _nMin = m_arrElements[nIndex_];
	int _nMinPos = nIndex_;
	if (_nL < m_arrElements.GetSize()
		&& m_arrElements[_nL] < _nMin)
	{
		_nMin = m_arrElements[_nL];
		_nMinPos = _nL;
	}

	if (_nR < m_arrElements.GetSize()
		&& m_arrElements[_nR] < _nMin)
	{
		_nMin = m_arrElements[_nR];
		_nMinPos = _nR;
	}

	if (_nMinPos == nIndex_)
	{
		// do nothing
	}
	else
	{
		m_arrElements[nIndex_] = _nMin;
		m_arrElements[_nMinPos] = _nV;
		Adjust(_nMinPos);
	}
}

拷贝构造

template <typename T>
MinHeap<T>::MinHeap(const MinHeap& mhA_)
{
	m_arrElements = mhA_.m_arrElements;
}

赋值

template <typename T>
typename MinHeap<T> MinHeap<T>::operator=(const MinHeap& mhA_)
{
	if (this == &mhA_)
	{
		return *this;
	}

	m_arrElements = mhA_.m_arrElements;
	return *this;
}

析构

template <typename T>
MinHeap<T>::~MinHeap()
{
}

添加

template <typename T>
void MinHeap<T>::Add(const T& nT_)
{
	// 实际元素个数
	int _nSize = m_arrElements.GetSize() - 1;
	if (_nSize < 0)
	{
		m_arrElements.Add(T());
		m_arrElements.Add(nT_);
		return;
	}

	if (_nSize == 0)
	{
		m_arrElements.Add(nT_);
		return;
	}

	// 新元素索引
	if ((_nSize + 1) % 2 == 0)
	{
		T _nPV = m_arrElements[(_nSize + 1) % 2];
		m_arrElements.Add(_nPV);// 添加元素取其父亲值
	}
	else
	{
		m_arrElements.Add(m_arrElements[_nSize]);// 添加元素取其兄弟值
	}

	// 将添加位置元素设置为指定值
	if (nT_ < m_arrElements[_nSize + 1])
	{
		m_arrElements[_nSize + 1] = nT_;
		Smaller(_nSize + 1);
	}
	else if (nT_ > m_arrElements[_nSize + 1])
	{
		m_arrElements[_nSize + 1] = nT_;
		Bigger(_nSize + 1);
	}
	else
	{
		// do nothing
	}
}

template <typename T>
void MinHeap<T>::Smaller(int nPos_)
{
	// 对以nPos为根子树而言,nPos变小,不会破坏子树各个节点的 已经满足的位置约束。
	if (nPos_ == 1)
	{
		return;
	}

	int _nP = nPos_ / 2;
	if (m_arrElements[_nP] <= m_arrElements[nPos_])
	{
		return;
	}
	else
	{
		T _nV = m_arrElements[_nP];
		m_arrElements[_nP] = m_arrElements[nPos_];// 父节点变小了。
		m_arrElements[nPos_] = _nV;// nPos位置虽然变大了。但新的值仍然小于改变前的值。故意nPos为根子树各个节点仍然满足 位置约束。
		
		Smaller(_nP);
	}
}

template <typename T>
void MinHeap<T>::Bigger(int nPos_)
{
	int _nL = 2 * nPos_;
	int _nR = _nL + 1;
	T _nV = m_arrElements[nPos_];
	T _nMin = m_arrElements[nPos_];
	int _nMinPos = nPos_;
	if (_nL < m_arrElements.GetSize()
		&& m_arrElements[_nL] < _nMin)
	{
		_nMin = m_arrElements[_nL];
		_nMinPos = _nL;
	}

	if (_nR < m_arrElements.GetSize()
		&& m_arrElements[_nR] < _nMin)
	{
		_nMin = m_arrElements[_nR];
		_nMinPos = _nR;
	}

	if (_nMinPos == nPos_)
	{
		return;
	}
	else
	{
		m_arrElements[nPos_] = _nMin;// nPos位置换入一个较小的值。此值不会破坏nPos所有祖先节点的位置约束。
		m_arrElements[_nMinPos] = _nV;// _nMinPos换入一个较大的值
		
		Bigger(_nMinPos);
	}
}

删除

template <typename T>
void MinHeap<T>::Delete(int nIndex_)
{
	int _nSize = m_arrElements.GetSize() - 1;
	if (nIndex_ > _nSize
		|| nIndex_ < 1)
	{
		return;
	}

	if (_nSize == 1)
	{
		m_arrElements.DeleteByIndex(nIndex_);
		return;
	}

	m_arrElements[nIndex_] = m_arrElements[1] - 1;// 变得比最小还小
	Smaller(nIndex_);// 调节后,原nIndex位置元素必位于索引为1位置
	m_arrElements[1] = m_arrElements[_nSize];
	m_arrElements.DeleteByIndex(_nSize);
	Bigger(1);
}

搜索

template <typename T>
int MinHeap<T>::Find(std::function<bool(const T& nT_)> fun_)
{
	int _nSize = m_arrElements.GetSize();
	for (int _i = 1; _i < _nSize; _i++)
	{
		if (fun_(m_arrElements[_i]))
		{
			return _i;
		}
	}

	return -1;
}

正确性证明

MinHeap(const Array::DynArray& arrElements_)

输入:无序的含n个元素的数组
算法目标:
输入数组中n个元素以符合最小堆定义的方式存储于m_arrElements的索引1,...,n处
正确性证明:
算法主体采用循环迭代实现,迭代实现的一般用循环不变式证明正确性

for (int _i = _nSize; _i >= 1; _i--)
{
	Adjust(_i);
}
循环不变式:
 区间[_i+1, _nSize]内各个元素均满足,
对区间内任意位置k的元素,有以下位置约束
若2*k位置元素存在,则有V[k] <= V[2*k]
若2*k+1位置元素存在,则有V[k] <= V[2*k+1]

证明:
初始时,区间为[_nSize+1, _nSize]为空 区间,循环不变式成立
对_i = k的迭代
依据循环不变式【本次循环迭代前的各个迭代后,循环不变式均满足】
所有_i > k的迭代处理后,循环不变式均满足

只需证明:Adjust(nIndex_)
在[nIndex_+1, nSize]区间内任意元素均满足 位置约束前提下,
通过调节[nIndex_, nSize]区间内元素位置,
可实现[nIndex_, nSize]区间内任意元素均满足 位置约束的效果即可。
如上述证明成立,则,循环不变式成立。

证明:Adjust(nIndex_)具备上述性质
1. 若k位置元素小于其可能存在的左孩子,右孩子元素
无需处理。
此时,结合算法前提,可知结论成立。
2. 若k位置元素 比起左孩子,或右孩子大
让孩子中最小的元素 和 k位置元素交换位置
交换后,对k位置,满足位置约束
对换入k位置元素的位置t, 该位置的元素相比原来变大了
此时我们希望求解
在[t+1, nSize]区间内任意元素均满足 位置约束前提下,
通过调节[t, nSize]区间内元素位置,
可实现[t, nSize]区间内任意元素均满足 位置约束的效果即可。
这和要求解元素问题属于同类问题。

综合,在Adjust处理中,
我们要么立即求解。要么转化为对同类问题的求解。
我们可以证明,算法在转化为同类求解的时候,总是朝着更易终止的方向进行,且可以证明,至多在经历Θ(lg(n))次,递归后,必然可以得到立即求解的情形。
综合,算法成立。

MinHeap::Add

按添加元素相比预先填充元素大小,实际演变为证明
Smaller,Bigger

MinHeap::Smaller

算法前提/背景:
本来区间[1, nSize]内所有位置均满足位置约束
现在nPos位置元素变小了,导致此位置可能不满足位置约束
算法目标:
通过对[1, nSize]各个元素进行位置调整,
使区间[1, nSize]内所有位置均满足位置约束

1. 若nPos_ == 1,无需处理
2. 若 m_arrElements[_nP] <= m_arrElements[nPos_],无需处理
3. m_arrElements[_nP] > m_arrElements[nPos_],
交换_nP和nPos_位置内容。
此时要求解问题:
本来区间[1, nSize]内所有位置均满足位置约束
现在_nP位置元素变小了,导致此位置可能不满足位置约束
希望通过对[1, nSize]各个元素进行位置调整,使区间[1, nSize]内所有位置均满足位置约束
这是和原问题同类型的问题

综合,在Smaller处理中,我们要么立即求解。要么转化为对同类问题的求解。
我们可以证明,算法在转化为同类求解的时候,总是朝着更易终止的方向进行,且可以证明,至多在经历Θ(lg(n))次,递归后,必然可以得到立即求解的情形。

MinHeap::Bigger

类似MinHeap<T>::Smaller

时间复杂度

假设元素集合中元素个数为n

构造

MinHeap()

时间复杂度Θ(1)

MinHeap(const Array::DynArray& arrElements_)

时间复杂度Θ(nlg(n))

拷贝构造

	时间复杂度Θ(n)

赋值

时间复杂度Θ(n)

析构

时间复杂度Θ(1)

添加

时间复杂度O(lg(n))

删除

时间复杂度O(lg(n))

搜索

时间复杂度O(n)
发布了117 篇原创文章 · 获赞 84 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/105376883