【二十】【C++】优先队列的简单实现和215. 数组中的第K个最大元素

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

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

示例 2:

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

提示:

  • 1 <= k <= nums.length <= 10(5)

  • -10(4) <= nums[i] <= 10(4)

 
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.end());
        k = k - 1;
        while (k--) {
            pq.pop();
        }
        return pq.top();
    }
};

优先队列的简单实现

 

#include <iostream>
using namespace std;

#include <vector>
// priority_queue--->堆
namespace Mypriority_queue {
    template<class T>
    struct less {
        bool operator()(const T& left, const T& right) {
            return left < right;
        }
    };

    template<class T>
    struct greater {
        bool operator()(const T& left, const T& right) {
            return left > right;
        }
    };

    template<class T, class Container = std::vector<T>, class Compare = less<T>>
    class priority_queue {
        public:
            // 创造空的优先级队列
            priority_queue() : c() {}

            template<class Iterator>
            priority_queue(Iterator first, Iterator last)
                : c(first, last) {
                // 将c中的元素调整成堆的结构
                int count = c.size();
                int root = ((count - 2) >> 1);
                for (; root >= 0; root--)
                    AdjustDown(root);
            }

            void push(const T& data) {
                c.push_back(data);
                AdjustUP(c.size() - 1);
            }

            void pop() {
                if (empty())
                    return;

                swap(c.front(), c.back());
                c.pop_back();
                AdjustDown(0);
            }

            size_t size()const {
                return c.size();
            }

            bool empty()const {
                return c.empty();
            }

            // 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
            const T& top()const {
                return c.front();
            }
        private:
            // 向上调整
            void AdjustUP(int child) {
                int parent = ((child - 1) >> 1);
                while (child) {
                    if (Compare()(c[parent], c[child])) {
                        swap(c[child], c[parent]);
                        child = parent;
                        parent = ((child - 1) >> 1);
                    } else {
                        return;
                    }
                }
            }

            // 向下调整
            void AdjustDown(int parent) {
                size_t child = parent * 2 + 1;
                while (child < c.size()) {
                    // 找以parent为根的较大的孩子
                    if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
                        child += 1;

                    // 检测双亲是否满足情况
                    if (Compare()(c[parent], c[child])) {
                        swap(c[child], c[parent]);
                        parent = child;
                        child = parent * 2 + 1;
                    } else
                        return;
                }
            }
        private:
            Container c;
    };
 }

void TestQueuePriority() {
    Mypriority_queue::priority_queue<int> q1;
    q1.push(5);
    q1.push(1);
    q1.push(4);
    q1.push(2);
    q1.push(3);
    q1.push(6);
    cout << q1.top() << endl;

    q1.pop();
    q1.pop();
    cout << q1.top() << endl;

    vector<int> v{ 5, 1, 4, 2, 3, 6 };
    Mypriority_queue::priority_queue<int, vector<int>, Mypriority_queue::greater<int>> q2(v.begin(), v.end());
    cout << q2.top() << endl;

    q2.pop();
    q2.pop();
    cout << q2.top() << endl;
 }
int main() {
    TestQueuePriority();
 }

lessgreater 的仿函数

 
    template<class T>
    struct less {
        bool operator()(const T& left, const T& right) {
            return left < right;
        }
    };

    template<class T>
    struct greater {
        bool operator()(const T& left, const T& right) {
            return left > right;
        }
    };

这两个模板结构体 lessgreater 是比较函数对象(或称为仿函数),它们分别实现了小于和大于的比较逻辑。在C++中,这种模式允许你将比较行为封装在一个对象中,这样的对象可以在需要进行比较操作的地方使用,比如排序算法、容器(如 std::setstd::priority_queue)等。

less 结构体

less 结构体模板接受一个类型参数 T,并重载了 operator(),使得该结构体的实例可以像函数一样被调用。

当你创建一个 less 实例并调用它时,它会比较两个类型为 T 的参数,并返回一个 bool 值,指示第一个参数是否小于第二个参数。

这在默认情况下用于构建最大堆或进行升序排序。

greater 结构体

greater 结构体模板同样接受一个类型参数 T,并重载了 operator(),允许它比较两个类型为 T 的参数。

它返回一个 bool 值,指示第一个参数是否大于第二个参数。

这通常用于构建最小堆或进行降序排序。

