📜  图形着色的 DSatur 算法

📅  最后修改于: 2022-05-13 01:56:06.993000             🧑  作者: Mango

图形着色的 DSatur 算法

图形着色是将颜色分配给图形顶点的任务,以便:

  • 成对的相邻顶点被分配不同的颜色,并且
  • 图表中使用的不同颜色的数量最少。

下图仅使用三种颜色(此处为红色、蓝色和绿色)着色。这实际上是这个特定图形所需的最小颜色数——也就是说,我们不能使用少于三种颜色为这个图形着色,同时确保相邻顶点的颜色不同。

图形的正确三色

为图形G着色所需的最小颜色数称为色数,通常用χ(G)表示。确定图的色数是 NP 难的。决定图 G 是否存在k着色的相应决策问题也是 NP 完全的。

该网站上的类似帖子已经描述了图形着色的贪心算法。该算法易于应用,但其解决方案中使用的颜色数量很大程度上取决于考虑顶点的顺序。在最好的情况下,正确的排序将产生使用χ(G)颜色的解决方案;然而,糟糕的订购可能会导致解决方案使用许多额外的颜色。

DSatur 算法(缩写为“饱和度”)具有与贪心算法相似的行为。不同之处在于它生成顶点排序的方式。具体来说,总是选择下一个要着色的顶点作为饱和度最高的未着色顶点。顶点的饱和度定义为当前分配给相邻顶点的不同颜色的数量。然后还使用其他规则来打破平局。

G是一个有n个顶点和m个边的图。此外,假设我们将使用颜色标签0, 1, 2, ..., n-1 。 (解决方案中永远不需要超过n种颜色)。 DSatur 算法操作如下

  1. vG中饱和度最大的无色顶点。在有关系的情况下,选择由无色顶点诱导的子图中度数最大的顶点。进一步的联系可以任意中断。
  2. v分配给颜色i ,其中i是集合{0, 1, 2, ..., n}中当前未分配给v的任何邻居的最小整数。
  3. 如果仍有未着色的顶点,则再次重复所有步骤,否则,在此步骤结束。

DSatur 算法类似于贪心算法,因为一旦选择了一个顶点,它就会被分配给没有分配给任何邻居的最低颜色标签。因此,第 1 步的动作提供了算法背后的主要力量,因为它们优先考虑被视为“最受约束”的顶点——即当前可用颜色选项最少的顶点。因此,首先处理这些“更受约束”的顶点,允许稍后对受约束较少的顶点着色。

DSatur的分析

因为 DSatur 算法在执行过程中会生成一个顶点排序,所以它使用的颜色数量比贪心算法更容易预测。它的解决方案也往往比贪心算法的解决方案具有更少的颜色。该算法的一个特点是,如果一个图由多个组件组成,那么单个组件的所有顶点将在考虑其他顶点之前被着色。 DSatur 也适用于几种图拓扑,包括二分图、循环图和轮图。 (使用这些图表,将始终生成使用χ(G)颜色的解决方案。)

DSatur 算法的总体复杂度为O(n 2 ) ,其中n是图中的顶点数。这可以通过执行一个O(n)过程的n 个单独应用程序来实现:

  • 根据 DSatur 的选择规则标识下一个要着色的顶点。
  • 为该顶点着色。

下面我们展示了 DSatur 的 C++ 实现,它在O((n + m) log n)时间内运行,其中m是图中的边数。对于除了最密集的图之外的所有图,这都比O(n 2 )快得多。该实现涉及使用红黑二叉树来存储所有尚未着色的顶点,以及它们的饱和度以及它们在由未着色顶点引起的子图中的度数。红黑树是一种自平衡二叉树,与 C++ 标准模板库中的集合容器一起使用。这允许在恒定时间内执行下一个要着色的顶点的选择(根据 DSatur 的选择规则)。它还允许在对数时间内插入和删除项目。

C++
// A C++ program to implement the DSatur algorithm for graph
// coloring
 
#include 
#include 
#include 
#include 
using namespace std;
 
// Struct to store information
// on each uncoloured vertex
struct nodeInfo {
    int sat; // Saturation degree of the vertex
    int deg; // Degree in the uncoloured subgraph
    int vertex; // Index of vertex
};
struct maxSat {
    bool operator()(const nodeInfo& lhs,
                    const nodeInfo& rhs) const
    {
        // Compares two nodes by
        // saturation degree, then
        // degree in the subgraph,
        // then vertex label
        return tie(lhs.sat, lhs.deg, lhs.vertex)
               > tie(rhs.sat, rhs.deg, rhs.vertex);
    }
};
 
