Heap Heap amélioré et file d'attente prioritaire pour les structures de données

contenu

1. Concepts associés de tas

Création de deux tas

 Implémentation de trois tas

L'utilisation de la file d'attente à quatre priorités et sa mise en œuvre

Réalisation de cinq réacteurs de surpression


1. Concepts associés de tas

Qu'est-ce qu'un tas ? Un tas est essentiellement un arbre binaire complet , qui est divisé en deux types :

1. Un gros tas de racines.

2. Petit tas de racines.

Qu'est-ce qu'un gros tas de racines ? Qu'est-ce qu'un petit tas de racines ?

Propriétés : la valeur de chaque nœud est supérieure à la valeur de ses enfants gauche et droit, qui est appelée le grand tas racine ; la valeur de chaque nœud est inférieure à la valeur de ses enfants gauche et droit, qui est appelée petit tas racine . Comme indiqué ci-dessous:

Nous avons marqué chaque nombre dans la figure ci-dessus, et la structure ci-dessus est mappée dans un tableau pour devenir la suivante :

Création de deux tas

Construire un tableau non ordonné dans un grand tas racine (un grand tas racine est utilisé pour l'ordre croissant et un petit tas racine est utilisé pour l'ordre décroissant)

L' idée principale: la première fois pour assurer la structure du gros tas racine de la position 0 ~ 0 (non-sens), la deuxième fois pour assurer la structure du gros tas racine de la position 0 ~ 1, la troisième fois pour assurer le gros tas racine de la position 0 ~ 2 structure... jusqu'à la position 0~n- 1 grande structure de tas racine (chaque fois que les données nouvellement insérées sont comparées avec son nœud parent, si le nombre inséré est plus grand que le nœud parent, il est échangé avec le nœud parent, sinon il a été échangé vers le haut jusqu'à ce qu'il soit inférieur ou égal au nœud parent, ou lorsqu'il arrive en haut) lors de l'insertion de 6, 6 est supérieur à son nœud parent 3, c'est-à-dire arr(1)>arr(0) , puis échangez ; à ce moment, il est garanti que la position 0~1 est une grande structure de tas racine, comme indiqué ci-dessous :

 

  Lors de l'insertion de 8, si 8 est supérieur à son nœud parent 6, c'est-à-dire arr(2)>arr(0), il est échangé ; à ce moment, la position 0~2 est garantie d'être une grande structure de tas racine, comme le montre la figure suivante

 Lors de l'insertion de 5, si 5 est supérieur à son nœud parent 3, il est échangé. Après l'échange, 5 est inférieur à 8, il n'est donc pas échangé ; à ce moment, la grande structure de tas racine aux positions 0 à 3 est garanti, comme le montre la figure suivante :

 Lors de l'insertion de 7, si 7 est supérieur à son nœud parent 5, il est échangé. Après l'échange, 7 est inférieur à 8, il n'est donc pas échangé ; à ce moment, l'ensemble du tableau est déjà un grand tas racine structure:

 

Il existe deux façons de construire un tas. La première consiste à construire un tas en insérant de haut en bas. (N*logN). La complexité temporelle de la seconde méthode est O(N).

 1. Ajustez le code correspondant de haut en bas :


		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;
				}
			}

		   }
  

Code de génération de tas correspondant :

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. L'ajustement de bas en haut est la manière d'insérer

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. Complexité temporelle de la construction du tas :

···················· Ensuite, nous examinons une question du JO :

Déterminer si tous les nombres d'un tableau n'apparaissent qu'une seule fois

Description du sujet:

décris

Étant donné un nombre de nombres arr, déterminez si tous les nombres du tableau arr apparaissent une seule fois.

Saisissez la description :

L'entrée se compose de deux lignes, la première ligne contient un entier n(1 \leq n \leq 10^5) (1≤n≤105), qui représente la longueur du tableau arr. La deuxième ligne comprend n entiers représentant le tableau arr(1 \leq arr[i] \leq 10^7 )(1≤arr[i]≤107).

Description de la sortie :

