c ++图形界面编程_C ++中的图形表示(作业面试速查表)

c ++图形界面编程

更新 :您可以在这里观看有关C ++中图形表示的视频:

图形被正式定义为一组顶点V和一组连接这些顶点的边E

G =(V,E)

每个边都是一对顶点。 对于有向图,边是有序对,对于无向图,边是无序的。

我们如何用C ++表示图? 本文总结了使用C ++标准模板库(STL)可用的各种选项。

对于每种方法,我们将实现一个简单的算法来检查图是否为Eulerian ,即奇数节点的数量是否正好为0或2。这被称为“柯尼斯堡七桥”问题。

方法1:定义的直接转换:G =(V,E)

这可能是用C ++表示图形的最直接的方法:我们仅将数学定义直接转换为STL容器。

在此方法中,可以使用以下两个STL容器之一来表示VEstd::vector, std::set, std::list

考虑以下简单图形:

为了简单起见,在所有可用选项中,我们选择:

  • V:std :: vector
  • E:std :: vector
  • 边:std :: pair

现在,可以将图定义为一个类,并可以使用STL的统一初始化轻松进行初始化

class Graph {
 public :
  Graph( std :: vector < int > &v, std :: vector < std ::pair< int , int >> &e)
      : v_(v), e_(e) {}
  bool IsEulerWalkable () ;
  void PrintGraph () ;
  std :: vector < int > v_;
  std :: vector < std ::pair< int , int >> e_;
};

int main ()  {
  std :: vector < int > v = { 0 , 1 , 2 , 3 };
  std :: vector < std ::pair< int , int >> e = {{ 1 , 3 }, { 1 , 3 }, { 3 , 0 }, { 3 , 0 },
                                        { 0 , 2 }, { 2 , 1 }, { 2 , 3 }};
  Graph g (v, e) ;
  std :: cout << g.IsEulerWalkable() << std :: endl ;
}

请注意,上述代码中的顶点和边初始化与输入图定义的匹配程度如何。

有了上面的定义和初始化,我们现在可以将IsEulerWalkable()实现为:

bool Graph::IsEulerWalkable() {
  // Create a table to hold degree of each vertex
  std :: vector < int > degrees(v_.size());
  
  // Iterate all edges
  for ( auto e : e_) {
    degrees[e.first]++;
    degrees[e.second]++;
  }
  int countOdds = 0 ;

  // Iterate through degree table and count the number of odd ones
  for ( auto d : degrees) {
    if (d % 2 == 1 ) {
      countOdds++;
    }
  }
  return (countOdds == 0 || countOdds == 2 );
}

上面的代码很容易说明:我们遍历所有边缘并增加边缘上每个顶点的度数。 最后,我们计算具有奇数边的顶点数,如果计数为0或2,则返回true。

方法2:邻接表

在此方法中,从概念上讲,我们将图形表示为顶点和相邻顶点集之间的映射,而不是为VE设置两个单独的集合。 考虑下图:

可以使用下表来表示此映射:

