Heap Enhanced Heap and Priority Queue for Data Structures

content

1. Related concepts of heap

Creation of two heaps

 Implementation of three heaps

The use of four-priority queue and its implementation

Realization of five booster reactors


1. Related concepts of heap

What is a heap? A heap is essentially a complete binary tree , which is divided into two types:

1. A large pile of roots.

2. Small root pile.

What is a big root heap? What is a small root heap?

Properties: The value of each node is greater than the value of its left and right children, which is called the big root heap; the value of each node is less than the value of its left and right children, which is called small root pile . As shown below:

We have marked each number in the above figure, and the above structure is mapped into an array to become the following:

Creation of two heaps

Construct an unordered array into a large root heap (a large root heap is used for ascending order, and a small root heap is used for descending order)

The main idea: the first time to ensure the 0~0 position big root heap structure (nonsense), the second time to ensure the 0~1 position big root heap structure, the third time to ensure the 0~2 position big root heap structure... until the 0~n- 1 position big root heap structure (each time the newly inserted data is compared with its parent node, if the inserted number is larger than the parent node, it is exchanged with the parent node, otherwise it has been exchanged upward until it is less than or equal to the parent node, or When it comes to the top) when inserting 6, 6 is greater than his parent node 3, that is, arr(1)>arr(0), then exchange; at this time, it is guaranteed that the 0~1 position is a big root heap structure, as shown below:

 

  When inserting 8, if 8 is greater than its parent node 6, that is, arr(2)>arr(0), it is exchanged; at this time, the position 0~2 is guaranteed to be a large root heap structure, as shown in the following figure

 When inserting 5, if 5 is greater than its parent node 3, it is exchanged. After the exchange, 5 is found to be smaller than 8, so it is not exchanged; at this time, the large root heap structure at positions 0 to 3 is guaranteed, as shown in the following figure:

 When inserting 7, if 7 is greater than its parent node 5, it is exchanged. After the exchange, 7 is found to be smaller than 8, so it is not exchanged; at this time, the entire array is already a large root heap structure:

 

There are two ways to build a heap. One way is to build a heap by inserting from top to bottom. (N*logN). The time complexity of the second method is O(N)

 1. Adjust the corresponding code from top to bottom:


		void AdjustDown(int* a, int n, int parent) {
			int child = 2 * parent + 1;//父亲的左孩子为父亲的小标*2+1;
			while (child < n) {//从上往下调整
				//防止越界
				if (child+1<n &&a[child+1]>a[child]) {//选出左右孩子大的那一个
					++child;
				}

				if (a[child]>a[parent]) {//如果孩子大于父亲交换
					swap(a[child], a[parent]);//交换
					parent = child;//迭代继续向下调整
					child = 2 * parent + 1;//重新计算孩子的下标
				}

				else {//如果父亲不小于孩子节点调整结束
					break;
				}
			}

		   }
  

Corresponding heap build code:

void AdjustDown(int* a, int n, int parent) {
			int child = 2 * parent + 1;//父亲的左孩子为父亲的小标*2+1;
			while (child < n) {//从上往下调整
				//防止越界
				if (child+1<n &&a[child+1]>a[child]) {//选出左右孩子大的那一个
					++child;
				}

				if (a[child]>a[parent]) {//如果孩子大于父亲交换
					swap(a[child], a[parent]);//交换
					parent = child;//迭代继续向下调整
					child = 2 * parent + 1;//重新计算孩子的下标
				}

				else {//如果父亲不小于孩子节点调整结束
					break;
				}
			}

		   }

void  HeapSort(int* a, int n) {

	for (int i = (n - 2) / 2; i >= 0; i--) {
		 AdjustDown(a, i, n);       
	}
//此处为建堆代码上面下面是实现堆排序
	int end = n - 1;
	while (end>0) {
		swap(a[0], a[end]);
		AdjustDown(a, 0, end);
		end--;
	}
 
}

 2. Adjustment from bottom to top is the way of inserting

