C++ 模板实现败者树,进行多路归并

项目需要实现一个败者树,今天研究了一下,附上实现代码。

几点说明:

1. 败者树思想及实现参考这里:http://www.cnblogs.com/benjamin-t/p/3325401.html

2. 多路归并中的“多路”的容器使用的是C语言数组 + 数组长度的实现(即

const ContainerType* ways, size_t num

),而没有用STL中的容器,这是因为项目需要如此,日后再改成STL容器;

3. _losers 存储下标,用的是 int 类型,还需要修改。程序中其他下标类型都是 size_t,但是这个 _losers 存的下标需要使用 -1 表示无效。

4. Foo 还不能用在 std::copy上,待修正;

5. 使用了 FooContainer<Foo> 类型以后,输出的时候不能直接输出,必须定义一个变量再输出,不知道为什么:

            //std::cout << data[i][j] << ", ";  
            Foo foo = data[i][j];
            std::cout << foo << ", ";

6. 把 const 变量作为 non-type template parameter 时,必须把该 const 变量定义在全局,并且加 extern,原因见这里:http://stackoverflow.com/questions/9183485/const-variable-as-non-type-template-parameter-variable-cannot-appear-in-a-const

7. 代码放在Github上:https://github.com/qinpeixi/code-pieces

8. 一个专业级的实现在这里:https://github.com/MITRECND/snugglefish

代码如下:

#include <iostream>
#include <vector>
#include <iterator>
#include <string>
#include <sstream>
#include <cstdlib>
#include <cassert>

class Foo {
public:
    Foo() {}
    explicit Foo(int v): _v(v) {}
    Foo(const Foo& foo) { _v = foo._v; }
    int value() const { return _v; }
    Foo& operator=(const Foo& foo) { _v = foo._v; return *this; }
    bool operator==(const Foo& foo) { return _v == foo._v; }

private:
    int _v;
};
std::ostream& operator<<(std::ostream& os, Foo& foo) {
    return os << foo.value();
}

extern const Foo FOO_MAX(INT_MAX);

namespace  std {
template<>
class less<Foo> : std::binary_function<Foo, Foo, bool>
{
public:
    bool operator() (const Foo& x, const Foo& y) const {
        return x.value() < y.value();
    }
};
} // namespace std

template<class ValueType>
class FooContainer
{
public:
    ValueType operator[](size_t idx) const { return _container[idx]; }
    size_t size() const { return _container.size(); }
    void push_back(const ValueType& value) {
        _container.push_back(value);
    }

private:
    std::vector<ValueType> _container;
};

template< class ValueType,
          class ContainerType,
          const ValueType& max_value,
          class Compare = std::less<ValueType> >
class LoserTree
{
public:
    ~LoserTree() {
        delete[] _indexes;
        delete[] _losers;
        delete[] _data;
    }

    LoserTree(const ContainerType* ways, size_t num) :
        _num(num), _ways(ways), _indexes(new size_t[_num]),
        _data(new ValueType[_num]), _losers(new int[_num])
    {
        std::fill(_indexes, _indexes + _num, 0);
        std::fill(_losers, _losers + _num, -1);
        for (int way_idx = _num-1; way_idx >= 0; --way_idx) {
            if (_indexes[way_idx] == _ways[way_idx].size()) {
                _data[way_idx] = max_value;
            } else {
                _data[way_idx] = _ways[way_idx][_indexes[way_idx]];
            }
            adjust(way_idx);
        }
    }

    bool extract_one(ValueType& v) {
        int way_idx = _losers[0];
        if (_data[way_idx] == max_value)
            return false;
        v = _data[way_idx];
        if (++_indexes[way_idx] == _ways[way_idx].size()) {
            _data[way_idx] = max_value;
        } else {
            _data[way_idx] = _ways[way_idx][_indexes[way_idx]];
        }
        adjust(way_idx);
        return true;
    }

private:
    size_t _num;
    const ContainerType* _ways;
    size_t* _indexes;
    ValueType* _data;
    int* _losers;

