【问题标题】:How to check for cycles in a graph?如何检查图中的循环?
【发布时间】:2020-01-07 14:30:25
【问题描述】:

我的代码工作正常,但在创建循环时跳过对的 check50 测试失败。我用来检查周期的逻辑是,在创建从赢家到输家的边缘之前,我从赢家返回边缘并检查它是否曾经到达失败者。如果是这样,则意味着它将创建一个循环,因此跳过边缘但它不起作用。我的逻辑也可能是错误的,如果是这样,请告诉我。这是我的代码-

#include <cs50.h>
#include <stdio.h>
#include <string.h>

// Max number of candidates
#define MAX 9

// preferences[i][j] is number of voters who prefer i over j
int preferences[MAX][MAX];

// locked[i][j] means i is locked in over j
bool locked[MAX][MAX];

// Each pair has a winner, loser
typedef struct
{
    int winner;
    int loser;
}
pair;

// Array of candidates
string candidates[MAX];
pair pairs[MAX * (MAX - 1) / 2];

int pair_count;
int candidate_count;

// Function prototypes
bool vote(int rank, string name, int ranks[]);
void record_preferences(int ranks[]);
void add_pairs(void);
void sort_pairs(void);
void lock_pairs(void);
void print_winner(void);
bool check_cycle(int n, int m);

int main(int argc, string argv[])
{
    // Check for invalid usage
    if (argc < 2)
    {
        printf("Usage: tideman [candidate ...]\n");
        return 1;
    }

    // Populate array of candidates
    candidate_count = argc - 1;
    if (candidate_count > MAX)
    {
        printf("Maximum number of candidates is %i\n", MAX);
        return 2;
    }
    for (int i = 0; i < candidate_count; i++)
    {
        candidates[i] = argv[i + 1];
    }

    // Clear graph of locked in pairs
    for (int i = 0; i < candidate_count; i++)
    {
        for (int j = 0; j < candidate_count; j++)
        {
            locked[i][j] = false;
        }
    }

    pair_count = 0;
    int voter_count = get_int("Number of voters: ");

    // Query for votes
    for (int i = 0; i < voter_count; i++)
    {
        // ranks[i] is voter's ith preference
        int ranks[candidate_count];

        // Query for each rank
        for (int j = 0; j < candidate_count; j++)
        {
            string name = get_string("Rank %i: ", j + 1);

            if (!vote(j, name, ranks))
            {
                printf("Invalid vote.\n");
                return 3;
            }
        }

        record_preferences(ranks);

        printf("\n");
    }

    add_pairs();
    sort_pairs();
    lock_pairs();
    print_winner();
    return 0;
}

// Update ranks given a new vote
bool vote(int rank, string name, int ranks[])
{
    // TODO
    for (int i = 0; i < candidate_count; i++)
    {
        if (strcmp(candidates[i], name) == 0)
        {
            ranks[rank] = i;
            return true;
        }
    }

    return false;
}

// Update preferences given one voter's ranks
void record_preferences(int ranks[])
{
    // TODO
    for (int i = 0; i < candidate_count; i++)
    {
        for (int j = 1; j < candidate_count - i; j++)
        {
            preferences[ranks[i]][ranks[i + j]]++;
        }
    }

    return;
}

// Record pairs of candidates where one is preferred over the other
void add_pairs(void)
{
    // TODO
    for (int i = 0; i < candidate_count; i++)
    {
        for (int j = 0; j < candidate_count; j++)
        {
            if (preferences[i][j] > preferences[j][i])
            {
                pairs[pair_count].winner = i;
                pairs[pair_count].loser = j;
                pair_count++;
            }
        }
    }

    return;
}

// Sort pairs in decreasing order by strength of victory
void sort_pairs(void)
{
    // TODO
    pair k;
    for (int i = 0; i < pair_count; i++)
    {
        for (int j = i + 1; j < pair_count; j++)
        {
            if (preferences[pairs[i].winner][pairs[i].loser] < preferences[pairs[j].winner][pairs[j].loser])
            {
                //memcpy
                k = pairs[i];
                pairs[i] = pairs[j];
                pairs[j] = k;
            }
        }
    }

    return;
}

