【问题标题】:Gin Rummy - Algorithm for determining optimal meldingGin Rummy - 确定最佳融合的算法
【发布时间】:2020-07-03 01:05:28
【问题描述】:

此处已提出与此类似的问题,但在我的问题中,融合不限于大小 3 的融合,融合可以是任何大小。

在 Gin Rummy 中,对于任何特定的卡片组,卡片都可以分为一组或一组。一组是一组 3 张或更多张相同等级的牌(2-D、2-C、2-H 或 7-D、7-C、7-H、7-S)。运行是一组 3 张或更多张具有连续等级和相同花色的牌(A-D、2-D、3-C 或 7-C、8-C、9-C、10-C、J-C)。不属于某个组的牌称为“枯木”。

我的算法的目标是找到一组特定卡片的最佳融合,即最小化所有枯木值的总和(数字卡片的值是它们的关联数字, ace 是 1,面卡的值是 10。)。

我最初对算法的尝试是基于这样的假设:对于任何冲突的运行和集合组,要么运行存在,要么存在集合组。在这种假设下,算法可以只计算运行值的总和和所有集合的值的总和,并保留较大的那个。例如,如果我们有组

[2-D、3-D、4-D]、[2-D、2-C、2-H] 和 [4-D、4-H、4-S]。运行值的总和将是 2 + 3 + 4 = 9,所有集合值的总和将是 2 + 2 + 2 + 4 + 4 + 4 = 18。在这种情况下,这意味着两个将保留集合以实现最佳融合,并且不会使用运行(3-D 将是枯木)。

此假设适用于大小为 3 的组,但不适用于更大的组。例如,考虑以下两组:

[4-D、5-D、6-D、7-D]、[7-D、7-H、7-S]

为此的最佳分组最终是 [4-D, 5-D, 6-D] 和 [7-D, 7-H, 7-S]。冲突的集合和运行的一部分被保留。我不确定如何创建算法,这不仅仅是蛮力。

任何帮助或想法将不胜感激。

编辑

我意识到我的原始算法甚至不适用于大小 3 的融合。在以下组的情况下:

[4-D, 5-D, 6-D], [4-C, 5-C, 6-C], [6-D, 6-C, 6-S]

该算法将分别查看两次运行,并得出结论认为应该删除它们以支持该集合,但最佳解决方案是保留两次运行并删除该集合。

仍在寻求帮助以创建适用于所有边缘情况的算法。

【问题讨论】:

  • 这个问题很像“set cover”问题,是NP难的。因此,找到所有最大融合(例如通过回溯搜索),然后通过死木分数选择最佳融合可能会尽可能好。

标签: algorithm


【解决方案1】:

我之前的答案已被删除,因为我没有真正提供解释,只是提供了一个脚本链接,其中包含针对此问题的算法。我意识到为什么这不适合现在堆栈溢出的答案。这是我对完整答案的尝试。

我在此处展示的算法基于此处找到的方法:https://gist.github.com/yalue/2622575。解决方案是上面 Paul Hankin 建议的回溯搜索。

算法创建了一个名为MeldNode的对象:

class MeldNode {
    Cards cards;
    MeldNode* parent;
    double value;

    MeldNode(Cards cards, MeldNode* parent) : cards(cards), parent(parent) {
        value = sum of values of cards;
        if (parent is not null){
            value += parent->value;
        }
    }
}

这里的值等于提供的卡的卡值与父卡的值之和。

创建了一个函数cleanMeldGroup,给定一个融合数组和一个融合,它将返回一个融合数组,其中只有与给定融合不冲突的融合。

Melds cleanMeldGroup(Melds melds, Cards meldAvoid) {
    Melds cleanMelds;
    for (Cards meld : melds) {
        bool clean = true;
        for (Card cardA : meld) {
            for (Card cardB : meldAvoid) {
                if (cardA == cardB) {
                    clean = false;
                }
            }
        }
        if (clean) {
            cleanMelds.push(meld);
        }
    }
    return cleanMelds;
}

接下来,创建一个函数getBestNode,它使用回溯来找到一个包含最佳融合组合数据的融合节点。

MeldNode* getBestNode(Melds melds, MeldNode* rootNode) {
    MeldNode* best = rootNode;
    for (Meld meld : melds) {
        MeldNode* node = new MeldNode(meld, rootNode);
        MeldNode* newTree = getBestNode(cleanMeldGroup(melds, meld), node);
        if (best is null || newTree->value > best->value){
            best = newTree;
        }
    }
}

请注意,由于这是现在编写的,这将导致 c++ 中的内存泄漏。如有必要,在数据已使用时采取措施释放内存(您可以在此函数中释放不属于最佳树的节点的内存,然后释放属于最佳树的节点的内存)你使用它们)。

最后,可以使用getOptimalMelding 函数确定最佳融合如下。

Melds getOptimalMelding(Cards hand) {
    Sort hand by rank and then by suit;

    Melds possibleMelds;

    // Find all possible runs
    int runLength = 1;
    for (int i = 0; i < hand.size(); i++) {
        if (hand[i].suit == hand[i - 1].suit && hand[i].rank == hand[i - 1].rank + 1) {
            runLength++;
        } else {
            if (runLength >= 3) {
                for (int size = 3; size <= runLength; size++) {
                    for (int start = 0; start <= runLength - size; start++) {
                        Cards run;
                        for (int j = i - runLength + start; j < i - runLength + start + s; j++) {
                            run.push(hand[j]);
                        }
                        possibleMelds.push(run);
                    }
                }
            }
            runLength = 1;
        }
    }
    if (runLength >= 3) {
        for (int size = 3; size <= runLength; size++) {
            for (int start = 0; start <= runLength - size; start++) {
                Cards run;
                for (int j = i - runLength + start; j < i - runLength + start + s; j++) {
                    run.push(hand[j]);
                }
                possibleMelds.push(run);
            }
        }
    }

    // Find all possible sets
    for (int i = 1; i <= 13; i++) {
        Cards set;
        for (Card card : hand) {
            if (card.rank == i) {
                set.push(card);
            }
        }
        if (set.size() >= 3) {
            possibleMelds.push(set);
        }
        if (set.size() == 4) {
            for (Card card : set) {
                Cards subset;
                for (Card add : set) {
                    if (add != card) {
                        subset.push(add);
                    }
                }
                possibleMelds.push(subset);
            }
        }
    }

    // Find Optimal Melding Combination
    MeldNode* bestNode = getBestNode(possibleMelds, null);
    Melds optimalMelds;
    while (bestNode is not null){
        optimalMelds.push(bestNode.cards);
        bestNode = bestNode->parent;
    }

    return optimalMelds;

请注意,possibleMelds 包含所有大小的所有可能融合。例如,对于手 [2-D、3-D、4-D、5-D、5-H、5-S、5-C、10-S、9-C、8-H],@987654331 @ 将包含以下组:

[2-D, 3-D, 4-D], 
[3-D, 4-D, 5-D], 
[2-D, 3-D, 4-D, 5-D], 
[5-D, 5-H, 5-S, 5-C],
[5-H, 5-S, 5-C],
[5-D, 5-S, 5-C],
[5-D, 5-H, 5-C],
[5-D, 5-H, 5-S]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-19
    • 1970-01-01
    • 1970-01-01
    • 2014-07-28
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多