數據結構--最小堆

最小堆

總體表述

開發過程,經常需要對數據集合進行維護。
維護數據結合的數據結構可統稱爲容器。
最小堆是維護數據集合的容器的一種,
最小堆具備如下性質:
設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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章