// Lock pairs into the candidate graph in order, without creating cycles
void lock_pairs(void)
{
    // TODO
    for (int i = 0; i < pair_count; i++)
    {
        if (!check_cycle(pairs[i].winner, pairs[i].loser))
        {
            locked[pairs[i].winner][pairs[i].loser] = true;
        }
    }
    return;
}

// Print the winner of the election
void print_winner(void)
{
    // TODO
    for (int i = 0; i < candidate_count; i++)
    {
        bool source = true;

        for (int j = 0; j < candidate_count; j++)
        {
            if (locked[j][i] == true)
            {
                source = false;
                break;
            }
        }

        if (source == true)
        {
            printf("%s\n", candidates[i]);
        }
    }

    return;
}

//checking for cycle
bool check_cycle(int n, int m)
{
    if (locked[m][n] == true)
    {
        return true;
    }

    for (int i = 0; i < candidate_count; i++)
    {
        if (locked[i][n] == true)
        {
            check_cycle(i, m);
        }
    }
    return false;
}

【问题讨论】:

  • 我不相信问题中有足够的信息。它当然不是 MCVE (Minimal, Complete, Verifiable Example)(或 MRE 或 SO 现在使用的任何名称)或 SSCCE (Short, Self-Contained, Correct Example)。我想也许你的循环检查只寻找长度为 2 的循环(那里和后面),而不是涉及多个步骤的循环。因此,如果您有节点 A->B->C 和 A->D 并添加 C->D,则它可能无法正确检测到(我不确定该示例)。我可能找错树了。

标签: c graph cycle cs50


【解决方案1】:

你只是在颠倒循环的方向。我使用了与您相同的逻辑并且它有效: 1.查看当前失败者是否锁定当前获胜者; 2.如果是,则返回true; 3. 否则,看是否有其他人锁定当前获胜者; 4. 递归调用循环检查器,查看当前失败者是否锁定到'i'。现在小心这一步,因为您必须将值传递给函数,因此基本情况检查器会执行 [loser][i],而不是 [i][loser],因为基本情况检查初始 LOSER 是否锁定在获胜者身上。并记得返回。这是我使用的代码,工作正常。

//Can_reach recursive auxiliary function: returns true if a can reach b.
//a = initial winner, b = initial loser
bool loopcheck(int a, int b)
{
    if (locked[b][a] == true)
    {
        return true;
    }

    for (int i = 0; i < candidate_count; i++)
    {
        if (locked[i][a] == true)
            {
            return loopcheck(i, b);
        }
    }
    return false;
}

