力扣C++刷题学习记录(堆排(优先队列),快排)-3.16


一、堆排

堆排结构就是一个完全二叉树,思路是创建大根堆即最大的值在堆顶,或是小根堆即最小的值在堆顶,一般可用优先队列来模拟堆。

1.自己创建堆

以力扣215. 数组中的第K个最大元素为例.

示例

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

在这个过程中,需要将数组转化堆的形式重新排列,即完全二叉树的形式排列。

先得到heapsize,可以看作树的节点数,完全二叉树的叶节点是总节点数的n/2或(n-1)/2,在堆建立过程中我们只需要排序根节点,所以bulidmaxheap()函数保证了这一点。

紧接着,要做的就是让根节点和左孩子右孩子节点相比较,在三者中选出最大值作为根节点,递归这一操作,保证最后整个树的根节点是最大值,maxheapify()函数执行这一步。

创建完堆后,因为要找的是第K大的元素,所以需要删除并重建堆来不断更新堆顶元素,以找到我们想要的目标,findKthLargest()的for循环里做了这件事。其中具体的操作是,每次将堆最后的叶子节点(nums[n-1],这里的n表示nums.size())和堆顶元素交换,并且将heapsize-1,这样可以看作整个堆删除了原来的堆顶元素,然后进入maxheapify()函数,重建堆,删除k-1次后的堆顶元素就是题目所要求的值。

class Solution {
public:
    void maxheapify(vector<int>&arr, int i, int heapsize) {
        int lchild = i * 2 + 1;//左孩子节点的下标
        int rchild = i * 2 + 2;//右孩子节点的下标
        int maxnum = i;//记录根左右三者中最大值的下标
        if (lchild<heapsize && arr[lchild]>arr[maxnum])
        {
            maxnum = lchild;
        }//如果这个根节点有左孩子并且左孩子的值大于根节点的值,更新最大值下标
        if (rchild<heapsize && arr[rchild]>arr[maxnum])
        {
            maxnum = rchild;
        }//如果这个根节点有右孩子并且右孩子的值大于最大值下标的值,更新最大值下标
        if (maxnum != i)
        {
            swap(arr[i], arr[maxnum]);
            maxheapify(arr, maxnum, heapsize);
        }//如果最大值下标有所改变,替换根节点的值和最大值下标的值,并且进入递归挨个创建堆
    }
    void bulidmaxheap(vector<int>&arr, int heapsize)
    {
        for (int i = heapsize / 2; i >= 0; --i)//剔除叶子节点,保证从根节点开始建立堆
        {
            maxheapify(arr, i, heapsize);
        }
    }
    int findKthLargest(vector<int>& nums, int k) {
        int heapsize = nums.size();//堆容量
        bulidmaxheap(nums, heapsize);//进入创建堆函数
        for (int i = nums.size() - 1; i >= nums.size() - k+1; --i) {
            swap(nums[0], nums[i]);
            --heapsize;
            maxheapify(nums, 0, heapsize);//删除堆顶元素并重建堆
        }
        return nums[0];
    }
};

2.用优先队列

优先队列的定义

priority_queue<type,container,function>q;

第一项表示数据类型,第二项表示实现堆的容器,并且必须是用数组实现的容器,默认是vector,第三项表示比较方式

for example

priority_queue<int>q;//默认大根堆

priority_queue<int,vector<int>,greater<int>>//小根堆,升序队列

priority_queue<int,vector<int>,less<int>>//大根堆,降序队列

常常会出现自定义类型的数据传入优先队列,这时候function需要做一些改变。
参考这个网址上的内容

优先队列常规操作和队列类似

push()
pop()
size()
empty()
top()
emplace()

以力扣347.前 K 个高频元素为例.

示例:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

首先用哈希表存储每个元素的出现频率

(关于这点,这里也想过vector数组也能做同样的事,为什么要用unordered_map,然后发现进入for循环后有个直接带下标赋值的过程,如果是vector没有初始化的话是不能直接赋值的,要是初始化的话又不知道初始化元素个数到底是多少,但是map初始值默认为0,可以直接赋值,具体底层两个容器结构是怎么实现的也不是很懂…也不知道具体对不对…需要后续详细了解和学习了)

然后将哈希表的值以pair<int,int>的形式push进优先队列

再弹出优先队列的队顶元素,并存储在res数组中,找到题目所需的前k个值。

