大小堆&&堆排序&&堆的應用

一.首先說一下堆的概念吧這裏就不按照標準的概念來說了,就說說我理解的堆。

堆就是一個數組中的元素,參考着完全二叉樹的這種數據結構存儲在數組中,這樣就是一個堆。注意:這裏是參考,實際的存儲還是在數組中,只不過數組中的存儲順序滿足完全二叉樹而已。說到堆離不開大小堆,下面繼續介紹大小堆的概念。

1.最小堆:因爲數組中的元素是參考完全二叉樹的存儲順序存儲的,所以小堆就是每一個雙親結點的值都<=左右孩子的值,堆頂的元素是最小值


2.最大堆:任何一個雙親結點的值都大於等於左右孩子的值,並且堆頂元素的值是最大值。


二.下面來說說如何創建一個堆,因爲堆離不開最大堆和最小堆,所以我這裏創建的堆是生成了自動生成了最小堆,首先給出代碼,然後在給出代碼解釋:

#include<iostream>
#include<vector>
using namespace std;
template<class T>
class Heap//堆
{
public:
	Heap(T* array, size_t size)//構造函數
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
	T top() const//返回堆頂的元素
	{
		T a = _top();
		return a;
	}
	void Push(const T& data)//給堆中插入元素,使用向上調整插入
	{
		_Push(data);
	}
	void Pop()//刪除堆中的元素其實就是刪除堆頂的元素
	{
		_Pop();
	}
private:
	void _AdjustDown(int parent)//從上往下調整,建立小堆
	{
		for (int i = parent; i >= 0; i--)
		{
			parent = i;
			size_t child = parent * 2 + 1;
			while (child<_size)
			{
				if (child + 1 < _size&&_array[child] > _array[child + 1])
					child += 1;
				if (/*child<_size&&*/_array[parent] > _array[child])
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	}
	void _AdjustUp(size_t child)//向上調整,插入中使用
	{
		size_t parent = (child - 1) >> 1;
		while (child > 0)
		{
			if (_array[parent] > _array[child])
			{
				swap(_array[parent], _array[child]);
				child = parent;
				parent = (child - 1) >> 1;
			}
			else
				break;
		}
	}
	T _top() const
	{
		return _array[0];
	}
	void _Push(const T data)//堆中插入元素
	{
		_array.push_back(data);
		_size++;
		if (_array.size() > 1)
		{
			_AdjustUp(_size - 1);
		}
	}
	void _Pop()//刪除堆頂元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustUp(0);
		}
	}
private:
	vector<T> _array;
	size_t _size;
};
int main()
{
	int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
	Heap<int> s(array, sizeof(array) / sizeof(*array));
	s.Push(1);
	s.Pop();
	cout << s.top() << endl;
	system("pause");
	return 0;
}
上面是整體所有的代碼下面給出解析:

1.創建堆(自動生成最小堆)

Heap(T* array, size_t size)//構造函數
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
這段代碼就不需要我多說了吧,將數組中的元素保存在容器vector中,下面我來終點介紹這段代碼:即得出最小堆的方法--->向下調整法
_AdjustDown((_size - 2) >> 1);


注意想要生成最小堆的方法是向下調整法,並不是說要生成最大堆就用向上調整法,想要生成最大堆,只需要將其中比較的順序符號反過來就可以了。而向上調整法是給堆中插入元素時使用的。

2.下面我來說說向上調整法



3.好了關於堆最核心的兩個算法已經講完了,剩下的就是一下很簡單的東西了,下面我來說說關於堆的刪除即Pop()

void _Pop()//刪除堆頂元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustUp(0);
		}
	}
算法核心就是將堆頂元素和最後一個元素互換,然後剔除互換後的最後一個元素(即要刪除的元素),在重新調整堆就好了。

到這裏關於堆的一些問題就講完了置於剩下的那幾個功能都很簡單自己去看就可以了。測試代碼我在最開是的代碼中也已經給出了自己去測試就可以了。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////雖然上面 的堆已經說完了但是美中不足的是,每次我創建的總是最小堆,要是我想創建最大難道每次都要去將其中的符號改動嗎?當然不需要,下面我就來說說升級版本的,這個版本中加入了仿函數,使一切看起就變得很簡單了

1.首先說一下什麼是仿函數

其實就是在你定義的類中重載(),實現一個具體的功能,然後這個類對象就可以當成函數調用了!

舉個簡單的例子:

template<class T>
class Add//定義具有加法功能的仿函數
{
public:
	int operator()(const T& a, const T& b)
	{
		int  sum;
		sum = a + b;
		return sum;
	}
};
template<class T>
class Mul//定義具有乘法功能的仿函數
{
public:
	int operator()(const T& a, const T& b)
	{
		int  sum;
		sum = a*b;
		return sum;
	}
};
template<class T,class CA>//在這裏只是多了個參數而已
class Caculate
{
private:
	int a;
	int b;
public:
	Caculate(int x, int y)
		:a(x)
		, b(y){}
	CA s;//仿函數類的對象
	int jisuanqi()
	{
		return s(a,b);//把對象當成函數調用
	}
};
int main()
{
	Caculate<int,Add<int>> s(10, 10);//在這裏你向進行什麼運算取決於你傳過去的是哪個對象
	cout << s.jisuanqi() << endl;//執行加法運算
	system("pause");
	return 0;
}
上面就是一個簡單的仿函數的應用。如果有不理解的地方私我。

