priority_queue的模拟实现和仿函数

priority_queue模拟
首先查看源代码,源代码就在queue剩下的部分中
push_heap是STL库中的堆算法,STL库中包装有支持堆的算法,在algorithm.h中:
只要不断用堆的形式插入数据,就会形成堆。
priority_queue模拟——初版
priority_queue.h中
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
template<class T, class container = vector<int>>
class myPriority_queue
{
public:
        void adjustUp(int lea) //向上调整算法,在插入数据后,将数据重新调整为堆时使用,lea为尾节点下标
        {
               if (lea == 0)
                       return;
               size_t fa = (lea - 1) / 2;
               while (_con[fa] < _con[lea])
               {
                       swap(_con[fa], _con[lea]);
                       lea = fa;
                       fa = (lea - 1) / 2;
               }
        }
        void adjustDown() //向下调整算法,在删除数据后,使空间重新调整为堆时使用,从根节点开始调整,不需要传入数据
        {
               size_t ch = 1; //代表子节点
               size_t fa = 0; //代表父节点
               while (ch < _con.size() && _con[fa] < _con[ch])
               {
                       if (ch + 1 < _con.size() && _con[ch] < _con[ch + 1]) //开始默认左子节点大
                              ch++;
                       swap(_con[fa], _con[ch]);
                       fa = ch;
                       ch = fa * 2 + 1;
               }
        }
        void push(const T& x)
        {
               _con.push_back(x);
               int lea = _con.size() - 1;
               adjustUp(lea);
        }
        void pop()
        {
               assert(!_con.empty());
               size_t tail = _con.size() - 1;
               swap(_con[0], _con[tail]);
               _con.pop_back();
               adjustDown();
        }
        const T& top()
        {
               return _con[0];
        }
        size_t size()
        {
               return _con.size();
        }
        bool empty()
        {
               return _con.empty();
        }
private:
        container _con;
};
test.cpp中
#define _CRT_SECURE_NO_WARNINGS 1
#include"priority_queue.h"
#include<iostream>
#include<vector>
using namespace std;
void test_priority_queue()
{
        myPriority_queue<int> pri_qu;
        pri_qu.push(8);
        pri_qu.push(2);
        pri_qu.push(5);
        pri_qu.push(1);
        pri_qu.push(4);
        pri_qu.push(3);
        cout << pri_qu.empty() << " " << pri_qu.size() << endl;
        while(!pri_qu.empty())
        {
               cout << pri_qu.top() << " ";
               pri_qu.pop();
        }
        cout << endl;
}
int main()
{
        test_priority_queue();
        return 0;
}
这样写出来的优先级队列只能排升序,而实际上的优先级队列是可以排升序,也可以排降序的。
实现升序降序控制的叫仿函数,即一开始priority_queue源代码看到的第三个模板参数。
所谓仿函数函数实际上是一个对象,在对象中重载了 '()' 符号,已知 '()' 符号有两种用法,一是(表达式),可以用于数据类型强转,或者控制优先级;一是作函数调用,这里重载的就是函数调用,将类写成一个类似函数的使用方式,所以叫仿函数。也叫函数对象。
下面写一个简单的仿函数:
class less
{
    bool operator()(int x, int y)
    {
        return x < y;
    }
};
使用这个类:
less  l1;
bool ret = l1(10, 20)
将仿函数修改为泛型:
template<class T>
class less
{
    bool operator()(const T& x, const T& y) const
    {
        return x < y; //支持的前提条件是类型要支持用 < 符号来进行大小的比较
    }
};
仿函数是一个类,是类就可以作为参数传递。
priority_queue模拟——进阶版(只是添加上了仿函数)
priority_queue.h中
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
template<class T>
class myless
{
public:
        bool operator()(const T& x, const T& y) const
        {
               return x < y;
        }
};
template<class T>
class mygreater
{
public:
        bool operator()(const T& x, const T& y) const
        {
               return x > y;
        }
};
template<class T, class container = vector<int>, class compare = myless<T>> //和库中一样,默认为大堆
class myPriority_queue
{
public:
        void adjustUp(int lea) //向上调整算法,在插入数据后,将数据重新调整为堆时使用,lea为尾节点下标
        {
               compare func;
               if (lea == 0)
                       return;
               size_t fa = (lea - 1) / 2;
                /*while (_con[fa] < _con[lea])*/
               while (func(_con[fa], _con[lea]))
              //利用仿函数达到比较的效果
             //现在不清楚是大于还是小于了,具体将取决于传入compare的类。
             //通过控制仿函数的类型,达到控制大堆还是小堆的效果。
               {
                       swap(_con[fa], _con[lea]);
                       lea = fa;
                       fa = (lea - 1) / 2;
               }
        }
        void adjustDown(size_t father = 0)
         //向下调整算法,在删除数据后,使空间重新调整为堆时使用
        //默认从根节点开始调整,在建堆时使用就需要传入第一个非叶子节点
        {
               compare func;
               size_t fa = father; //代表父节点
               size_t ch = fa * 2 + 1; //代表子节点
                /*while (ch < _con.size() && _con[fa] < _con[ch])*/
               while (ch < _con.size() && func(_con[fa], _con[ch]))
               {
                        //if (ch + 1 < _con.size() && _con[ch] < _con[ch + 1])//开始默认左子节点大
                       if (ch + 1 < _con.size() && func(_con[ch], _con[ch + 1]))
                              ch++;
                       swap(_con[fa], _con[ch]);
                       fa = ch;
                       ch = fa * 2 + 1;
               }
        }
        //push,pop,top,size,empty部分和上面一样,这里省略
private:
        container _con;
};
test.cpp中
#define _CRT_SECURE_NO_WARNINGS 1
#include"priority_queue.h"
#include<iostream>
#include<vector>
using namespace std;
void test_priority_queue()
{
        myPriority_queue<int,vector<int>, mygreater<int>> pri_qu; //通过传仿函数控制为小堆,默认为大堆。
        
        //剩下的部分一样,这里省略
}
int main()
{
        test_priority_queue();
        return 0;
}
利用模板实现不同比较的调用过程。
当使用默认传参时,compare被myless<T>赋值,compare创建的func对象,是myless的对象,func(_con[fa], _con[lea])会调用myless类中的成员函数,完成比较。当使用mygreater<T>传参时,compare被mygreater<T>赋值,compare创建的func对象,是mygreater的对象,func(_con[fa], _con[lea])会调用mygreater类中的成员函数,完成比较。
下面函数指针的内容了解就可以:
less和greater都是在库中包装好的仿函数,使用需要包头文件<functional>。一般利用仿函数的优势来替换函数指针。如果myPriority_queue传给compare的不是仿函数,而是函数指针会如何?
准备知识:
1.对函数指针解引用,找到对应函数,调用参数,和直接使用函数指针调用参数是一样的,即(*函数指针)(函数参数1.函数参数2)与函数指针(函数参数1,函数参数2)等价,代表函数指针可以直接使用()操作符,这样就能通过函数指针指向对应的比较函数,完成比较。
2.模板会自动推导类型,传什么就是什么,不会构造成单成员变量的类。
先来看一下函数指针的应用:
这是一个函数,*和()相比,()优先级更高,函数名与()结合,为函数。括号内的内容,即函数参数为void (*f)(),f是名称,与*结合,是函数指针,指向一个返回值为void,没有参数的函数。最后剩下:static void(* )()是返回值,即函数的返回值是一个函数指针,指针指向的类型为无参,返回值为static void。
模板具备自动推导的功能,可以兼容使用函数指针完成比较。
bool comIntLess(int x, int y)
{
    return x > y;
}
myPriority_queue<int,vector<int>, bool(* )(int, int)> pri_qu; //模板参数要传类型
按照想象,应该是传入函数指针,compare推导出指针类型,创建func函数指针变量,func(参数1,参数2)调用comIntLess函数。
但是直接这样替换myPriority_queue<int,vector<int>, greater<int>> pri_qu;会出错。
因为compare推导出函数指针类型后,compare func创建的变量是一个没有初始化的函数指针变量,函数指针指向未知。传过去的是类型,没有指向具体函数,不知道调用哪个函数。
要用函数指针,需要在构造的地方传入。同时要兼顾仿函数的传入,这就需要在一些地方做修改:简单来说就是把负责比较的类变成成员变量。
1.添加一个成员变量_comfunc
2.写myPriority_queue的构造函数
myPriority_queue(const compare& x = compare())
    :_comfunc(x)
{}
//传给compare为仿函数类时,x是类,传给compare为函数指针时,x是指针。类创建的_comfunc对象可以调用类的成员函数
//这样是方便传入函数指针,即使不写构造函数,仿函数依然能正常使用,自定义类型为成员变量时会自动调用构造函数,也就是compare()。单凭模板是无法将函数地址传入myPriority_queue中。
//即使这样也不能在传模板参数的时候传入函数指针,而是通过构造函数完成指针的传值。
3.将所有func改为_comfunc
4.在创建优先级队列的地方将函数指针传入:myPriority_queue<int,vector<int>, bool(* )(int, int)> pri_qu(comIntLess)
总之,函数指针也能达到仿函数的效果,但比仿函数复杂,这还只是一个简单函数的情况下。
大多数情况下库中的仿函数就够用,在一些情况下需要自己写。
以c++sort为例:
可以看见,sort中也是有仿函数的。
运行如下程序,及其运行结果:
sort的传参和优先级队列仿函数传参不同的是多了一个(),原因在于优先级队列传的是模板参数,这里传的是对象,优先级队列是在内部创建对象,sort是将创建好的对象传入sort使用。sort是一个函数模板(优先级队列是类模板),传递的是函数的参数,所以这里传的应该是对象,而不是类型。
当需要对自定义类型进行排序的时候,就需要自己写仿函数了,有点类似运算符重载。
sort可以对数组进行排序吗?
数组空间连续,其迭代器本质就是指针,完全可以用sort对数组进行排序。
sort排序的基本原则之一就是要知道要排序对象的大小关系。或者说被排序的对象要有大小的比较规则。
这样的排序是会报错的,因为没有goods类的比较规则。当传入两个goods类的对象到less仿函数中,struct中没有对<的重载,也就无法比较大小,实现升序排序;要进行降序排序也需要对>进行重载。
所以要加上比较方式:
排序前:
排序后:
按照价格升序进行排列。但是也有没解决的问题,如果要按照销量进行排序呢?
<可以被重载两次吗?当然可以,但是要如何解决参数类型不同的问题呢?less中的比较是两个goods对象的比较,即<重载的函数参数就固定为两个goods对象。
这时候仿函数就有用了,就是比较麻烦。当然,这个麻烦是有解决方案的。解决方案需要以后的知识(c++11中)。
将仿函数传给compare作为比较方式。升序的排序方式也是需要仿函数手动实现的,所以说比较麻烦。
重载<和仿函数的区别在于:
重载<函数是被库中用于比较的less仿函数调用的,less在比大小时,调用了operator<;
而手动仿函数则是取代less,完成比较的目的(compare类型为lessPrice,在sort中创建一个lessPrice对象,这个对象来对传入的两个goods对象比大小)。
补充一点:优先级队列有靠区间构造的方式,这个还是很有用的,比如需要对一个数字进行TopK问题的解决,这时用数组的区间构造一个优先级队列就能解决。
在兼容函数指针的版本下继续修改:即新写一个构造函数要支持传迭代器完成建堆
myPriority_queue(InputIterator first, InputIterrator last, const compare& x = compare())
//这里是构造函数,所以没有Container
    :_comfunc(x)
{
    while(first != last)
    {
        _con.push_back(*first);
        first++;
    }
    for(size_t i = (_con.size() - 1 - 1) / 2; i >= 0; i--) //_con.size() - 1是尾节点,(尾节点-1)/2得到第一个非叶子节点
    {
        adjustdown(i);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值