前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >纯C++11标准写类topk算法(不稳定排序)类模板

纯C++11标准写类topk算法(不稳定排序)类模板

作者头像
10km
发布2022-05-07 10:03:54
4570
发布2022-05-07 10:03:54
举报
文章被收录于专栏:10km的专栏

topk排序是指从N个数据中找出最大/小的前K个数据,并以升/降序排列,本文讨论的topk与这个定义稍有差别(所以叫类topk算法):

从N个数据中将临时计算结果t满足阀值T(大于或小于T)的前K个数据找出,并以升/降序排列(不满足阀值的t不允许占用内存)。 比如,从今年中考成绩中挑出总成绩(总成绩是根据各科成绩加总临时计算的结果,不允许保存)大于520分的前20个学生。如果有50个大于520的学生,我也只挑前20个。如果只有10个学生总分大于520,那么结果就是10个。

最适合topk排序的无疑是堆排序算法(heap sort),但因为有阀值T的存在,而且加上”不满足阀值的数据不能占用内存”的要求,所以传统的堆排序算法并不适合(建堆的过程要求临时保存所有排序数据)。 为什么会有”不满足阀值的数据不能占用内存”这么看似奇怪的要求呢? 比如在一个数据库中有千万级至亿级的标明地点的gps位置数据。现在要从数据库中找出距离位置P 1公里以内最近的前100个地点。按传统的堆排序算法就要将P与所有这些地点计算出距离d1,d2,d3....dn,然后在内存中建堆,排序找出topk。这需要消耗大量内存的,很不现实,也很不经济,更没必要。 合适的做法是:

建立一个容量为100条记录的排序缓冲区数组SORT,初始时缓冲区是空的,排序结果数目SIZE为0。 对数据库中每个地址的位置计算与P的距离,当距离大于1公里时,这个地点自然被丢弃。 小于1公里时,把这个地点p按降序插入SORT(对于有序数组采取二分法查找获取插的位置),SORT中已经有的数据中所有小于p的数据自动向后移动一个数据单元,SIZE累加1。如果缓冲区满(SIZE==100)则最后的一条数据自动被丢弃,同时将最后一条记录的距离值做为最小阀值(比如0.9公里),用于下一次计算时的阀值。 对所有的地点处理完之后。SORT中自然是要求的前100个地点。

以下是使用纯C++11标准实现的代码

代码语言:javascript
复制
/*
 * topk_base.h
 *
 *  Created on: 2015年10月22日
 *      Author: guyadong
 */

