最小堆
总体表述
开发过程,经常需要对数据集合进行维护。
维护数据结合的数据结构可统称为容器。
最小堆是维护数据集合的容器的一种,
最小堆具备如下性质:
设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)