推广"删除所有重复项"

时间:2014-12-01 16:59:34

标签: c++ containers elements

related成为二元谓词。让我们定义一个"关系子集"作为所有元素的集合,使得子集中的任何元素与子集中的至少一个其他元素相关,而不是与其自身相关(因此元素是否与其自身相关是无关紧要的)在形成关系子集)。注意:关系子集不一定是strongly connected component。例如,假设A与B相关,并且B和C彼此相关。然后{A,B,C}是定义的关系子集,但不是强连接组件,因为没有从B到A或从C到A的路径通过相关的'。

请注意'相关'不一定是对称的,即相关的(a,b)== true并不一定意味着相关的(b,a)==是真的,也不是传递的,即相关的(a,b)==真实和相关的(b, c)== true并不一定意味着相关(a,c)== true。据我所知,为了将一个集合划分为关系子集,不需要对二元谓词related施加任何限制。如果元素x根本不与任何元素相关(除了它自身),那么{x}本身就是它自己的关系子集。

一个好问题是定义

template <typename Container, typename BinaryPredicate>
void partition (Container& container, BinaryPredicate related);

将对容器的元素进行排序,以便将容器分区为其关系子集。在尝试原始问题后,这个问题很快出现了:

template <typename Container, typename BinaryPredicate>
void removeRelated (Container& container, BinaryPredicate related);

除了容器中找到的每个子集的第一个元素之外,从每个关系子集中删除(来自container)所有元素。

当然,平等只是“相关”的一个特例,因此这是&#34;删除所有副本的概括&#34; (这就是我如何通过试图概括这个众所周知的解决问题来思考这个问题)。我们想要的是保留每个关系子集的一个代表,即第一个根据容器的元素排序。

以下是我尝试实施的代码,具有以下关系:

Bob knows Mary
Mary knows Jim
Jim knows Bob
Sally knows Fred
Fred knows no one.