#ifndef CMIMPL_TOPK_BASE_H_
#define CMIMPL_TOPK_BASE_H_
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <memory>
#include <minmax.h>
#include <functional>
#include <type_traits>
#include "utility.h"
using namespace std;
namespace gyd{
/* 计算比较函数的返回值类型 */
template<typename N>
struct compare_result{
    using type=typename std::conditional    <std::is_arithmetic<N>::value,decltype(declval<N>()-declval<N>()),int>::type;
};

/* 计算比较器对象(Callable Object)的类型 */
template<typename N>
struct compartor{
    using type=std::function<typename gdface::compare_result<N>::type(N,N)const noexcept>;
};
/* 定义比较器类型,并根据N,CMP,DEC选择合适的比较器类型
 * dec为降序,inc为升序
  */
template <typename N,typename compartor<N>::type *CMP,bool DEC>
struct comparator{
    /* 缺省的算术比较器,只适用于arithmetic类型 */
    struct arithmetic{
        struct dec{
            typename compare_result<N>::type inline value(N n1,N n2) const noexcept{return n1-n2;};
        };
        struct inc{
            typename compare_result<N>::type inline value(N n1,N n2) const noexcept{return n2-n1;};
        };
    };
    /* 使用CMP提供的自定义比较器  */
    struct other{
        struct dec{
            static_assert(nullptr != CMP,"nullptr==CMP");
            const typename compartor<N>::type&_comparator = *CMP;
            typename compare_result<N>::type inline value (N n1,N n2)const noexcept {return _comparator(n1-n2);};
        };
        struct inc{
            static_assert(nullptr != CMP, "nullptr==CMP");
            const typename compartor<N>::type&_comparator = *CMP;
            typename compare_result<N>::type inline value (N n1,N n2)const noexcept {return _comparator(n2-n1);};
        };
    };
    using type=typename std::conditional<
            nullptr!=CMP,
            typename std::conditional<DEC,typename other::dec,typename other::inc>::type,
            typename std::conditional<DEC,typename arithmetic::dec,typename arithmetic::inc>::type
            >::type;
};
template <typename T,typename N,typename compartor<N>::type *CMP=nullptr,bool DEC=true,typename Enable=void>
class topk_base;//通例

/*
 * topk排序(不稳定排序)类模板,
 * 对象内部有一个容量为m_capacity的排序缓冲区m_sort(初始化时生成),
 * 用insert方法每次加入一个要排序的对象T,当T的排序值N大于阀值时,将T按排序值插入m_sort(始终是一个有序数组)
 * T为要排序的对象类型,
 * N为排序依据的值类型,
 * CMP为比较器函数对象(Callable Object)指针
 * DEC为排序类型,true为降序,
 * N为数字/算术(arithmetic)类型时,CMP可以为nullptr,否则必须提供CMP,
 * 非算术类型的CMP函数返回值为int,函数签名int(N n1,N n2),
 * 如果希望n1要排在n2前面,则返回>0的整数,n1==n2,返回0,否则小0;
 * 特例: 禁止N为bool类型,比较值为bool类型无意义
 * + 操作符 将两个对象合并排序成新对象
 * += 操作符将参数对象合并到当前对象
 */
template <typename T,typename N,typename compartor<N>::type *CMP,bool DEC>
class topk_base<T,N,CMP,DEC,typename std::enable_if<!is_same<N,bool>::value>::type>{
    //静态断言:N为非算术类型时,CMP不能为nullptr
    static_assert(std::is_arithmetic<N>::value||nullptr!=CMP,"FUN must not be nullptr while N is not arithmetic");
public:
    using SIZE_TYPE =size_t;
protected:
private:
    /* 初始阀值 */
    const N m_threshold;
    /* 排序缓冲区容量 */
    const SIZE_TYPE m_capacity;
protected:
    /* 排序节点 */
    struct _CompareNode{
        const T* t;
        N  n;
    };
    /* 排序缓冲区 */
    unique_ptr<_CompareNode[]> m_sort;
    /* 缓冲区中已经排序元素个数,初始为零 */
    SIZE_TYPE m_size=0;
    /* 当前阀值指针,初始值指向m_threshold */
    const N *mp_threshold=&m_threshold;
public:
    using M_SIZE_TYPE=decltype(m_size);
private:
    /* 计算比较器类型 */
    using _CMP_TYPE=typename comparator<N,CMP,DEC>::type;
    /* 比较器对象 _compatator.value(n1,n2)返回值>0则n1排在n2前面 */
    _CMP_TYPE _compatator;
    /* 二分法获取新排序节点node在排序缓冲区的排序位置(循环) */
    auto rank(const _CompareNode &node, SIZE_TYPE from, SIZE_TYPE size)const noexcept->decltype(from){
        auto start=from;
        auto len=size;
        decltype(len>>1) mid;
        typename compare_result<N>::type cmp_res;
        do{
            if(len>1){
                mid=len >> 1;
                cmp_res=_compatator.value(node.n, m_sort[start+mid].n);
                if(cmp_res>0){
                    len-=mid;// 前半部搜索
                }else   if(cmp_res<0){
                    start+=++mid,len-=mid; // 后半部搜索
                }else
                    return start+mid;
            }else if(len==1){
                return _compatator.value(node.n,m_sort[start].n)>=0?start:start+1;
            }else
                return start;
        }while(true);
    }
    /* 更新当前阀值指针 */
    void inline update_threshold()noexcept{
        if(m_size==m_capacity){
            mp_threshold=&m_sort[m_size - 1].n;//当排序缓冲区满的时候,用最后一个节点的值更新当前阀值指针
        }
    }
    /* 断言两个对象类型相同 */
    static void inline assert_same_type(const topk_base &t1,const topk_base &t2)noexcept {
        assert(t1.m_capacity==t2.m_capacity&&t1.m_threshold==t2.m_threshold);
    }
public:
    /* 构造函数 maxRows必须>0 */
    topk_base(SIZE_TYPE maxRows,N threshold) noexcept:
        m_capacity(maxRows),
        m_threshold(threshold),
        m_sort(make_unique<_CompareNode[]>(maxRows)){
        assert(m_capacity>0);
    }
    /* 复制构造函数 */
/*  topk_base(const topk_base& v):
            m_capacity(v.m_capacity),
            m_threshold(v.m_threshold),
            m_sort(make_unique<_CompareNode[]>(v.m_capacity)),
            m_size(v.m_size){
        memcpy(m_sort.get(),v.m_sort.get(),sizeof(_CompareNode)*m_size);//只复制有效数据
    };*/
    /* 移动构造函数 */
    topk_base(topk_base&& rv)noexcept:
            m_capacity(rv.m_capacity),
            m_threshold(rv.m_threshold),
            m_size(rv.m_size){
        std::swap(m_sort,rv.m_sort);
    }
    /* 移动拷贝函数 */
    topk_base& operator=(topk_base&& rv) {
        if(m_capacity!=rv.m_capacity||m_threshold!=rv.m_threshold)
            throw logic_error("m_capacity==rv.m_capacity OR m_threshold==rv.m_threshold");
        m_size=rv.m_size;
        m_sort=std::move(rv.m_sort);
        return *this;
    };