Si tous les nombres dans arr n'apparaissent qu'une seule fois, sortez "OUI", sinon sortez "NON".

Exemple 1

Entrer:

3 
1 2 3

Copiez le résultat :

OUI

copie

Entrer:

3 
1 2 1

sortir:

NON

Exiger

1. Complexité temporelle O(n). 2. Complexité spatiale supplémentaire O(1)

L'idée générale est très simple d'utiliser une table de hachage pour compter le nombre de fois, mais le problème nécessite une complexité spatiale de O(1). Cela a beaucoup à voir avec l'idée de construire un tas que nous avons mentionnée ci-dessus. Nous avons seulement besoin de vérifier lors de la construction d'un tas. Il y a trois situations qui peuvent être vérifiées :

1. Le père et l'enfant laissé par le père sont égaux

2. Le père et l'enfant droit du père sont égaux

3. Les nœuds frères et sœurs du père et du père sont égaux

 Pour le 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";
    
}

 Implémentation de trois tas

L'insertion du tas a déjà été mentionnée, et la suppression est également très simple, il suffit d'échanger l'élément en haut du tas avec le dernier élément et de l'ajuster du haut du tas vers le bas. Pour plus de détails, veuillez consulter le 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();
	}
}

L'utilisation de la file d'attente à quatre priorités et sa mise en œuvre

1. Une file d'attente prioritaire est un adaptateur de conteneur dont le premier élément est toujours le plus grand des éléments qu'il contient, selon des critères stricts d'ordre faible.
2. Ce contexte est similaire à un tas où les éléments peuvent être insérés à tout moment et seul le plus grand élément du tas (l'élément supérieur dans la file d'attente prioritaire) peut être récupéré
.
3. La file d'attente prioritaire est implémentée en tant qu'adaptateur de conteneur. L'adaptateur de conteneur encapsule une classe de conteneur spécifique en tant que classe de conteneur sous-jacente. La file d'attente fournit un ensemble de fonctions membres spécifiques pour accéder à ses éléments. Les éléments sont extraits de la "queue" d'un conteneur particulier, appelé le haut de la file d'attente prioritaire. , et prend en charge les opérations suivantes :

1.empty() : Vérifie si le conteneur est vide (vérifie si la file prioritaire est vide, retourne vrai, sinon retourne
faux)
2.size() : retourne le nombre d'éléments valides dans le conteneur
3.push() Insère un élément

4.pop() affiche un élément

5.top() renvoie l'élément en haut du tas

 Notez que par défaut, la file d'attente prioritaire est un grand tas racine

#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;
}

 Trier les classes de date à l'aide d'une file d'attente prioritaire :

2. Si vous placez des données d'un type personnalisé dans priority_queue, l'utilisateur doit fournir la surcharge de > ou < dans le type personnalisé.

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;
}

Mise en œuvre de la simulation

#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;
	};
}

Réalisation de cinq réacteurs de surpression

Le tas amélioré est utilisé pour compenser certaines lacunes du tas. Le tas implémenté dans la bibliothèque ne peut supprimer que des éléments en haut du tas, mais ne peut pas supprimer des éléments à d'autres positions. Parce que cela détruira la structure du tas. Si vous ne détruisez pas la structure du tas, vous devez traverser pour le trouver, donc la complexité temporelle est O(N), et l'efficacité est considérablement réduite.Afin de résoudre ce problème, le tas amélioré est introduit ici .

Par rapport au tas ordinaire, le tas amélioré dispose d'une table d'index inverse supplémentaire pour enregistrer la position de chaque élément sur le tas. Avec la suppression de la position, c'est fondamentalement la même chose que la suppression de la tête, et le dernier élément est échangé avec le dernier élément à supprimer le dernier élément. Ensuite, ajustez-le vers le haut ou vers le bas à partir de la position supprimée. Notez que lors de l'ajustement, non seulement la position dans le tableau d'index inversé de la valeur doit être échangée, mais également la position dans le tableau d'index inversé.

Pour le 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;
};

 

Je suppose que tu aimes

Origine blog.csdn.net/qq_56999918/article/details/123337611
conseillé
Classement