一个题目教会你使用 priority_queue<int,vector<int>> q 优先级队列 和 unordered_map<int,vector<int>> hash 二维数组 hash表

目录

一、熟悉priority_queue>  q,vector

1.priority_queue 又称优先级队列 

二、熟悉unordered_map> hash ,vector

1).首先熟悉一下unordered_map hash;,int>

2).对于unordered_map> hash 的理解,vector

3).常用函数接口:

三、通过priority_queue>  q 明白大小堆的构造,vector

四、题目(前k个高频元素)

五、总结

堆的实现


一、熟悉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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值