目录
一.堆的相关概念
什么是堆?堆本质上是一种完全二叉树,它分为两个类型:
1.大根堆。
2.小根堆。
什么是大根堆?什么是小根堆?
性质:每个结点的值都大于其左孩子和右孩子结点的值,称之为大根堆;每个结点的值都小于其左孩子和右孩子结点的值,称之为小根堆。如下图:
我们对上面的图中每个数都进行了标记,上面的结构映射成数组就变成了下面这个样子:
二 堆的创建
将无序数组构造成一个大根堆(升序用大根堆,降序就用小根堆)
主要思路:第一次保证0~0位置大根堆结构(废话),第二次保证0~1位置大根堆结构,第三次保证0~2位置大根堆结构...直到保证0~n-1位置大根堆结构(每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端)插入6的时候,6大于他的父结点3,即arr(1)>arr(0),则交换;此时,保证了0~1位置是大根堆结构,如下图:
插入8的时候,8大于其父结点6,即arr(2)>arr(0),则交换;此时,保证了0~2位置是大根堆结构,如下图
插入5的时候,5大于其父结点3,则交换,交换之后,5又发现比8小,所以不交换;此时,保证了0~3位置大根堆结构,如下图:
插入7的时候,7大于其父结点5,则交换,交换之后,7又发现比8小,所以不交换;此时整个数组已经是大根堆结构:
建堆有两种方式一种方式是从上到下插入的方式建堆,一种方式是从下往上建堆两种方式都可以建堆但是第一种方式建堆的时间复杂度为O(N*logN).第二种方式时间复杂度为O(N)
1.从上往下调整对应代码:
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 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.从下往上调整也就是插入的方式
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.建堆的时间复杂度:
····················下面我们来看一道OJ题:
题目描述:
描述
给定一个个数字arr,判断数组arr中是否所有的数字都只出现过一次。
输入描述:
输入包括两行,第一行一个整数n(1 \leq n \leq 10^5)(1≤n≤105),代表数组arr的长度。第二行包括n个整数,代表数组arr(1 \leq arr[i] \leq 10^7 )(1≤arr[i]≤107)。
输出描述:
如果arr中所有数字都只出现一次,输出“YES”,否则输出“NO”。
示例1
输入:
3 1 2 3复制输出:
YES复制
输入:
3 1 2 1输出:
NO要求
1.时间复杂度O(n)。2.额外空间复杂度O(1)
常规思路非常的简单使用一个哈西表统计次数 即可,但是题目要求空间复杂度为O(1)。这和上面我们提的建堆的思想有很大的关系,我们只需要在建堆的时候进行检查即可检查的情况分为三种:
1.父亲和父亲的左孩子相等
2.父亲和父亲的右孩子相等
3.父亲和父亲的兄弟节点相等
对于代码:
#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"; }
三 堆的实现
堆的插入了在前面已经说过了,删除也很简单就是将堆顶的元素和最后一个元素交换然后从堆顶向下调整具体请看代码:
#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(); } }
四优先级队列使用及其实现
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元
素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。,并支持以下操作:1.empty():检测容器是否为空(检测优先级队列是否为空,是返回true,否则返回
false)
2.size():返回容器中有效元素个数
3.push()插入一个元素4.pop()弹出一个元素
5.top()返回堆顶的元素
注意默认情况下优先级队列是大根堆
#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; }
使用优先级队列排序日期类:
2. 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。
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; }
模拟实现
#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; }; }
五加强堆的实现
加强堆了是用来弥补堆的一些不足之处,库里面实现的堆只能删除堆顶的元素,不能删除其他位置的元素。因为这样会破坏堆的结构。如果不破坏堆的结构就需要遍历去找这样一来时间复杂度为O(N)效率就降低了很多为了解决这一问题在这里引入加强堆。
加强堆相比普通的堆多了一张反向索引表用来记录每个元素在堆上的位置,有了位置删除和头部删除基本一样,和最后一个元素交换在将最后一个元素删除。然后再从删除的位置向上或者向下调整,注意调整时不仅要交换值反向索引表中的位置也要换。
对于代码:
#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; };