    virtual ~topk_base()=default;

    /* 将obj加入topk,如果cmpValue小于当前阀值,则返回false */
    bool insert(const T& obj,const N cmpValue)noexcept {
        auto c=_compatator.value(cmpValue,*mp_threshold);
        // 大于阀值才进一步执行比较排序,小于阀值则返回
        if(c<0||(c==0&&m_size==m_capacity))
            return false;
        _CompareNode node{std::addressof<const T>(obj),cmpValue};
        //将排序节node点加入排序缓冲区
        auto offset = rank(node, 0, m_size);
        auto sort=m_sort.get();
        // 在offset位置插入节点,offset(含)之后的数据向后移一位
        memmove(sort + offset + 1, sort + offset,
                sizeof(_CompareNode) * (m_size < m_capacity ? m_size++ - offset : m_capacity - offset-1));
        sort[offset] = node;
        update_threshold();
        return true;
    }

    M_SIZE_TYPE size()const noexcept{return m_size;}
    /* 导出排序结果 */
    virtual M_SIZE_TYPE result(T * out)const noexcept{
        assert(nullptr!=out);
        for(M_SIZE_TYPE i=0;i<m_size;i++)out[i]=*m_sort[i].t;
        return m_size;
    }
    /* 将from中排序的数据合并到当前对象中
     * buf_in为排序用的临时对象,可以为nullptr,如果不为nullptr,类型必须与当前对象相同
     * 返回当前对象引用
     */
    topk_base& merge(const topk_base &from,topk_base * buf_in=nullptr) noexcept{
        if(from.m_size==0)
            return *this;
        else {
            if(nullptr==buf_in){
                topk_base buf(m_capacity,this->m_threshold);
                merge_to(from,buf);
                *this=std::move(buf);
            }else{
                merge_to(from,*buf_in);
                *this=std::move(*buf_in);
            }
        }
        return *this;
    }
    /* 将当前对象和from合并排序到目标对象to
     * 返回to引用
     */
    topk_base& merge_to(const topk_base &from,topk_base &to) noexcept{
        assert_same_type(from,*this);
        assert_same_type(from,to);
        assert(to.m_size==0);
        auto buf = to.m_sort.get();
        typename std::remove_const<decltype(m_capacity)>::type idx = 0;
        M_SIZE_TYPE idx_this = 0, idx_from = 0;
        decltype(rank(m_sort[0],0,0)) offset_this,offset_from;
        while ( idx < m_capacity&&idx_this < m_size&&idx_from < from.m_size&&idx_from < from.m_size) {
            offset_this=rank(from.m_sort[idx_from],idx_this,m_size-idx_this);
            if(offset_this==idx_this){
                offset_from = from.rank(m_sort[idx_this], idx_from + 1, from.m_size - idx_from - 1);
                auto copy_size=min(this->m_capacity-idx,offset_from-idx_from);
                memcpy(buf+idx,from.m_sort.get()+idx_from,copy_size);
                idx+=copy_size;
                idx_from += copy_size;
                if (idx < m_capacity) {
                    buf[idx++] = m_sort[idx_this++];
                }
            }else{
                auto copy_size=min(this->m_capacity-idx,offset_this-idx_this);
                memcpy(buf+idx,m_sort.get()+idx_this,copy_size);
                idx+=copy_size;
                idx_this += copy_size;
                if (idx < m_capacity) {
                    buf[idx++] = from.m_sort[idx_from++];
                }
            }
        }
        if (idx < m_capacity) {
            if (idx_from < from.m_size)
                memcpy(buf + idx, from.m_sort.get() + idx_from, sizeof(_CompareNode) * min(m_capacity-idx, from.m_size - idx_from));
            else if (idx_this < this->m_size)
                memcpy(buf + idx, m_sort.get()         + idx_this,   sizeof(_CompareNode) * min(m_capacity-idx, this->m_size - idx_this));
        }
        to.m_size = min(to.m_capacity, from.m_size + m_size);
        to.update_threshold();
        return to;
    }
    /* += 操作符 将参数对象合并到当前对象 */
    topk_base&  operator+=(const topk_base &from) noexcept{
        return merge(from);
    }
    /* + 操作符 将两个对象合并排序成新对象 */
    topk_base  operator+(const topk_base &from) noexcept{
        topk_base res(this->m_capacity,this->m_threshold);
        return std::move(merge_to(from,res));//转为右值引用以调用移动构造函数
    }
};/* topk_base */
}/* namespace gyd*/