    void adjust(int way_idx) {
        using std::swap;
        int loser_idx = (way_idx + _num) / 2;
        while (loser_idx != 0 && way_idx != -1) {
            if (_losers[loser_idx] == -1 || !Compare()(_data[way_idx],  _data[_losers[loser_idx]]))
                swap(way_idx,_losers[loser_idx]);
            loser_idx /= 2;
        }
        _losers[0] = way_idx;
    }
};

/*
 * input format:
 * 1 10 100 1000
 * 2 20 200 2000
 * 3 30 300
 * 4 40 400 4000 40000
 */
std::vector<std::vector<int> > get_input()
{
    std::vector<std::vector<int> > data;
    std::string line;
    while (std::getline(std::cin, line)) {
        std::vector<int> tmp_data;
        std::istringstream iss(line);
        std::copy(std::istream_iterator<int>(iss), std::istream_iterator<int>(), std::back_inserter(tmp_data));
        data.push_back(tmp_data);
    }

    for (size_t i = 0; i < data.size(); ++i) {
        std::copy(data[i].begin(), data[i].end(), std::ostream_iterator<int>(std::cout, ", "));
        std::cout << std::endl;
    }

    return data;
}

template<class ValueType, class ContainerType>
std::vector<ContainerType> generate_data()
{
    const int way_num = 20;
    std::vector<ContainerType> data(way_num);
    for (int num = 0; num < 10/*100000*/; ++num) {
        data[rand() % way_num].push_back(ValueType(num));
    }

    return data;
}

void test_foo()
{
    std::vector<FooContainer<Foo> > data = generate_data<Foo, FooContainer<Foo> >();
    /*
    for (size_t i = 0; i < data.size(); ++i) {
        for (size_t j = 0; j < data[i].size(); ++j) {
            //std::cout << data[i][j] << ", ";
            Foo foo = data[i][j];
            std::cout << foo << ", ";
        }
        std::cout << std::endl;
    }
    */

    LoserTree<Foo, FooContainer<Foo>, FOO_MAX> lt(data.data(), data.size());
    Foo v;
    Foo correct_res(0);
    while(lt.extract_one(v)) {
        //assert(v == correct_res);
        //correct_res = Foo(correct_res.value()+1);
        std::cout << v.value() << ", ";
    }
    std::cout << std::endl;
}

extern const int int_max = INT_MAX;
void test()
{
    std::vector<std::vector<int> > data = generate_data<int ,std::vector<int> >();
    LoserTree<int, std::vector<int>, int_max> lt(data.data(), data.size());
    int v;
    int correct_res(0);
    while (lt.extract_one(v)) {
        assert(v == correct_res++);
        std::cout << v << ", ";
    }
    std::cout << std::endl;
}

int main()
{
    test_foo();
    test();

    return 0;
}
时间: 2024-07-30 13:49:26

C++ 模板实现败者树,进行多路归并的相关文章

排序(二)键索引、桶排序、位示图、败者树等

排序(二) 以上排序算法都有一个性质:在排序的最终结果中,各元素的次序依赖于它们之间的比较.我们把这类排序算法称为比较排序. 任何比较排序的时间复杂度的下界是nlgn. 以下排序算法是用运算而不是比较来确定排序顺序的.因此下界nlgn对它们是不适用的. 键索引计数法(计数排序) 计数排序假设n个输入元素中的每一个都是在0到k区间的一个整数,其中k为某个整数. 思想:对每一个输入元素x,确定小于x的元素个数.利用这一信息,就可以直接把x放到它在输出数组中的位置了. 例如: 学生被分为若干组,标号为

外排序 &amp; 败者树 &amp; 多路归并-学习

来来来,根据这篇文章,学一下败者树吧: http://blog.csdn.net/whz_zb/article/details/7425152 一.胜者树 胜者树的一个优点是,如果一个选手的值改变了,可以很容易地修改这棵胜者树.只需要沿着从该结点到根结点的路径修改这棵二叉树,而不必改变其他比赛的结果. 二.败者树 败者树是胜者树的一种变体.在败者树中,用父结点记录其左右子结点进行比赛的败者,而让胜者参加下一轮的比赛.败者树的根结点记录的是败者,需要加一个结点来记录整个比赛的胜利者.采用败者树可以