【讨论】:

    【解决方案2】:

    我不认为之前的一些答案给出了正确的答案。

    1. 对于n 候选人,存在ith 候选人,没有 指向她或他的箭头当且仅当 “锁定”表全是假的。在这种情况下,没有圆(a 如果第 i 列必须是节点,则可以绘制具有 n 个节点的圆。
    2. 但是,这并不能保证没有其他更小的圆圈。如果我们将“锁定”表缩小一号(即删除 nth 候选人,现在我们将有一个 (n-1) *(n-1) “锁定” 桌子。就像我们在step(1) 中所做的一样,我们可以确保一圈n-1 节点也是不可能的。通过重复此处理,不可能有任何圆(任何大小)。
    3. 我们可以使用递归来检查它。

    如果你不检查是否可以做更小的圆圈:你会出现这样的错误:

    lock_pairs 如果创建循环则跳过中间对 lock_pairs 没有正确锁定所有非循环对

    代码如下:

    bool is_circle (bool locked_array[MAX][MAX], int candi_count)
    {
        //check the basic case
        //if there is only one node of course it doesn't form a circle
        if (candi_count == 1)
        {
            return false;
        }
    
        //recursively check whether the smaller locked_array with n-1 candidate form a circle
        //if the smaller one have a circle
        //this mean a middle pair create a circle if added into the "locked" table
        if (!is_circle(locked_array, candi_count - 1))
        {
            //if the one-size smaller "locked" table doesn't have a circle
            //check whether adding a new candidate forms a circle
    
            //if there is a column that is all false's after adding a new candidate
            //then it must not introduce a circle in this step
            for (int j = 0; j < candi_count; j++)
            {
                //a indicator variable for check whether a column has a true value;
                bool true_in_column = false;
    
                for (int i = 0; i < candi_count; i++)
                {
                    if (locked_array[i][j] == true)
                    {
                        true_in_column = true;
                    }
                }
    
                //if there is a column doesn't have a true value then not circle is create at this step
                if (true_in_column == false)
                {
                    return false;
                }
            }
    
            //if we cannot find such a column then the graph represented by current locked array
            //must have a circle
            return true;
    
        }
    
        //the smaller "locked" table forms a circle
        else
        {
            return true;
        }
    
    }
    
    

    【讨论】:

    • 感谢您确认之前的答案并解释您认为它们不起作用的原因。在回答老问题时,这是一个很好的做法,非常值得赞赏——尤其是对于第一篇文章。感谢您对社区的贡献!
    【解决方案3】:

    if (locked[i][n] == true) 那么这个“赢家”(n)是另一个锁定对中的“输家”,因此将创建一个循环。 IMO 这就是您决定是否将这对锁定在lock_pairs 函数中所需的所有信息。

    【讨论】:

      【解决方案4】:

      if (locked[i][n] == true)内部,如果check_cycle(i, m)返回true,你也应该在check_cycle(n, m)中返回true,这样函数才能正常工作。

      【讨论】:

        【解决方案5】:

        我想出了这个非递归版本。在锁定对之前,在某个锁定节点中搜索一个失败者成为获胜者,如果有的话 - 跳过它。

        //set first locked pair
        if(pair_count > 0)
        {
            locked[pairs[0].winner][pairs[0].loser] = true;
        }
        
        for (int i = 1; i < pair_count; i++)
        {
            bool cycle = false;
            for (int j = 0; j < pair_count; j++){
                if(locked[pairs[i].loser][j])
                {
                    cycle = true;
                    break;
                }
            }
        
            //check if adding this node will create a cycle
            if(!cycle)
            {
                locked[pairs[i].winner][pairs[i].loser] = true;
            }
        }
        

        【讨论】:

          【解决方案6】:

          我认为if (locked[i][n] == true) 中的check_cycle (i, m) 返回的值不一致

          这里有一个代码来解决这个问题

          bool check_cycle(int n, int m)
          {
              if (locked[m][n] == true)
              {
                  return true;
              }
          
              for (int i = 0; i < candidate_count; i++)
              {
                  if (locked[i][n] == true)
                  {
                      if (check_cycle(i, m))
                      {
                          return true;   
                      }
                      else 
                      {
                          return false;
                      }
                  }
              }
              return false;
          }
          

          【讨论】:

            【解决方案7】:
            //Set all the edges in graph
            for (int i = 0; i < pair_count; i++)
            {
                locked[pairs[i].winner][pairs[i].loser] = true;
            }
            //Check if there is a cycle remove the edges that make a cycle
            for (int i = 0; i < pair_count; i++)
            {
                for (int j = i + 1; j < pair_count; j++)
                {
                    if (pairs[i].winner == pairs[j].loser)
                    {
                        locked[pairs[j].winner][pairs[j].loser] = false;
                    }
                }
            }
            

            【讨论】:

              【解决方案8】:

              只需从您的 lock_pairs 函数中调用此函数,并使用 (loser, Winner) 作为参数来检查失败者是否有任何可以追溯到获胜者的连接。

              为了简化,如果你想检查你可以建立一个连接 a -> b 然后简单地调用 if(!circle(b,a) { }。如果链接将在一个封闭的循环中返回,则结果为 true,否则为 false如果没有。我们在传递 n(要检查的目的地)和 i(我们要继续关注的 id)时基本上进入了递归模式

                  bool circle(int i, int n)
              {
                  bool b = false;
                  if (i != n) // stop when the cycle results in a closed loop
                  {
                      for (int j = 0; j < candidate_count; j++)
                      {
                          if (locked[i][j] == true)
                          {  
                              b = circle(j, n); // Call again to follow the link from j
                          }
                      }
                  }
                  else
                  {
                      b = true; // 
                  }
                  return b;
              }
              

              【讨论】:

                猜你喜欢
                • 2016-03-28
                • 1970-01-01
                • 1970-01-01
                • 2013-02-23
                • 2015-09-22
                • 2016-11-05
                • 1970-01-01
                • 2021-07-28
                • 2017-11-07
                相关资源
                最近更新 更多