void AdjustDownUp(T* a, int child) {
			int parent = (child - 1) / 2;//由公式计算父亲节点的下标
			while (child > 0) {//知道调整到只有一个节点
				if (a[child] > a[parent]) {//孩子大于父亲
					swap(a[parent], a[child]);//交换
					child = parent;//重新及计算位置
					parent = (child - 1) / 2;
				}
				else {//调整结束
					break;
				}
			}
		}

		void HeapPush(T x)
		{
			if (_capacity == _size) {
				int newcapacity = _capacity == 0 ? 8 : _capacity * 2;
				T* tmp = new T[newcapacity];

				for (int i = 0; i < _size; i++) {
					tmp[i] = _a[i];//拷数据
				}
				delete[] _a;
				_capacity = newcapacity;
				_a = tmp;
			}

			_a[_size] = x;//放进去
			_size++;
			AdjustDownUp(_a, _size - 1);

		}

 3. Time complexity of heap building:

······················ Next we look at an OJ question:

Determine if all numbers in an array appear only once

Topic description:

describe

Given a number of numbers arr, determine whether all numbers in the array arr appear only once.

Enter description:

The input consists of two lines, the first line contains an integer n(1 \leq n \leq 10^5) (1≤n≤105), which represents the length of the array arr. The second line includes n integers representing the array arr(1 \leq arr[i] \leq 10^7 )(1≤arr[i]≤107).

Output description:

If all numbers in arr appear only once, output "YES", otherwise output "NO".

Example 1

enter:

3
1 2 3

Copy the output:

YES

copy

enter:

3
1 2 1

output:

NO

Require

1. Time complexity O(n). 2. Additional space complexity O(1)

The general idea is very simple to use a hash table to count the number of times, but the problem requires a space complexity of O(1). This has a lot to do with the idea of ​​building a heap we mentioned above. We only need to check when building a heap. There are three situations that can be checked:

1. The father and the father's left child are equal

2. The father and the father's right child are equal

3. Father and father's sibling nodes are equal

 For code:

#include<iostream>
#include<vector>
using namespace std;
bool AdjustDown(vector<int>&arr,int root,int n){
    int parent=root;
    int child=2*parent+1;
    while(child<n){
        //如果父亲和左右孩子是否相等如果相等就重复了
         if(arr[child]==arr[parent]||(child+1<n&&arr[child+1]==arr[parent])){
            return false;
        }
        //判断旁边的父亲
        if(parent>0&&arr[parent]==arr[parent-1]){
            return false;
        }
        if(child+1<n&&arr[child+1]>arr[child]){
            ++child;
        }
        if(arr[child]>arr[parent]){
            swap(arr[child],arr[parent]);
            parent=child;
            child=2*parent+1;
        }
        else{
            break;
        }
    }
     return true;   
}
int main(){
   int n;
   cin>>n;
    vector<int>arr(n);
    for(int i=0;i<n;i++){
        cin>>arr[i];
    }
    for(int i=(n-2)/2;i>=0;i--){
        bool ret=AdjustDown(arr,i,n);
        if(!ret){
           cout<<"NO";
          return 0;
        }
    }
    cout<<"YES";
    
}

 Implementation of three heaps

The insertion of the heap has been said before, and the deletion is also very simple, just swap the element at the top of the heap with the last element and adjust it from the top of the heap down. For details, please see the code:

#pragma once
#include<algorithm>
#include<iostream>
using namespace std;
namespace ksy {
	template<class T>
	class Heap
	{
	public:
		Heap()
			:_a(nullptr)
			, _size(0)
			, _capacity(0)
		{}

		void AdjustDown(int* a, int n, int parent) {
			int child = 2 * parent + 1;//父亲的左孩子为父亲的小标*2+1;
			while (child < n) {//从上往下调整
				//防止越界
				if (child+1<n &&a[child+1]>a[child]) {//选出左右孩子大的那一个
					++child;
				}

				if (a[child]>a[parent]) {//如果孩子大于父亲交换
					swap(a[child], a[parent]);//交换
					parent = child;//迭代继续向下调整
					child = 2 * parent + 1;//重新计算孩子的下标
				}

				else {//如果父亲不小于孩子节点调整结束
					break;
				}
			}

		   }


		void AdjustDownUp(T* a, int child) {
			int parent = (child - 1) / 2;//由公式计算父亲节点的下标
			while (child > 0) {//知道调整到只有一个节点
				if (a[child] > a[parent]) {//孩子大于父亲
					swap(a[parent], a[child]);//交换
					child = parent;//重新及计算位置
					parent = (child - 1) / 2;
				}
				else {//调整结束
					break;
				}
			}
		}