#endif /* CMIMPL_TOPK_BASE_H_ */
//以上代码在gcc5/vs2015下编译通过

说明: 考虑的并行计算的需求,topk_base支持归并排序,merge方法就用于对两个已经排序过的topk_base对象进行归并排序,生成新的排序结果,新结果的容量m_capacity仍然不变。 在构造函数使用了上一篇文章《C++11:unique_ptr 自己定义类似make_shared的make_unique模板函数》中提到的make_unique函数(#included "utility.h"),所以代码中虽然有使用堆内存,但没有使用new/delete内存申请释放代码,内存管理都由智能指针unique_ptr自动完成。

代码语言:javascript
复制
topk_base(SIZE_TYPE maxRows,N threshold) noexcept:
        m_capacity(maxRows),
        m_threshold(threshold),
        m_sort(make_unique<_CompareNode[]>(maxRows)){
        assert(m_capacity>0);
    }

为减少手工定义数据类型经常出容量出现不一致的问题,代码中大量使用了C++11的新特性”类型推导”,除了类最开始的代码出现了具体数据类型using SIZE_TYPE =size_t;,类中所有的成员变量局部变量,返回值都由此推导。 例如:

代码语言:javascript
复制
using M_SIZE_TYPE=decltype(m_size);
M_SIZE_TYPE size()const noexcept{return m_size;}//根据m_size的类型推导size方法的返回值类型
代码语言:javascript
复制
        auto buf = to.m_sort.get();
        typename std::remove_const<decltype(m_capacity)>::type idx = 0;//根据m_capacity的类型推导idx的类型
        M_SIZE_TYPE idx_this = 0, idx_from = 0;//根据m_size的类型推导idx_this,idx_from的类型
        decltype(rank(m_sort[0],0,0)) offset_this,offset_from;//根据rank方法的返回类型定义offset_this,offset_from的类型

以下是topk_base调用方法的示范代码

代码语言:javascript
复制
    topk_base<code_bean,float>top(200, 0.75f);
    //迭代器循环调用insert,将当前对象*itor和临时计算出来的排序依据值(FCUtils::compare(itor->code, code))加入top
    for (auto itor = begin(); !itor.end(); ++itor) {
        top.insert(*itor, FCUtils::compare(itor->code, code));
    }
    //循环结束,top对象就是排序结果。可以调用 size()获取排序结果数目,然后调用 result方法获取排序结果。

    topk_base<code_bean,float> tb1(200,0.5f);//创建一个对象
    topk_base<code_bean,float> tb2(200,0.5f);
    tb1.merge(tb2);//合并tb1到tb1
    topk_base<code_bean,float> tb3=tb1+tb2;//tb1,tb2合并到tb3
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015-11-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档