// Class representing
// an undirected graph
class Graph {
 
    // Number of vertices
    int n;
 
    // Number of vertices
    vector > adj;
 
public:
    // Constructor and destructor
    Graph(int numNodes)
    {
        n = numNodes;
        adj.resize(n, vector());
    }
    ~Graph() { adj.clear(); }
 
    // Function to add an edge to graph
    void addEdge(int u, int v);
 
    // Colour the graph
    // using the DSatur algorithm
    void DSatur();
};
 
void Graph::addEdge(int u, int v)
{
    adj[u].push_back(v);
    adj[v].push_back(u);
}
 
// Assigns colors (starting from 0)
// to all vertices and
// prints the assignment of colors
void Graph::DSatur()
{
    int u, i;
    vector used(n, false);
    vector c(n), d(n);
    vector > adjCols(n);
    set Q;
    set::iterator maxPtr;
 
    // Initialise the data structures.
    // These are a (binary
    // tree) priority queue, a set
    // of colours adjacent to
    // each uncoloured vertex
    // (initially empty) and the
    // degree d(v) of each uncoloured
    // vertex in the graph
    // induced by uncoloured vertices
    for (u = 0; u < n; u++) {
        c[u] = -1;
        d[u] = adj[u].size();
        adjCols[u] = set();
        Q.emplace(nodeInfo{ 0, d[u], u });
    }
 
    while (!Q.empty()) {
 
        // Choose the vertex u
        // with highest saturation
        // degree, breaking ties with d.
        // Remove u from the priority queue
        maxPtr = Q.begin();
        u = (*maxPtr).vertex;
        Q.erase(maxPtr);
 
        // Identify the lowest feasible
        // colour i for vertex u
        for (int v : adj[u])
            if (c[v] != -1)
                used] = true;
        for (i = 0; i < used.size(); i++)
            if (used[i] == false)
                break;
        for (int v : adj[u])
            if (c[v] != -1)
                used] = false;
 
        // Assign vertex u to colour i
        c[u] = i;
 
        // Update the saturation degrees and
        // degrees of all uncoloured neighbours;
        // hence modify their corresponding
        // elements in the priority queue
        for (int v : adj[u]) {
            if (c[v] == -1) {
                Q.erase(
                    { int(adjCols[v].size()),
                      d[v], v });
                adjCols[v].insert(i);
                d[v]--;
                Q.emplace(nodeInfo{
                    int(adjCols[v].size()),
                    d[v], v });
            }
        }
    }
 
    // The full graph has been coloured.
    // Print the result
    for (u = 0; u < n; u++)
        cout << "Vertex " << u
             << " --->  Color " << c[u]
             << endl;
}
 
// Driver Code
int main()
{
    Graph G1(5);
    G1.addEdge(0, 1);
    G1.addEdge(0, 2);
    G1.addEdge(1, 2);
    G1.addEdge(1, 3);
    G1.addEdge(2, 3);
    G1.addEdge(3, 4);
    cout << "Coloring of graph G1 \n";
    G1.DSatur();
 
    Graph G2(5);
    G2.addEdge(0, 1);
    G2.addEdge(0, 2);
    G2.addEdge(1, 2);
    G2.addEdge(1, 4);
    G2.addEdge(2, 4);
    G2.addEdge(4, 3);
    cout << "\nColoring of graph G2 \n";
    G2.DSatur();
 
    return 0;
}


输出
Coloring of graph G1 
Vertex 0 --->  Color 0
Vertex 1 --->  Color 2
Vertex 2 --->  Color 1
Vertex 3 --->  Color 0
Vertex 4 --->  Color 1

Coloring of graph G2 
Vertex 0 --->  Color 0
Vertex 1 --->  Color 2
Vertex 2 --->  Color 1
Vertex 3 --->  Color 1
Vertex 4 --->  Color 0

该实现的第一部分涉及初始化数据结构。这涉及遍历每个顶点并填充红黑树。这需要O(n log n)时间。在算法的主要部分,红黑树允许在恒定时间内选择下一个要着色的顶点u 。一旦u被着色,对应于u未着色邻居的项需要在红黑树中更新。对每个顶点执行此操作会导致总运行时间为O(m log n) 。因此,总运行时间为O((n log n) + (m log n)) = O((n+m) log n)

有关此算法和其他图形着色算法的更多信息,请参阅本书:图形着色指南:算法和应用程序(2021 年)