		void HeapPush(T x)
		{
			if (_capacity == _size) {
				int newcapacity = _capacity == 0 ? 8 : _capacity * 2;
				T* tmp = new T[newcapacity];

				for (int i = 0; i < _size; i++) {//一个一个插入
					tmp[i] = _a[i];//拷数据
				}

				delete[] _a;
				_capacity = newcapacity;
				_a = tmp;
			}

			_a[_size] = x;//放进去
			_size++;
			AdjustDownUp(_a, _size - 1);

		}

		void HeapPop() {
			swap(_a[0], _a[_size - 1]);//把堆顶元素和最后一个元素交换在将最后一个元素删除
			_size--;//个数减一
			AdjustDown(_a, _size, 0);//从堆顶向下调整
		}

		~Heap() {
			_a = nullptr;
			_capacity = _size = 0;
		}

		void PritHeap() {//打印堆
			for (int i = 0; i < _size; i++) {
				cout << _a[i] << " ";
			}
		}
		size_t size() {
			return _size;
		}
		bool empty() {
			return _size == 0;//判空
		}
		T& top() {//返回堆顶的元素
			return _a[0];
		}

	private:
		T* _a;
		int _size;//个数
		int _capacity;//容量
	};
	void test_Heap() {
		int a[] = { 70,56,30,25,15,10,75 };
		Heap<int>t;

		for (auto& e : a) {
			t.HeapPush(e);
		}
		t.HeapPop();
		t.PritHeap();
	}
}

The use of four-priority queue and its implementation

1. A priority queue is a container adapter whose first element is always the largest of the elements it contains, according to strict weak ordering criteria.
2. This context is similar to a heap where elements can be inserted at any time and only the largest heap element (the top element in the priority queue) can be retrieved
.
3. The priority queue is implemented as a container adapter. The container adapter encapsulates a specific container class as its underlying container class. The queue provides a set of specific member functions to access its elements. Elements are popped from the "tail" of a particular container, which is called the top of the priority queue. , and supports the following operations:

1.empty(): Check whether the container is empty (check whether the priority queue is empty, return true, otherwise return
false)
2.size(): Return the number of valid elements in the container
3.push() Insert an element

4.pop() pops up an element

5.top() returns the element at the top of the heap

 Note that by default the priority queue is a large root heap

#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
void TestPriorityQueue()
{
// 默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{3,2,7,6,0,4,1,9,8,5};
priority_queue<int> q1;
for (auto& e : v)
q1.push(e);
cout << q1.top() << endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}

 Sort date classes using a priority queue:

2. If you put data of a custom type in priority_queue, the user needs to provide the overload of > or < in the custom type.

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
void TestPriorityQueue()
{
// 大堆,需要用户在自定义类型中提供<的重载
priority_queue<Date> q1;
q1.push(Date(2018, 10, 29));
q1.push(Date(2018, 10, 28));
q1.push(Date(2018, 10, 30));
cout << q1.top() << endl;
// 如果要创建小堆,需要用户提供>的重载
priority_queue<Date, vector<Date>, greater<Date>> q2;
q2.push(Date(2018, 10, 29));
q2.push(Date(2018, 10, 28));
q2.push(Date(2018, 10, 30));
cout << q2.top() << endl;
}

Simulation implementation