priority_queue 的模版参数

 
template<class T, class Container = std::vector<T>, class Compare = less<T>>
    class priority_queue {

模板参数

T:队列中元素的类型。

Container:用于底层存储的容器类型,默认为 std::vector<T>。这个容器需要支持随机访问迭代器、back()push_back()pop_back() 操作。

Compare:比较函数对象的类型,默认为 less<T>,这意味着默认情况下队列是一个最大堆。你可以通过提供不同的比较函数对象来改变队列的行为,例如使用 greater<T> 来创建一个最小堆。

成员变量

Container c;

创建底层存储的容器类型的对象c。

构造函数

 
 // 创造空的优先级队列
            priority_queue() : c() {}

            template<class Iterator>
            priority_queue(Iterator first, Iterator last)
                : c(first, last) {
                // 将c中的元素调整成堆的结构
                int count = c.size();
                int root = ((count - 2) >> 1);
                for (; root >= 0; root--)
                    AdjustDown(root);
            }

默认构造函数

它使用成员初始化列表来初始化底层容器 c。这里,c() 调用了容器的默认构造函数,创建一个空的容器。如果 Containerstd::vector<T>,这相当于创建了一个空的 std::vector

范围构造函数

这个构造函数允许从一个现有的元素范围创建优先队列,其中 firstlast 是迭代器,分别指向范围的开始和结束。它通过成员初始化列表初始化底层容器 c,使得 c 包含了给定范围内的所有元素。

接着,构造函数通过一系列向下调整操作,将这些元素调整成堆的结构。这是通过从最后一个非叶子节点开始,向根节点逐个进行向下调整操作来完成的。这里使用的计算方法 ((count - 2) >> 1) 是找到最后一个元素的父节点的索引,这个父节点也是最后一个需要进行向下调整的节点。向下调整确保每个节点都遵循堆的性质:对于最大堆,任何给定节点的值都大于其子节点的值;对于最小堆,则相反。

在一个堆中,父节点和子节点之间的下标关系可以通过简单的数学计算得到。假设数组表示堆,下标从0开始:

  • 对于任意节点 i 的父节点,其下标为 (i-1)/2。如果 i 是根节点(即 i = 0),则其父节点也是它自己。

  • 对于任意节点 i 的左子节点,其下标为 2i + 1。

  • 对于任意节点 i 的右子节点,其下标为 2i + 2。

push、pop、size、empty、top函数

 
            void push(const T& data) {
                c.push_back(data);
                AdjustUP(c.size() - 1);
            }

            void pop() {
                if (empty())
                    return;

                swap(c.front(), c.back());
                c.pop_back();
                AdjustDown(0);
            }

            size_t size()const {
                return c.size();
            }

            bool empty()const {
                return c.empty();
            }

            // 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
            const T& top()const {
                return c.front();
            }

push(const T& data)

将新元素添加到优先队列的底层容器末尾。

调用 AdjustUP 方法,从添加元素的位置开始向上调整堆,以保持堆的特性。这确保了所有父节点都会大于(或小于,取决于比较准则)它们的子节点。

pop()

如果队列为空,则直接返回,不执行任何操作。

交换底层容器中的第一个元素(堆顶元素)和最后一个元素,然后移除最后一个元素(原堆顶元素)。

调用 AdjustDown 方法从根节点开始向下调整堆,以保持堆的特性。

size() const

返回优先队列中元素的数量,即底层容器的大小。

empty() const

检查优先队列是否为空,即底层容器是否不包含任何元素。

top() const

返回对优先队列顶部元素的引用,即底层容器的第一个元素。

该方法标记为 const,说明调用它不会修改优先队列的状态。

注意,堆顶元素不允许通过 top 方法直接修改,因为任何修改都可能破坏堆的结构特性。如果需要修改堆顶元素,应先将其 pop 出队列,做出修改后再通过 push 方法重新添加到队列中,以确保堆的整体特性得以保持。

AdjustUP 向上调整算法

 
            // 向上调整
            void AdjustUP(int child) {
                int parent = ((child - 1) >> 1);
                while (child) {
                    if (Compare()(c[parent], c[child])) {
                        swap(c[child], c[parent]);
                        child = parent;
                        parent = ((child - 1) >> 1);
                    } else {
                        return;
                    }
                }
            }

child 是新加入堆的元素的索引,或者是需要向上调整的堆元素的索引。

计算父节点索引:通过表达式 ((child - 1) >> 1) 计算出当前节点的父节点索引。这里使用的是位运算符 >>,实际上是对 (child - 1) 进行除以 2 的操作,符合堆结构的父子关系定位方法。

循环向上调整:当 child 不是根节点(即 child 的索引不为 0)时,循环开始。比较当前节点(child)与其父节点(parent)的值:

如果当前节点应该在父节点之前(对于最大堆,意味着 child 节点的值大于 parent 节点的值;对于最小堆,则相反),则交换它们的值,并更新 childparent 的索引,继续循环。

如果不需要交换(即已满足堆的条件),则结束循环。

Compare() 函数对象:Compare 是在类模板参数中指定的比较函数对象类型,用于决定元素之间的排序准则。通过调用 Compare()(c[parent], c[child]),判断是否需要交换父子节点来保持堆性质。这种方式允许灵活定义堆的性质,比如构建最大堆或最小堆。

AdjustDown 向下调整算法

 
            // 向下调整
            void AdjustDown(int parent) {
                size_t child = parent * 2 + 1;
                while (child < c.size()) {
                    // 找以parent为根的较大的孩子
                    if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
                        child += 1;

                    // 检测双亲是否满足情况
                    if (Compare()(c[parent], c[child])) {
                        swap(c[child], c[parent]);
                        parent = child;
                        child = parent * 2 + 1;
                    } else
                        return;
                }
            }

parent 是需要向下调整的节点的索引。

初始化子节点索引:计算出 parent 节点的左子节点索引,即 parent * 2 + 1。这是因为在堆的数组表示中,给定父节点索引为 i,其左子节点索引为 2*i + 1

循环向下调整:当 child 索引在容器 c 的范围内时,循环开始。这个循环确保如果还有子节点,就继续检查和调整。

选择较大(或较小)的子节点:如果 child 不是最后一个节点,并且右子节点(child + 1)的值大于左子节点的值(对于最大堆),则 child 索引加一,指向较大的子节点。对于最小堆,比较逻辑会相反,选择较小的子节点。

交换父子节点:如果父节点的值小于所选子节点的值(对于最大堆),则交换父子节点的值,并更新 parentchild 的索引,以继续向下调整。对于最小堆,如果父节点的值大于所选子节点的值,则进行交换。

终止条件:如果父节点的值不需要与子节点交换(即已满足堆的条件),则结束循环。

Compare() 函数对象:这里使用了 Compare 函数对象来决定元素之间的比较准则,允许这个方法灵活地应用于构建最大堆或最小堆。通过 Compare()(c[child], c[child + 1]) 判断是否需要选择右子节点,以及通过 Compare()(c[parent], c[child]) 判断是否需要进行父子节点的交换。

对向上调整算法和向下调整算法的探究

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 32
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值