外排序 &nbsp; 败者树 &nbsp; 多路归并

一.外排序 排序按数据存在的位置不同分为内排序和外排序 内排序:数据都在内存中,选择合适的排序方法对数据进行排序,比如选择排序.快速排序等 衡量内排序的效率是数据的比较次数 外排序:数据无法全部加载到内存中,只能不断在外部存储器和内存中进行交换完成排序 衡量外排序的效率是内存与外村的交换次数 外排序是针对大文件的数据排序,内存中无法加载这个大文件,把这个文件分为k个小文件,分别排序后合并 http://blog.csdn.net/msdnwolaile/article/details/52084

算法-排序(1)k路平衡归并与败者树

const int MaxValue=999; //根据实际情况选择最大值 void kwaymerge(Element *r,int k){ int i,q; r=new Element[k]; //在败者树中的k个记录 int *key=new int[k+1]; //k个排序码和建树单元key[k] int *loser=new int[k]; //k-1个败者和冠军loser[0] for(i=0; i<k; i++){ //从k个归并段输入第一个记录及其排序码 InputRecord(

多路归并排序之败者树

#include<iostream> #include<iomanip> #define M 4 using namespace std; class LoserTree { private: // 调整K为2的整数次幂 int round(int k) { if(k&(k-1)!=0) { int i=0; for(i=31;i>=0;i--) { if(((1<<i)&k)!=0) { cout<<i<<endl; br

堆排序、胜者树、败者树,孰优孰劣?

在顺序存储结构中,堆排序是一种非常不错的高级选择排序算法,普通情况和最差情况下都可以将时间复杂度控制在O(n * logn). 堆排序可以用在顺序存储结构,是因为完全二叉树的一种独特性质.而这里还要先提一下满二叉树. 啥叫满二叉树?满二叉树是这样一种二叉树,它的每一层都是"满"的,设根部为第0层,则每一层都有2^n个节点.所有节点的度数要么是2,要么是0(叶子). 那完全二叉树呢?我们首先做出如下规定,即对二叉树中的节点,按从根部到叶子.每层从左到右递增的编号:如果某棵树,其所有节点的

外部排序---置换选择+败者树

当需要对一个大文件进行排序时,计算机内存可能不够一次性装入所有数据,解决办法是归并.归并的大概做法是将大文件分为若干段,依次读入内存进行排序,排序后再重新写入硬盘.这些排好序的片段成为顺串.然后对这些顺串进行逐躺归并,使归并段逐渐由小变大,最终使整个文件有序.要使用归并就得考虑两个问题,一个是如何生成顺串,一个是如何对顺串进行归并. 置换选择算法 先考虑如何生成顺串.我们知道,减少顺串的数量可以降低归并的次数,而在文件大小固定的情况下,要减少顺串的数量,就要增大单个顺串的长度.如果使用内部排序生

(转)败者树 和 胜者树---数组实现

转自:http://blog.csdn.net/sqx2011/article/details/8241734 胜者树和败者树都是完全二叉树,是树形选择排序的一种变型.每个叶子结点相当于一个选手,每个中间结点相当于一场比赛,每一层相当于一轮比赛. 不同的是,胜者树的中间结点记录的是胜者的标号:而败者树的中间结点记录的败者的标号. 胜者树与败者树可以在log(n)的时间内找到最值.任何一个叶子结点的值改变后,利用中间结点的信息,还是能够快速地找到最值.在k路归并排序中经常用到. 一.胜者树 胜者树

看数据结构写代码(66) 败者树

计算机的 内存 是 有限的,无法 存入 庞大的数据.当 遇到 大数据需要排序时,我们 需要 将 这些 数据 分段 从 硬盘里 读到 内存中,排好序,再 写入到 硬盘中,这些段 叫做 归并段.最后将 这些 分段 合并 成 一个 最终  完整 有序的 数据. 这里 操作的 时间 =  内部 排序 时间 +  外存读写时间 + 内部归并所需时间. 其中 外存 读写时间 最耗时,外存读写时间 = 读写次数 * 读写数据的时间 ,读写 数据的时间 因 设备 性能 而 影响,我们 无法控制,我们 只能 控制