#include<vector>
#include<iostream>
namespace ksy
{
	//比较方式(使内部结构为大堆)
	template<class T>
	struct less
	{
		bool operator()(const T& l, const T& r)
		{
			return l < r;
		}
	};
	//比较方式(使内部结构为小堆)
	template<class T>
	struct greater
	{
		bool operator()(const T& l, const T& r)
		{
			return l > r;
		}
	};
	//仿函数默认为less
	template<class T, class  Container = std::vector<T>,class Compare=less<T>>
	class priority_queue
	{
	public:
		//向上调整算法,每push一个数都要调用向上调整算法,保证插入后是一个大堆
		void AdjustUp(int child)
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent],_con[child]))
				{
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent= (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		//向下调整算法,每次调用pop都要进行向下调整算法重新构成大堆
		void AdjustDwon(int parent)
		{
			Compare com;
			int child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() &&com( _con[child] ,_con[child + 1]))
				{
					child++;
				}
				if (com(_con[parent] ,_con[child]))
				{
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		//迭代器初始化
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)//在这里传迭代器进行初始化
		{
			for (size_t i = 1; i <_con.size(); i++)
			{
				AdjustUp(i);//向上调整建堆
			}
		}

		void push(const T& x)
		{
			_con.push_back(x);

			AdjustUp(_con.size() - 1);
		}
		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDwon(0);
		}
		T top()
		{
			return _con[0];
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

Realization of five booster reactors

The enhanced heap is used to make up for some shortcomings of the heap. The heap implemented in the library can only delete elements at the top of the heap, but cannot delete elements in other positions. Because this will destroy the structure of the heap. If you don't destroy the structure of the heap, you need to traverse to find it, so the time complexity is O(N), and the efficiency is greatly reduced. In order to solve this problem, the enhanced heap is introduced here.

Compared with the ordinary heap, the enhanced heap has an additional reverse index table to record the position of each element on the heap. With position deletion, it is basically the same as head deletion, and the last element is exchanged with the last element to delete the last element. Then adjust it up or down from the deleted position. Note that when adjusting, not only the position in the reverse index table of the value should be exchanged, but also the position in the reverse index table.

For code:

 

#pragma once
#include<unordered_map>
#include<iostream>
#include<vector>
using namespace std;
//T一定要是非基础类型如果有基础类型需求封装一层
template<class T, class Compare>
class HeapGreater {
public:
	HeapGreater()
		:heapSize(0)
	{}
	bool empty() {
		return heapSize == 0;
	}
	size_t size() {
		return heapSize;
	}
	bool contains(T& obj) {
		return indexMap.count(T);
	}
	T& peek() {
		return heap[0];
	}
	void push(const T& obj) {
		heap.push_back(obj);//插入
		indexMap[obj] = heapSize;//记录位置
		AdjustUp(heapSize++);
	}
	T& pop() {
		T& ans = heap[0];
		Swap(0, heapSize - 1);
		indexMap.erase(ans);//表中的位置也要删除
	
		--heapSize;
		AdjustDown(0);//向下调整
		return ans;
	}
	void  AdjustDown(int root) {
		Compare cmp;
		int parent = root;
		int child = 2 * parent + 1;
		while (child < heapSize) {
			if (child + 1 < heapSize && cmp(heap[child], heap[child + 1])) {
				++child;
			}
			if (cmp(heap[parent], heap[child])) {
				Swap(parent, child);
				parent = child;
				child = parent * 2 + 1;
			}
			else {
				break;
			}

		}
	}
	void resign(const T& obj) {//obj位置的值发生改变
		AdjustUp(indexMap[obj]);//这两个只会发生一个
		AdjustDown(indexMap[obj]);
	}

	void Erase(const T& obj) {
		T& replace = heap[heapSize - 1];
		int index = indexMap[obj];
		indexMap.erase(obj);//删除表中的位置
		
		--heapSize;
		
		if (obj != replace) {//替换他的位置
			heap[index] = replace;
			indexMap[replace] = index;
			resign(replace);
		}
		heap.pop_back();
	}

	void set(const T& obj, const T& target) {
		int index = indexMap[obj];//先取出位置
		heap[index] = target;//
		indexMap.erase(obj);//删除表中位置
		indexMap[target] = index;//重写赋值
		AdjustDown(index);//向上或者向下调整
		AdjustUp(index);
	}
	
	void AdjustUp(int child) {
		Compare cmp;
		int parent = (child - 1) / 2;
		while (child > 0) {
			if (cmp(heap[parent], heap[child])) {
				Swap(parent, child);
				child = parent;
				parent = (child - 1) / 2;
			}
			else {
				break;
			}
		}
	}

	void Swap(int i, int j) {
		T o1 = heap[i];
		T o2 = heap[j];
		heap[i] = o2;
		heap[j] = o1;
		indexMap[o2] = i;
		indexMap[o1] = j;
	}



	//private void heapInsert(int index) {
	//	while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) {
	//		swap(index, (index - 1) / 2);
	//		index = (index - 1) / 2;
	//	}
	//}

	vector<T>heap;
	unordered_map<T, int>indexMap;
	int heapSize;
	Compare cmp;
};

 

Guess you like

Origin blog.csdn.net/qq_56999918/article/details/123337611