struct cmp{
    bool operator()(pair<int,int> a,pair<int,int> b){
        return a.second<b.second;
    }
};//自定义数据类型
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        vector<int>res;//存储结果的数组
        using e=pair<int,int>;//自定义的数据类型
        priority_queue<e,vector<e>,cmp>que;//定义优先队列
        unordered_map<int,int>count;//存储每个元素出现频率的哈希表
        int n=nums.size();
        for(int i=0;i<n;i++)
        {
            count[nums[i]]++;
        }
        for(auto i:count)
        {
            que.push(make_pair(i.first,i.second));
        }
        while(k--)
        {
            int x=que.top().first;
            que.pop();
            res.push_back(x);
        }
        return res;
    }
};

二、快排

快排运用分治的思想,总的来说就是让比index(假设其为要找的值)小的子数组排在index的一边,比index大的子数组排在index的另一边。

还是以力扣215. 数组中的第K个最大元素为例.

首先srand(time(0))表示随机种子,力扣官方题解里面引入随机化来加速递归过程,它的时间代价的期望是 O(n),然而rand()函数提供的值是不变的,所以引入随机时间的种子,使得rand()函数值有所改变。

randompartition()函数随机选择数组中的值作为暂时的index,并将其和最右边的值对换位置,使index位于最大值的位置。

sonnum()函数执行最主要的操作,即将比index大的值和小的值分别放在两边。

quickselect()找到index,此时下标值就是该值在数组中排序的位置,如果不是则进入递归。

class Solution {
public:
    int quickselect(vector<int>&nums,int l,int r,int k)
    {
        int q=randompartition(nums,l,r);
        if(q==k)
        {
            return nums[q];
        }
        else
        {
            return q<k?quickselect(nums,q+1,r,k):quickselect(nums,l,q-1,k);
        }
    }
    inline int randompartition(vector<int>&nums,int l,int r)
    {
        int i=rand()%(r-l+1)+l;//随机一个值
        swap(nums[i],nums[r]);//将其置于最右边
        return sonnum(nums,l,r);
    }
    inline int sonnum(vector<int>&nums,int l,int r)
    {
        int x=nums[r];
        int i=l-1;
        for(int j=l;j<r;j++)
        {
            if(nums[j]<=x)
            {
                swap(nums[++i],nums[j]);
               /*注意这里必须用++i,不能用i++,++i才能用作左值,因为第一步,i初始值为-1
               ++i是先+1再赋值,而i++是赋值再+1,而nums[-1]不存在,所以代码会报错*/
            }
        }//把比index大的值排到后面来,比index小的值排到前面去
        swap(nums[i+1],nums[r]);//交换比index大的值和index的位置
        return i+1;//这时候index的位置代表index在原数组中真实的排序位置
    }
    
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(0));//随机种子
        return quickselect(nums,0,nums.size()-1,nums.size()-k);
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习Java通常需要按照以下步骤来进行: 1. **基础入门**:首先了解Java的基础知识,包括数据类型、变量、运算符、流程控制(如循环、条件判断)、数组和集合等。推荐从官方文档开始,如《Java语言规范》(JLS) 和《Java核心技术》系列书籍。 2. **语法与面向对象编程**:深入理解类、对象、封装、继承、多态等核心概念,并通过编写小程序练习。LeetCode、菜鸟教程等网站有丰富的实例供参考。 3. **Java SE**:学习Java Standard Edition (SE),包括文件I/O、异常处理、网络编程等内容。同时,要熟悉Java标准库,特别是ArrayList、HashMap等常用容器。 4. **数据结构与算法**:掌握基本的数据结构(如栈、队列、链表、树、图),以及一些经典算法。这些对于解决实际问题至关重要。 5. **实战项目**:尝试做一些小型项目,比如计算器、图书管理系统等,提升应用能力。这有助于理解和运用所学知识。 6. **进阶学习**:如果对设计模式、并发编程(线程、锁、并发工具类)感兴趣,可以进一步研究。Spring框架、MySQL数据库操作也是必修课程。 7. **力扣刷题**:达到一定程度后,你可以开始在LeetCode刷题。一般来说,熟悉并能够解决大多数Easy级别的题目(约占总题目的80%左右),再逐渐挑战Medium和Hard级别,边做题边思考解法,能有效检验和提高编程水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值