下面來說說把仿函數應用到堆中的改造方法,因爲最大小堆的建立就是取決於如下語句

if (child + 1 < _size&&com(_array[child+1], _array[child]))
					child += 1;
				if (child<_size&&com(_array[child],_array[parent]))
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
所以我們可以將判斷的if語句中的表達式交給仿函數去完成,我們在if裏面只需要調用仿函數就可以了,所以產生如下的仿函數

template<class T>
class Less//建立小堆的仿函數
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left < right;
	}
};
template<class T>
class Greater//建立大堆的仿函數
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left >right;
	}
};
使用方法如下

下面給出全部代碼:

#include<iostream>
#include<vector>
using namespace std;
//1.函數指針可以實現
//2.仿函數
template<class T>
class Less
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left < right;
	}
};
template<class T>
class Greater
{
public:
	bool operator()(const T& left, const T& right)
	{
		return left >right;
	}
};
//這裏創建的都是小堆
template<class T,class Compare=Less<int>>
class Heap//堆
{
public:
	Heap(T* array, size_t size)
		:_size(size)
	{
		_array.resize(size);
		for (size_t i = 0; i < _size; i++)
		{
			_array[i] = array[i];
		}
		_AdjustDown((_size - 2) >> 1);
	}
	T top() const//返回堆頂的元素
	{
		return _array[0];
	}
	void Push(const T& data)//給堆中插入元素,使用向上調整插入
	{
		//方法1,直接尾插
		_array.push_back(data);
		_size++;
		if (_array.size() > 1)
		{
			_AdjustUp(_size - 1);
		}
	}
	void Pop()//刪除堆中的元素其實,就是刪除堆頂的元素
	{
		if (_array.empty())
			return;
		size_t last = _size - 1;
		swap(_array[0], _array[last]);
		_array.pop_back();
		_size--;
		if (_size > 1)
		{
			_AdjustDown(0);
		}
	}
private:
	void _AdjustDown(int parent)//從上往下調整,建立小堆
	{
		for (int i = parent; i >= 0; i--)
		{
			parent = i;
			size_t child = parent * 2 + 1;
			while (child<_size)
			{
				Compare com;
				if (child + 1 < _size&&com(_array[child+1], _array[child]))
					child += 1;
				if (child<_size&&com(_array[child],_array[parent]))
				{
					swap(_array[parent], _array[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	}
	void _AdjustUp(size_t child)//向上調整,插入中使用
	{
		size_t parent = (child - 1) >> 1;
		while (child>0)
		{
			if (Compare()(_array[child],_array[parent]))
			{
				swap(_array[parent], _array[child]);
				child = parent;
				parent = (child - 1) >> 1;
			}
			else
				break;
		}
	}
private:
	vector<T> _array;
	size_t _size;//缺點就是每次插入的需要調整
};
int main()
{
	int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
	Heap<int,Greater<int>> s(array, sizeof(array) / sizeof(*array));
	/*s._downtoup();*/
	s.Push(1);
	s.Pop();
	cout << s.top() << endl;
	system("pause");
	return 0;
}
因爲測試代碼我也給出了,所以自行去測試。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

關於堆的內容說完了,下面說說堆的應用

1.優先級隊列

template<class T,class Compare>
class Priority
{
public:
	Priority(){}
	void Push(const T& data)
	{
		_ph.Push(data);
	}
	void Pop()
	{
		_ph.Pop();
	}
	T Top() const
	{
		return _hp.Top();
	}
	bool Empty()const 
	{
		return _hp.Empty();
	}
private:
	Heap<int, Compare> _hp;
};
2.對排序(遞增排序)

1.首先建立調整最大堆的的函數

2.將你要排序的數組首先調整一次,生成堆

3.開始排序(交換堆頂和最後一個元素的位置,然後除去最後一個元素,繼續調整,一次類推直到全部排序完)

下面給出代碼

void Adjustheap(int *array, int size, int parent)//調整堆。調整出來的是大堆
{
	int child = parent * 2 + 1;

	while (child<size)
	{
		if (child+1<size&&array[child]<array[child + 1])
			child += 1;
		if (array[parent] < array[child])
		{
			swap(array[parent], array[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;//不需要調整直接結束
	}
}
void HeapSort(int *array, int size)
{
	//創建堆
	int root = (size - 2) >> 1;
	for (; root > 0; --root)
	{
		Adjustheap(array, size, root);//調整堆
	}
	//堆排序
	for (int i = 0; i < size-1;i++)
	{
		swap(array[0],array[size - i - 1]);
		Adjustheap(array, size - i-1, 0);
	}
}
int main()
{
	int array[] = { 5, 3, 2, 4, 1 };
	HeapSort(array, 5);
	system("pause");
	return 0;
}
排序後的數組

3.Topk問題:

即在海量的數據中查找最大或者最小的前 N個數據

1.假如要找前N個最大的數話,首先建立一個元素爲N的小堆,然後開始遍歷數據,如果數據比堆根大則將堆根pop出去,然後將此元素push進堆,然後繼續調整,調整好以後的堆頂是最小的,以此類推,當數據全部掃描完成,則這個堆中的元素就是我們要的數據

2.假如要找前N個最小的數的話,使用最大堆,比堆頂元素小的話pop堆頂,push元素,然後調整堆,直到數據遍歷完畢。

以上關於堆的一些內容就介紹完了,希望對大家對於堆的理解有幫助,不懂或出錯的地方歡迎留言一起交流學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章