目录
二、熟悉unordered_map> hash ,vector
1).首先熟悉一下unordered_map hash;,int>
2).对于unordered_map> hash 的理解,vector
三、通过priority_queue> q 明白大小堆的构造,vector
一、熟悉priority_queue<int,vector<int>> q
1.priority_queue 又称优先级队列
调用函数的接口:
这里其实就已经可以看出,跟普通队列接口一样,就是push()数据,和pop()数据。
对于优先级队列,故名思意,就是按照数据的优先级在队列中进行排序,一般默认大数字优先级高,小数字优先级低。
那么,解释一下 q 的三个参数 :
int:是存放的数据类型 可以为int char double等类型
vector<int> : 是实现这个优先级队列的底层容器,一般来说都是用vector<int> 也是默认的,可以不显示写出来
greater<int> : 很明白就是优先级取反,让小数字优先级高,先排在队首。
二、熟悉unordered_map<int,vector<int>> hash
1).首先熟悉一下unordered_map<int,int> hash;
对于unordered_map<int,int> hash; 说明存放的数据就都是int类型,是一个一维数组
int main()
{
unordered_map<int, int> hash;
for (int i = 0; i < 10; i++)
{
hash[i]++;
}
for (int i = 0; i < 10; i++)
{
cout << hash[i] << " ";
}
//1 1 1 1 1 1 1 1 1 1
return 0;
}
上图就是对于一维数组hash的存储数据以及访问,可以清楚的看到重载的operator[ ] 很方便能够进行访问。 了解到一维hash后 可以考虑了解二维hash<int,vector<int>>。
int main()
{
unordered_map<int, vector<int>> hash; //相当于一个二维数组
hash[0].push_back(10);
hash[0].push_back(20);
hash[0].push_back(30);
hash[1].push_back(40);
hash[1].push_back(50);
hash[2].push_back(60);
for (auto e : hash[0]) cout << e << " "; cout << endl; //10 20 30
for (auto e : hash)
{
cout << "first:" << e.first << " ";
for (auto f : e.second)
cout <<"second:" << f << " ";
cout << endl;
}
//first:0 second:10 second:20 second:30
//first:1 second:40 second:50
//first:2 second:60
return 0;
}
由上述代码可以看出对于访问hash[一维格子 i ] 进行访问vector<int> 里面的元素可以知道用迭代器来进行访问元素;
但是这是以及确定好了hash[i] 列表的定位;对于访问整个二维数组:
可以看到跟普通二维数组一样,要用两层循环嵌套。第一层访问元素的e.first 是一位格子元素;第二层访问元素的e.second 来遍历二维数组。
2).对于unordered_map<int,vector<int>> hash 的理解
可以理解为在一维数组的每一个int类型的格子里面都加了一条一维数组 为vector<int> 就相当于是一个二维数组。
3).常用函数接口:
三、通过priority_queue<int,vector<int>> q 明白大小堆的构造
通过上述了解 优先级队列 是将优先级大的放在队首,那么就可以理解为是在这个队列中来建立大小堆 ,可以知道优先级队列的底层是通过大小堆来建立的,那么就可以通过priority_queue<> 来设计多种组合来堆数据进行排序。
但是:要注意的是 vector<int> 只是容器底层的实现,千万不要认为是存入的数据!!!
四、题目(前k个高频元素)
建议:自己先思考,如果没思路再看解析,我写的真的很全面了,非常详细!!看题就知道应该用堆排序
1.题目要求是求出现频率最高的k个数,那么第一步不用想,肯定使用hash将出现的次数跟数字存起来。
2.但是问题就是如果用map<int,int> hash, 让hash[i]++,那么就会让数字存起来后不好访问次数最多的是谁,第二多,第三多...的次数不好访问,那么这个时候就要换思路,其实,我这个时候硬着头皮有创建了一个hash<int,int>表 来将hash的次数跟数据反着存放,可是我发现无法将次数存放后,在继续存放相同的数据。那么前面一个达到这么多次数的数据就会被覆盖掉。
3.那么就要想办法将hash<int,int> 改变掉,让他能在一个count次数下存放多个数据,不被覆盖掉,那么有了前面的思路就说明,前面被覆盖掉,会是一维数组,后面在一个count下存放多个数据就是二维数组。
4.有了这种思路那么就将hash<int,int> 改变成 -> 为 hash<int,vector<int>>,就相当于在一维数组的每个格子里面又加一条数组,就变成了二维数组,那么这样就可以存放出现同样次数的不同数据。 这个时候存放就又成了问题,因为构建的hash是
unordered_map<int,vector<int>> hash;
这个时候存放数据就是要hash[count].push_back(nums[i]); 可以理解为在hash[count]这个一维数组的格子中要继续存放vector<int> 型的数据进去,那么就是要进行push_back(),而数据是int型,数据又是nums[i] , 那么就可以理解这种写法。
5.这时就要统计每个元素出现的次数,来进行添加数据到hash里面。这里采用count=1来开始计数。首先我们要进行sort(nums.begin(),nums.end()) 保证nums是有序的,好方便统计每一种数据所出现的次数,在循环内,如果(i<n-1&&nums[i] == nums[i + 1]) 那么就会让count++;,因为i不会越界,i就不会取到n-1,这也就是为什么count开始就等于1的原因。当计数完成,就会进入hash[count].push_back(nums[i]);
将这个数据存放到出现的这个次数中。
6.此时,我们就会将出现的这个次数count存放到 优先级队列 里面
priority_queue<int, vector<int>, greater<int>> q;
那么来解释这个优先级队列,int就是存放的数据类型,vector<int> 是构建优先级队列的底层容器,greater<int> 会让优先级取反,一般默认是大数优先级高,取反后 小数优先级高。
现在思考为什么我们要用优先级队列进行存入每种数据出现的次数。首先,是因为这题要计算出现频率最高的k个数据,那么我们就要想办法把频率最高的k个次数求出来,才能进一步求出k个数据,那么q 就只用来存放次数,那么求出 k 个最大数据我们要考虑用小堆求解,但是我们不可能在算法里面完整写一个小堆,太麻烦,但是 好巧不巧 就是优先级队列底层就是 大小堆 实现的,我们将 优先级队列 优先级取反后 就可以得到一个小堆。 在比较q的元素个数还没满k个时 可以随便入,但是当等于k个了 ,就要开始考虑是否会出现 q 的顶部元素 的这个最小次数 是不是小于新来的比较元素,如果是,那么就删除q的top() 元素,然后加入新元素;否则就忽略。
7.将k个最大次数加入优先级队列 q 后,他会自动调整,保留最大的在下面,最小的在上面。就开始创建一个vector<int> ret来存放出现最大次数的数据。就开始ret.push_back(),这里push_back的元素是在hash[次数][0]表的最大次数的首元素。这里最大次数就从q.top()开始,加入一个就删一个,那么循环k次 ,取k次q.top()的元素。
ret.push_back(hash[q.top()][0]);
然后在进行删除hash[次数][首元素],因为在同一个次数中可能存在重复的元素,我们就要考虑要取出重复的元素,考虑到下次取元素还是取的hash[q.top()][0]; 有可能q的下一个元素跟q.top()的次数相同,那么就要删除当前hash[q.top()][0],保证下一个数据能成为首元素。 完成后最后进行q.pop() 寻找下一个最大次数即可。
class Solution {
public:
int index;
priority_queue<int, vector<int>, greater<int>> q;
void add(int val)
{
if (q.size() < index) q.push(val);
else
if (q.top() < val)
{
q.pop();
q.push(val);
}
}
vector<int> topKFrequent(vector<int>& nums, int k) {
index = k;
unordered_map<int, vector<int>> hash; //<次数,出现这么多次数的 元素>
int count = 1; //记录次数
int n = nums.size();
sort(nums.begin(), nums.end());
for (int i = 0; i < n; i++)
{
if (i<n-1&&nums[i] == nums[i + 1]) count++;
else
{
hash[count].push_back(nums[i]);
add(count);
count = 1;
}
}
vector<int> ret;
for (int i = 0; i < k; i++)
{
ret.push_back(hash[q.top()][0]);
hash[q.top()].erase(hash[q.top()].begin());
q.pop();
}
return ret;
}
};
五、总结
对于unordered_map<int,vector<int>> hash;有了更深层的理解,对于二维数组的引用不管是push_back() , 还是hash[ ].erase(hash[ ].begin())删除都有了更深的影响。
对于优先级队列priority_queue<> 底层是大小堆的实现有更深的了解 , 非常建议自己动手来实现一下大小堆!!
堆的实现
heap.h
#pragma once
#include <iostream>
#include <assert.h>
#include <algorithm>
#include <vector>
using namespace std;
typedef int HPDataType;
typedef struct Heap
{
public:
HPDataType* _a;
int _size;
int _capacity;
}Heap;
class heap
{
public:
//初始化
heap()
:hp(new Heap())
{
hp->_a = NULL;
hp->_capacity = hp->_size = 0;
}
堆的销毁
~heap()
{
if (hp->_a) delete[] hp->_a;
hp->_a = nullptr;
hp->_capacity = hp->_size = 0;
}
void Print()
{
for (int i = 0; i < hp->_size; i++)
{
cout << hp->_a[i] << " ";
}
cout << endl;
}
Heap*& c_get()
{
return hp;
}
int size()
{
return hp->_size;
}
//扩容
void reserve(int n);
//向上调整
void AdjustUp(int child);
//向下调整
void AdjustDonw(int left,int right);
// 堆的插入
void HeapPush(HPDataType x);
// 堆的删除
void HeapPop();
// 取堆顶的数据
HPDataType HeapTop();
// 堆的判空
int HeapEmpty();
//堆排
void HeapSort(int n);
private:
Heap* hp;
};
void Test_Heap1();
heap.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "heap.h"
void heap::reserve(int n)
{
if (n > hp->_capacity)
{
HPDataType* tmp = new HPDataType[n];
if (hp->_a)
{
for (int i = 0; i < size(); i++)
tmp[i] = hp->_a[i];
delete[] hp->_a;
}
hp->_a = tmp;
hp->_capacity = n;
}
}
// 堆的插入
void heap::HeapPush(HPDataType x)
{
assert(hp);
//扩容
if (size() == hp->_capacity)
{
reserve(hp->_capacity == 0 ? 4 : hp->_capacity*2);
}
hp->_a[size()] = x;
hp->_size++;
//AdjustUp(size() - 1);
}
//向上调整
//建小堆
void heap::AdjustUp(int child)
{
int parent = (child - 1) / 2;
int temp = hp->_a[child];
//while (parent>=0) parent 不会小于0
while (child > 0) //child == 0 时结束
{
if (temp < hp->_a[parent])
{
hp->_a[child] = hp->_a[parent];
child = parent;
parent = (child - 1) / 2;
}
else break;
}
hp->_a[child] = temp;
}
//向下调整
void heap::AdjustDonw(int left,int right)
{
int child = left * 2 + 1;
int temp = hp->_a[left];
while (child < right) //child >= n 就说明孩子不存在了 调整到叶子节点了
{
if (child < size() - 1 && hp->_a[child] > hp->_a[child + 1]) child++;
if (temp > hp->_a[child])
{
hp->_a[left] = hp->_a[child];
left = child;
child = left * 2 + 1;
}
else break;
}
hp->_a[left] = temp;
}
// 堆的删除
void heap::HeapPop()
{
//交换第一个元素和最后一个元素
std::swap(hp->_a[0], hp->_a[size() - 1]);
hp->_size--;
//向下调整
AdjustDonw(hp->_size, 0);
}
HPDataType heap::HeapTop()
{
return hp->_a[0];
}
// 堆的判空
int heap::HeapEmpty()
{
return size() == 0;
}
//堆排
void heap::HeapSort(int n)
{
//建堆
//for (int i = n - 1; i >= 0; i--) AdjustUp(i);
for (int i = (n - 1) / 2; i >= 0; i--) AdjustDonw(i, n);
Print();
//排序
for (int i = size() - 1; i > 1; --i) // i > 1 重点
{
std::swap(hp->_a[0], hp->_a[i]);
AdjustDonw(0, i - 1); //最后 i = 2,要保证最后传入donw是i-1=1 是最后一个数
}
}
Test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "heap.h"
void Test_Heap1()
{
vector<int> a = { 4,2,8,1,5,6,9,7 };
heap h;
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
h.HeapPush(a[i]);
}
h.Print();
/*for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
cout << h.c_get()->_a[0] << " ";
h.HeapPop();
}
cout << endl;*/
h.HeapSort(sizeof(a) / sizeof(int));
h.Print();
}
int main()
{
Test_Heap1();
return 0;
}