为了在C ++中对此建模,我们可以创建一个名为Vertex的新类,该类具有两个成员变量:

  1. vertex_number :一个整数变量
  2. adjacents :一组相邻的顶点
  3. struct Vertex {
      Vertex( int v, std :: set < int > a) : vertex_number(v), adjacents(a) {}
      int vertex_number;
      std :: set < int > adjacents;
    };
    
    class Graph {
     public :
      Graph( std :: vector <Vertex> v) : v_(v) {}
      bool IsEulerWalkable () ;
      std :: vector <Vertex> v_;
    };
    
    int main ()  {
      Graph g ({Vertex( 0 , { 1 , 2 , 3 }), Vertex( 1 , { 0 , 2 , 3 }), Vertex( 2 , { 0 , 1 , 3 }),
               Vertex( 3 , { 0 , 1 , 2 })}) ;
      std :: cout << g.IsEulerWalkable() << std :: endl ;
    }

    再次注意,我们如何使用统一初始化将图形初始化为表示该图形的输入表。

    具有以上定义,我们可以将IsEulerWalkable()重写为:

    bool Graph::IsEulerWalkable() {
      // Create a table to hold degree of each vertex
      std :: vector < int > degrees(v_.size());
    
      // Iterate vertices
      for ( auto v : v_) {
        degrees[v.vertex_number] = v.adjacents.size();
      }
    
      // Iterate through degree table and count the number of odd ones
    
      int countOdds = 0 ;
    
      for ( auto d : degrees) {
        if (d % 2 == 1 ) {
          countOdds++;
        }
      }
      return (countOdds == 0 || countOdds == 2 );
    }

    注意我们如何使用STL的size()函数来获取每个节点的度数:

    使用邻接表,每个节点的程度已经可用。

    方法3:邻接矩阵

    邻接矩阵是一个二维矩阵,如果在顶点ij之间存在边,则条目i,j1

    如果该图是无向的,则邻接矩阵是对称的:如果条目i,j1 ,则条目ji也为1

    邻接矩阵可以使用二维矩阵表示:

    class Graph {
     public :
      Graph( std :: vector < std :: vector < int >> &adjacency) : adjacency_(adjacency) {}
      bool IsEulerWalkable () ;
      void PrintGraph () ;
      std :: vector < std :: vector < int >> adjacency_;
    };
    
    int main ()  {
      std :: vector < std :: vector < int >> adjacency = {{ 0 , 1 , 1 , 0 , 0 },
                                                 { 1 , 0 , 1 , 1 , 1 },
                                                 { 1 , 1 , 0 , 1 , 0 },
                                                 { 0 , 1 , 1 , 0 , 1 },
                                                 { 0 , 1 , 0 , 1 , 0 }};
      Graph g (adjacency) ;
      std :: cout << "g.isEulerWalkable(): " << g.IsEulerWalkable() << std :: endl ;
      return 0 ;
    }

    再次注意,使用统一初始化的邻接矩阵初始化与给定表的匹配程度如何。

    通过上面的类定义,我们可以将IsEulerWalkable()重写为:

    bool Graph::IsEulerWalkable() {
      // Create a table to hold degree of each vertex
      std :: vector < int > degrees(adjacency_.size());
    
      // Iterate all edges
      for ( int i = 0 ; i < adjacency_.size(); i++) {
        for ( int j = 0 ; j < adjacency_.size(); j++) {
          if (adjacency_[i][j] == 1 ) {
            degrees[i]++;
          }
        }
      }
    
      int countOdds = 0 ;
    
      // Iterate through degree table and count the number of odd ones
      for ( auto d : degrees) {
        if (d % 2 == 1 ) {
          countOdds++;
        }
      }
      return (countOdds == 0 || countOdds == 2 );
    }

    请注意,要获取每个顶点的度数,我们必须迭代每行并添加1's数量。

    邻接矩阵的主要问题是它的大小。 对于n个顶点的图,我们需要n^2个条目。 但是,优点是我们可以仅通过检查O(1)时间中的条目ij来检查节点i和j是否相邻。

    三种方法的比较

    这些方法如何比较? 下表总结了主要区别:

    第一种方法,直接平移,似乎具有较小的内存大小,但是找到顶点的相邻点需要O(m) ,其中m是边的数量。

    邻接表提高了查找顶点相邻点的运行时复杂度。 但是,如果我们只想检查节点i,j是否相邻,则运行时间仍然不是恒定的。

    邻接矩阵具有最大的空间,但是由于向量访问时间的性质,您可以快速检查两个节点是否在恒定时间内相邻。

    在std :: vector,std :: set和std :: list之间进行权衡:

    正如我在一开始所提到的,在使用STL的向量,集合或列表表示顶点和边缘之间需要权衡取舍。

    让我们回顾一些主要差异:

    std :: set:

  • 一套自动排序
  • 插入/删除/查找集合需要O(log n)

std :: unordered_set:

  • 无序集未排序
  • 插入/删除/查找集合需要O(log 1)

std :: vector:

  • 向量未排序
  • 插入/删除/查找集合需要O(n)
  • std :: list:

  • 列表未排序
  • 在集合中查找需要O(n)
  • 插入/删除集合需要O(1)
  • 基于此,如果要使边缘或顶点保持排序,则可能应该使用一组。 如果要非常快的搜索时间,则应使用unordered_set。

    如果查找时间不那么重要,则可以使用列表或向量。 但是还有一个更重要的原因,那就是人们可能使用向量/列表而不是集合来表示多图:

    多重图形类似于图形,但是在两个节点之间可以有多个边。

    set或unordered_set不能包含重复项。 例如,在上图所示的多重图形中,我们将在0和1之间有两个相似的边: (0,1)(0,1) 。 不能使用集合来表示。 因此,为了表示多重图形,您可能需要使用std :: vector,list或multiset

    摘要

    我们展示了如何使用以下三种方法之一在C ++中表示图形:图形定义的直接翻译,邻接表和邻接矩阵。 如果您准备进行技术面试,请确保您了解每种方法的区别以及优缺点。

    通过GTest,Glog和Abseil来查看有关Bazel和Visual Studio Code的其他视频:

              翻译自: https://hackernoon.com/graph-representation-in-c-job-interview-cheatsheet-sp1a3vgx

              c ++图形界面编程

            • 0
              点赞
            • 0
              收藏
              觉得还不错? 一键收藏
            • 0
              评论
            评论
            添加红包

            请填写红包祝福语或标题

            红包个数最小为10个

            红包金额最低5元

            当前余额3.43前往充值 >
            需支付:10.00
            成就一亿技术人!
            领取后你会自动成为博主和红包主的粉丝 规则
            hope_wisdom
            发出的红包
            实付
            使用余额支付
            点击重新获取
            扫码支付
            钱包余额 0

            抵扣说明:

            1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
            2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

            余额充值