最小堆
總體表述
開發過程,經常需要對數據集合進行維護。
維護數據結合的數據結構可統稱爲容器。
最小堆是維護數據集合的容器的一種,
最小堆具備如下性質:
設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)