具有恒定插入时间的有序字符串容器?

时间:2013-03-30 06:10:43

标签: data-structures stl

我需要的是一个有序的,带有字符串键的关联容器,由数字向量赋值。此外,我需要O(1)插入时间。

我的描述听起来很抽象,我会给你一个场景:

有在线测试。当一个人参加此测试时,他的名字会被添加到数据库中。如果他们喜欢,人们可以反复参加考试。他们的所有分数都将以他们的名字记录(这是唯一的)。例如,大卫,汤姆,爱丽丝来参加考试几次。该程序应该能够以下列格式打印输出:

Alice 65 70 84
David 98 97 93
Tom   100 45 
...

正如您所看到的,他们的名字应按字典顺序打印出来。每当有人参加测试时,他的分数就会被添加到数据库中。由于很多人会参加测试,因此必须O(1)时间复杂度。但是,打印数据库也经常发生;每秒说一次。因此,不必在每个显示器上明确排序是有利的。

我可以在这里使用哪种数据结构? (STL是首选。)我目前正在使用unordered_map,因为它给我O(1)插入,但它不能按字典顺序迭代键。

3 个答案:

答案 0 :(得分:4)

可以在O(1)中插入并在O(n)中进行有序迭代的容器可用于在线性时间内对字符串进行排序。

我们可以立即得出结论,这个容器不能单独使用比较器,因为基于比较器的排序的下限为O(n log n)

只有少数排序算法按线性时间排序,它们通常需要知道您的密钥空间才能工作。这种算法的增量“在线”版本可能对您有用,并且它使用的增量构建的内部数据将成为您的容器。

Here是对线性时间排序算法的讨论。

答案 1 :(得分:1)

考虑到在这种情况下真正的 O(1)操作是一个非常复杂的问题,并且您更喜欢使用STL,我建议使用类似以下数据结构的东西。并不是说这不满足你的Big-Oh要求,即使使用STL也不是最有效的方法,但它简单而有效。

您的主要数据结构是std::map。但是为了加速对O(1)的查找,你可以像这样使用std::unordered_map

using std::map, std::string, std::vector, std::unordered_map;

typedef map<string, vector<int> > TestTakersMap;
typedef unordered_map<string, TestTakersMap::iterator> LookupAccelerator;

现在您的各种操作将是:

  • 添加新人:您插入地图,但也要在地图中添加名称和迭代器,将新记录插入到unordered_map中。的 O(日志(N))即可。
  • 查找某个人:使用unordered_map,您可以获取该人的迭代器和数据。 预计 O(1)
  • 添加新分数:您找到使用unordered_map的人,然后使用您添加的迭代器添加新分数。 摊销 O(1)
  • 打印出所有名称和分数:您对地图本身进行迭代,并按字典顺序获取名称,不添加任何排序步骤。可以被视为 O(S),其中 S 是所有参与者的总分数。

请注意,在所有这些中,你的瓶颈将是你的缓存,并且所有这些指针在内存中追逐和跳跃将帮助你。当然,这取决于其他几个因素,例如,您实际获得了多少名称,每个人有多少分数,添加新人的频率,添加新分数的频率,以及打印所有分数的频率姓名和分数,每人和每次测试所需的数据和需求量等等。

更新:您可以执行以下基本操作。包括等等如下所示:

#include <map>
#include <string>
#include <unordered_map>
#include <vector>

using std::map;
using std::string;
using std::unordered_map;
using std::vector;

这是一个非常简单的类,可以执行您想要的某些操作。请注意,我正在使用C ++ 11功能(autoemplace,...)但不要将此代码视为特别好的编程风格;我不能担保。

class TestScores
{
private:
    typedef int ScoreType;
    typedef vector<ScoreType> ScoreList;
    typedef map<string, ScoreList> TestTakersMap;
    typedef unordered_map<string, TestTakersMap::iterator> LookupAccelerator;

public:
    bool hasName (string const & new_name) const
    {
        return m_lookup.end() != m_lookup.find (new_name);
    }

    // Returns true if the name is really new
    bool addName (string const & new_name)
    {
        if (hasName(new_name))
            return false; // name already in there

        auto i = m_takers.emplace (new_name, vector<int>()).first;
        m_lookup.emplace (new_name, i);

        return true;
    }

    ScoreList const & getScores (string const & name) const
    {
        // This redirects to the private, non-const version
        return const_cast<TestScores *>(this)->getScores(name);
    }

    void addScore (string const & name, ScoreType new_score)
    {
        getScores(name).push_back (new_score);
    }

private:
    // If the name doesn't already exist, it is added!
    ScoreList & getScores (string const & name)
    {
        if (!hasName(name))
            addName (name);

        return m_lookup[name]->second;
    }

private:
    TestTakersMap m_takers;
    LookupAccelerator m_lookup;
};

答案 2 :(得分:0)

如果您真的认真对待数据集的大小非常大,并且您绝对需要有效的插入,查找和词典迭代,那么您可以查看Judy Arrays。 Judy数组具有快速,内存效率和类似 trie 的关联数据结构。

您可以查看以下两种实现: