草庐IT

c++ - 计算一组关系的整数映射的更有效算法

coder 2024-02-24 原文

原题和简单算法

给定一组关系,例如

a < c
b < c
b < d < e

找到一组从 0 开始的整数(以及尽可能多的重复整数!)与关系集匹配的最有效算法是什么,即在这种情况下

a = 0; b = 0; c = 1; d = 1; e = 2

简单的算法是重复迭代关系集并根据需要增加值,直到达到收敛,如下面的 Python 实现:

relations = [('a', 'c'), ('b', 'c'), ('b', 'd', 'e')]

print(relations)
values = dict.fromkeys(set(sum(relations, ())), 0)

print(values)
converged = False
while not converged:
    converged = True
    for relation in relations:
        for i in range(1,len(relation)):
            if values[relation[i]] <= values[relation[i-1]]:
                converged = False
                values[relation[i]] += values[relation[i-1]]-values[relation[i]]+1
    print(values)

除了 O(Relations²) 的复杂性(如果我没记错的话),如果给出无效关系(例如添加 e <>

基于 Tim Peter 评论的 Python 实现

relations = [('a', 'c'), ('b', 'c'), ('b', 'd'), ('b', 'e'), ('d', 'e')]

symbols = set(sum(relations, ()))
numIncoming = dict.fromkeys(symbols, 0)
values = {}

for rel in relations:
    numIncoming[rel[1]] += 1

k = 0
n = len(symbols)
c = 0
while k < n:
    curs = [sym for sym in symbols if numIncoming[sym] == 0]
    curr = [rel for rel in relations if rel[0] in curs]
    for sym in curs:
        symbols.remove(sym)
        values[sym] = c
    for rel in curr:
        relations.remove(rel)
        numIncoming[rel[1]] -= 1
    c += 1
    k += len(curs)
print(values)

目前它要求关系被“拆分”(b < d="" 和="" d="">< e="" 而不是="" b="">< d="">< e),但是循环检测很容易(只要="">curs 为空和 k < n),并且应该可以更有效地实现它(尤其是如何确定="">curscurr)

最坏情况时序(1000 个元素,999 个关系,倒序):

Version A: 0.944926519991
Version B: 0.115537379751

最佳情况时间安排(1000 个元素、999 个关系、前向顺序):

Version A: 0.00497004507556
Version B: 0.102511841589

平均用例时间(1000 个元素,999 个关系,随机顺序):

Version A: 0.487685376214
Version B: 0.109792166323

测试数据可以通过

生成
n = 1000
relations_worst = list((a, b) for a, b in zip(range(n)[::-1][1:], range(n)[::-1]))
relations_best = list(relations_worst[::-1])
relations_avg = shuffle(relations_worst)

基于 Tim Peter 的回答的 C++ 实现(针对符号 [0, n) 进行了简化)

vector<unsigned> chunked_topsort(const vector<vector<unsigned>>& relations, unsigned n)
{
    vector<unsigned> ret(n);
    vector<set<unsigned>> succs(n);
    vector<unsigned> npreds(n);

    set<unsigned> allelts;
    set<unsigned> nopreds;

    for(auto i = n; i--;)
        allelts.insert(i);

    for(const auto& r : relations)
    {
        auto u = r[0];
        if(npreds[u] == 0) nopreds.insert(u);
        for(size_t i = 1; i < r.size(); ++i)
        {
            auto v = r[i];
            if(npreds[v] == 0) nopreds.insert(v);
            if(succs[u].count(v) == 0)
            {
                succs[u].insert(v);
                npreds[v] += 1;
                nopreds.erase(v);
            }
            u = v;
        }
    }

    set<unsigned> next;
    unsigned chunk = 0;
    while(!nopreds.empty())
    {
        next.clear();
        for(const auto& u : nopreds)
        {
            ret[u] = chunk;
            allelts.erase(u);
            for(const auto& v : succs[u])
            {
                npreds[v] -= 1;
                if(npreds[v] == 0)
                    next.insert(v);
            }
        }
        swap(nopreds, next);
        ++chunk;
    }

    assert(allelts.empty());

    return ret;
}

具有改进缓存局部性的 C++ 实现

vector<unsigned> chunked_topsort2(const vector<vector<unsigned>>& relations, unsigned n)
{
    vector<unsigned> ret(n);
    vector<unsigned> npreds(n);

    vector<tuple<unsigned, unsigned>> flat_relations; flat_relations.reserve(relations.size());
    vector<unsigned> relation_offsets(n+1);

    for(const auto& r : relations)
    {
        if(r.size() < 2) continue;
        for(size_t i = 0; i < r.size()-1; ++i)
        {
            assert(r[i] < n && r[i+1] < n);
            flat_relations.emplace_back(r[i], r[i+1]);
            relation_offsets[r[i]+1] += 1;
            npreds[r[i+1]] += 1;
        }
    }
    partial_sum(relation_offsets.begin(), relation_offsets.end(), relation_offsets.begin());
    sort(flat_relations.begin(), flat_relations.end());

    vector<unsigned> nopreds;
    for(unsigned i = 0; i < n; ++i)
        if(npreds[i] == 0)
            nopreds.push_back(i);

    vector<unsigned> next;
    unsigned chunk = 0;
    while(!nopreds.empty())
    {
        next.clear();
        for(const auto& u : nopreds)
        {
            ret[u] = chunk;
            for(unsigned i = relation_offsets[u]; i < relation_offsets[u+1]; ++i)
            {
                auto v = std::get<1>(flat_relations[i]);
                npreds[v] -= 1;
                if(npreds[v] == 0)
                    next.push_back(v);
            }
        }
        swap(nopreds, next);
        ++chunk;
    }

    assert(all_of(npreds.begin(), npreds.end(), [](unsigned i) { return i == 0; }));

    return ret;
}

C++ 时序 10000 个元素,9999 个关系,平均超过 1000 次运行

“最坏情况”:

chunked_topsort: 4.21345 ms
chunked_topsort2: 1.75062 ms

“最佳情况”:

chunked_topsort: 4.27287 ms
chunked_topsort2: 0.541771 ms

“平均情况”:

chunked_topsort: 6.44712 ms
chunked_topsort2: 0.955116 ms

与 Python 版本不同,C++ chunked_topsort 很大程度上取决于元素的顺序。有趣的是,随机顺序/平均情况是迄今为止最慢的(使用基于集合的 chunked_topsort)。

最佳答案

这是我之前没有时间发布的实现:

def chunked_topsort(relations):
    # `relations` is an iterable producing relations.
    # A relation is a sequence, interpreted to mean
    # relation[0] < relation[1] < relation[2] < ...
    # The result is a list such that
    # result[i] is the set of elements assigned to i.
    from collections import defaultdict
    succs = defaultdict(set)    # new empty set is default
    npreds = defaultdict(int)   # 0 is default
    allelts = set()
    nopreds = set()

    def add_elt(u):
        allelts.add(u)
        if npreds[u] == 0:
            nopreds.add(u)

    for r in relations:
        u = r[0]
        add_elt(u)
        for i in range(1, len(r)):
            v = r[i]
            add_elt(v)
            if v not in succs[u]:
                succs[u].add(v)
                npreds[v] += 1
                nopreds.discard(v)
            u = v
    result = []
    while nopreds:
        result.append(nopreds)
        allelts -= nopreds
        next_nopreds = set()
        for u in nopreds:
            for v in succs[u]:
                npreds[v] -= 1
                assert npreds[v] >= 0
                if npreds[v] == 0:
                    next_nopreds.add(v)
        nopreds = next_nopreds
    if allelts:
        raise ValueError("elements in cycles %s" % allelts)
    return result

然后,例如,

>>> print chunked_topsort(['ac', 'bc', 'bde', 'be', 'fbcg'])
[set(['a', 'f']), set(['b']), set(['c', 'd']), set(['e', 'g'])]

希望对您有所帮助。请注意,这里没有任何类型的搜索(例如,没有条件列表理解)。这使得它在理论上 ;-) 高效。

之后:计时

在帖子结尾处生成的测试数据中,chunked_topsort() 对输入的顺序非常不敏感。这并不奇怪,因为该算法仅对输入进行一次迭代以构建其(本质上无序的)字典和集合。总之,它比 版本 B 快大约 15 到 20 倍。 3 次运行的典型计时输出:

worst chunked  0.007 B  0.129 B/chunked  19.79
best  chunked  0.007 B  0.110 B/chunked  16.85
avg   chunked  0.006 B  0.118 B/chunked  19.06

worst chunked  0.007 B  0.127 B/chunked  18.25
best  chunked  0.006 B  0.103 B/chunked  17.16
avg   chunked  0.006 B  0.119 B/chunked  18.86

worst chunked  0.007 B  0.132 B/chunked  20.20
best  chunked  0.007 B  0.105 B/chunked  16.04
avg   chunked  0.007 B  0.113 B/chunked  17.32

使用更简单的数据结构

考虑到问题已经改变 ;-),这里重写了假设输入是 range(n) 中的整数,并且 n 也被传递了。在初始传递输入关系后,没有集合、没有指令,也没有动态分配。在 Python 中,这比测试数据上的 chunked_topsort() 快大约 40%。但我太老了,不能再与 C++ 搏斗了 ;-)

def ct_special(relations, n):
    # `relations` is an iterable producing relations.
    # A relation is a sequence, interpreted to mean
    # relation[0] < relation[1] < relation[2] < ...
    # All elements are in range(n).
    # The result is a vector of length n such that
    # result[i] is the ordinal assigned to i, or
    # result[i] is -1 if i didn't appear in the relations.
    succs = [[] for i in xrange(n)]
    npreds = [-1] * n
    nopreds = [-1] * n
    numnopreds = 0

    def add_elt(u):
        if not 0 <= u < n:
            raise ValueError("element %s out of range" % u)
        if npreds[u] < 0:
            npreds[u] = 0

    for r in relations:
        u = r[0]
        add_elt(u)
        for i in range(1, len(r)):
            v = r[i]
            add_elt(v)
            succs[u].append(v)
            npreds[v] += 1
            u = v

    result = [-1] * n
    for u in xrange(n):
        if npreds[u] == 0:
            nopreds[numnopreds] = u
            numnopreds += 1

    ordinal = nopreds_start = 0
    while nopreds_start < numnopreds:
        next_nopreds_start = numnopreds
        for i in xrange(nopreds_start, numnopreds):
            u = nopreds[i]
            result[u] = ordinal
            for v in succs[u]:
                npreds[v] -= 1
                assert npreds[v] >= 0
                if npreds[v] == 0:
                    nopreds[numnopreds] = v
                    numnopreds += 1
        nopreds_start = next_nopreds_start
        ordinal += 1
    if any(count > 0 for count in npreds):
        raise ValueError("elements in cycles")
    return result

这又是 - 在 Python 中 - 对输入顺序不敏感。

关于c++ - 计算一组关系的整数映射的更有效算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19327977/

有关c++ - 计算一组关系的整数映射的更有效算法的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  3. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  4. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  7. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  8. [工业相机] 分辨率、精度和公差之间的关系 - 2

    📢博客主页:https://blog.csdn.net/weixin_43197380📢欢迎点赞👍收藏⭐留言📝如有错误敬请指正!📢本文由Loewen丶原创,首发于CSDN,转载注明出处🙉📢现在的付出,都会是一种沉淀,只为让你成为更好的人✨文章预览:一.分辨率(Resolution)1、工业相机的分辨率是如何定义的?2、工业相机的分辨率是如何选择的?二.精度(Accuracy)1、像素精度(PixelAccuracy)2、定位精度和重复定位精度(RepeatPrecision)三.公差(Tolerance)四.课后作业(Post-ClassExercises)视觉行业的初学者,甚至是做了1~2年

  9. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  10. ruby - 在 Ruby 中将整数格式化为固定长度的字符串 - 2

    有没有一种简单的方法可以将给定的整数格式化为具有固定长度和前导零的字符串?#convertnumberstostringsoffixedlength3[1,12,123,1234].map{|e|???}=>["001","012","123","234"]我找到了解决方案,但也许还有更聪明的方法。format('%03d',e)[-3..-1] 最佳答案 如何使用%1000而不是进行字符串操作来获取最后三位数字?[1,12,123,1234].map{|e|format('%03d',e%1000)}更新:根据theTinMan的

随机推荐