在这个例子中,A与B有关,当且仅当A知道B. {Bob,Mary,Jim}是一个关系子集,因为每个人都与其他人有关(Bob与Mary有关,Mary与吉姆,吉姆和鲍勃有关。请注意,{Sally,Fred}不是关系子集,因为虽然Sally与Fred有关,但Fred与Sally无关。因此,我们留下{Sally},{Fred}作为另外两个关系子集。

所以最后的答案应该是:Bob,Sally,Fred。注意,如果要定义函数partition,那么它将简单地保持{Bob,Mary,Jim,Sally,Fred}不变,​​Bob,Sally,Fred是每个分区的第一个元素。因此,即使我们有partition函数(当然不要与std :: partition混淆,但我现在还没有真正想到一个好名字),它仍然不能立即清楚{ {1}}需要。

removeRelated

上面代码中的错误是Mary被插入到&#39; s&#39;因为在那一点上她与s中的任何人都没有关系(通过&#39; know&#39;)。最后,她与Jim有关,他与Bob(以及Bob对Mary)的关系通过“知道”,因此{Bob,Mary,Jim}是一个关系子集,所以bob应该是“这三个人中只有一个人”。

但是在迭代期间,函数不知道这一点。我该如何修复算法? 一个想法是首先定义上面提到的函数#include <iostream> #include <algorithm> #include <list> #include <set> template<typename Container, typename BinaryPredicate> void removeRelated (Container& container, BinaryPredicate related) { using element = typename Container::value_type; std::set<element> s; for (typename Container::iterator it = std::begin(container); it != std::end(container); ) { if (std::find_if(s.begin(), s.end(), [=](const element& x)->bool {return related(*it,x);}) != s.end()) it = container.erase(it); // *it is related to an element in s. else { s.insert(*it); it++; } } } struct Person { // Used for testing removeRelated. std::string name; std::list<Person*> peopleKnown; Person (const std::string& n) : name(n) {} void learnsAbout (Person* p) {peopleKnown.push_back(p);} bool knows (Person* p) const { return std::find(peopleKnown.begin(), peopleKnown.end(), p) != peopleKnown.end(); } }; int main() { Person *bob = new Person("Bob"), *mary = new Person("Mary"), *jim = new Person("Jim"), *sally = new Person("Sally"), *fred = new Person("Fred"); bob->learnsAbout(mary); mary->learnsAbout(jim); jim->learnsAbout(bob); sally->learnsAbout(fred); std::list<Person*> everybody {bob, mary, jim, sally, fred}; removeRelated (everybody, [](Person* a, Person* b)->bool {return a->knows(b);}); for (const Person* x : everybody) std::cout << x->name << ' '; // Bob Mary Sally Fred // Should be 'Bob Sally Fred' since {Bob, Mary, Jim}, {Sally}, {Fred} are the relation subsets. } ,即对容器进行排序,以便将容器划分为其关系子集(这本身就是一个非常好的问题,很可能是主要问题在手边),然后简单地采取每个分区的第一个元素。

另一个想法是替换lambda函数

partition

[=](const element& x)->bool {return related(*it,x);}

并且我认为它可以解决问题,其中辅助函数relatedIndirectly搜索与x的关系链。

以下是Cimbali(下文)研究的另一个例子。假设A与B相关,A与C相关,B与C相关。那么{A,B}不能是关系子集,因为B与A无关。 类似地,{A,C}和{B,C}不能是关系子集(C与A无关,C与B无关),{A,B,C}绝对不是因为C与之无关任何人。 {A},{B},{C}是唯一满足我对关系子集定义的分区。

猜想:关系子集始终是强连接组件的并集,这样联合中的每个强连接组件都至少有一个元素与联合中不同的强连接组件中的某个元素相关。但这需要数学证明。

更新 我已经加强了相关子集的上述定义(大大),以便: BinaryPredicate&#39;相关&#39;应该是自反的(任何x的相关(x,x)== true),对称(相关(x,y)暗示相关(y,x))和传递(相关(x,y)和相关(y,z) )暗示相关(x,z))。这会将任何集合划分为等价类。 removeRelated将从每个等价类中删除除容器中第一个元素之外的所有元素。这概括了&#34;删除所有重复的经典问题&#34;,因为相等是等价关系的特殊情况。下面的代码现在给出了正确的结果,但我想知道是否有办法削弱相关的条件&#39;并且仍然得到相同的结果。

[=](const element& x)->bool {return relatedIndirectly(*it,x);}

如果&#39;相关&#39;是'知道&#39;或者&#39;是&#39;的朋友,它们不需要是对称的也不是传递性的,我们仍然会有分区,因此removeRelated仍然可以工作吗?

我想知道的另一个问题是:对上述进行排序的最快排序算法是什么,以便等价类由连续元素组成?这就是我想出的:

#include <iostream>
#include <algorithm>
#include <list>
#include <set>

template<typename Container, typename BinaryPredicate>
void removeRelated (Container& container, BinaryPredicate related) {
    using element = typename Container::value_type;
    std::set<element> s;
    for (typename Container::iterator it = std::begin(container);  it != std::end(container); ) {
        if (std::find_if(s.begin(), s.end(),
                [=](const element& x)->bool {return related(*it,x);}) != s.end())
            it = container.erase(it);  // *it is related to an element in s.
        else {
            s.insert(*it);
            it++;
        }
    }
}

// Used for testing removeRelated.  Person::isRelativeOf is an equivalence relation.
struct Person {
    std::string name;
    std::list<Person*> relatives;
    Person (const std::string& n) : name(n) {relatives.push_back(this);}  // Forcing reflexivity
    void addRelative (Person* p) {
        for (Person* relatives_of_p : p->relatives)
            relatives.push_back(relatives_of_p);  // Forcing transitivity ('relatives.push_back(p);' included in this)
        p->relatives.push_back(this);  // Forcing symmetry
    }
    bool isRelativeOf (Person* p) const {
        return std::find(relatives.begin(), relatives.end(), p) != relatives.end();
    }
};

int main() {
    Person *bob = new Person("Bob"), *mary = new Person("Mary"), *jim = new Person("Jim"),
        *sally = new Person("Sally"), *fred = new Person("Fred");
    bob->addRelative(mary);  // crashes
    mary->addRelative(jim);
    jim->addRelative(bob);
    sally->addRelative(fred);
    std::list<Person*> everybody {bob, mary, jim, sally, fred};
    removeRelated (everybody, [](Person* a, Person* b)->bool {return a->isRelativeOf(b);});
    for (const Person* x : everybody) std::cout << x->name << ' ';  // Bob Sally (correct)
}

但排序并不保留元素的原始相对排序。如何保存?

1 个答案:

答案 0 :(得分:1)

好的,您可以通过以下

定义子集
  

让我们将“关系子集”定义为所有元素的集合,使得子集中的任何元素通过“相关”与子集中的至少一个其他元素相关,而不是自身

这听起来有点递归,但据我了解

  • 没有外向关系的节点n位于单身{n}
  • 的子集中
  • 仅与单身人士有外向关系的节点n位于单身{n}
  • 的子集中
  • 任何周期(任何长度),更常见的是任何强连接组件形成一个子集,包括其关系的所有前任节点

实施例。以下关系:

A -> B
B -> A
C -> B
D -> C
E -> F

定义以下子集:{A, B, C, D}{E}{F}


假设以上是正确的,我们可以设计以下算法(伪代码):

int visit(Node n, int s) { // returns 0 iff there is no cycle anywhere beneath

    if(length(n.relations) = 0 || n.singleton == true)
    // leaves, or only leaves below
    {
        n.singleton = true;
        return false;
    }
    else if(n.visited == true || n.bigger_subset > 0)
    // part of a subcycle, or predecessor of one
    {
        n.bigger_subset = s;
        return true;
    }
    else
    // searching for the nature of what is below
    {
        n.visited = true;
        bool found_cycle = 0;

        for each node m such that n is related to m (n -> m)
            found_cycle = max(Visit(m), found_cycle);

        if( found_cycle > 0 )
            n.bigger_subset = found_cycle;
        else
            n.singleton = true; // parent of only singletons

        n.visited = false;
        return found_cycle;
    }
}

s = length(node_set) + 1; // clearly bigger than the maximal number of subsets
for n in node_set:
{
    if( n.singleton == false && n.big_subcycle == 0 )
    {
        // state unknown, it is thus the first of its subset, unless it is a predecessor (or part) of a known subset
        int set = visit(n, s);

        // not a singleton, and not the current set : a previous one
        if( set > s )
            node_set.remove(n);

        s--;
    }
    else
        node_set.remove(n);
}

这样做基本上是从每个元素开始的深度优先搜索,标记正在访问的节点,以便检测周期。通过记住每个节点的状态,可以将子集的任何前任添加到子集中,而无需再次进入循环。


以下是上面给出的示例中此算法的C代码:http://ideone.com/VNumcN