×

Search anything:

Algorithm to find cliques of a given size k【O(n^k) time complexity】

Internship at OpenGenus

Get this book -> Problems on Array: For Interviews and Competitive Programming

Reading time: 35 minutes

In this article, we will go through a simple yet elegant algorithm to find a clique of a given size. Clique is an interesting topic in itself given that the clique decision problem is NP-Complete and clique arises in almost all real-life applications involving graphs. Before we go into the wonderful algorithm, we will go through some basic ideas.

A clique is a subset of vertices of an undirected graph G such that every two distinct vertices in the clique are adjacent; that is, its induced subgraph is complete. Cliques are one of the basic concepts of graph theory and are used in many other mathematical problems and constructions on graphs. So we can say that a clique in an undirected graph is a subgraph that is complete.

Learn more about Clique in general and related ideas and problems

A maximal clique is a clique that cannot be extended by including one more adjacent vertex, that is, a clique which does not exist exclusively within the vertex set of a larger clique. Maximal cliques can be very small. A graph may contain a non-maximal clique with many vertices and a separate clique of size 2 which is maximal. While a maximum (i.e., largest) clique is necessarily maximal, the converse does not hold.

Learn why the Clique decision problem is NP-Complete

A clique of size k in a graph G is a clique of graph G containing k vertices, i.e. the degree of each vertex is k-1 in that clique.
So particularly, if there is a subset of k vertices that are connected to each other in the graph G, we say that graph contains a k-clique.
A k-clique can be a maximal clique or can be a subset of a maximal clique, so if a graph contains a clique of size more than k then it definitely contains a clique of size k.
For example the graph shown below:

k_cliques

Algorithm

We can find all the 2-cliques by simply enumerating all the edges.

To find k+1-cliques, we can use the previous results. Compare all the pairs of k-cliques. If the two subgraphs have k-1 vertices in common and graph contains the missing edge, we can form a k+1-clique.

def k_cliques(graph):
    # 2-cliques
    cliques = [{i, j} for i, j in graph.edges() if i != j]
    k = 2

    while cliques:
        # result
        yield k, cliques

        # merge k-cliques into (k+1)-cliques
        cliques_1 = set()
        for u, v in combinations(cliques, 2):
            w = u ^ v
            if len(w) == 2 and graph.has_edge(*w):
                cliques_1.add(tuple(u | w))

        # remove duplicates
        cliques = list(map(set, cliques_1))
        k += 1

The above algorithm of finding k-clique in a graph G takes polinomial time for its execution. The algorithm starts from 2-clique pairs and use this as base data to find 3-cliques and more.

To generate 3-cliques from 2-cliques we take each combination pair of 2-cliques and take intersection of the pair, if the intersection is an edge and it is present in the graph then the union of the pair is a clique of size 3. By doing intersection of the pair we find the missing edge so that the 2-clique can be extended to 3-clique, and if the edge is present in the graph then we extend the 2-clique pair into 3-clique and store it. In similar way we generate k+1-clique from k-clique.

Let's understand with it with a graph with 4 vertices:

2-3_clique

To find k-cliques we iterate the same method O(k) times. The method which finds the p+1-clique from p-clique takes O(n) time where n is number of vertices. So in overall the algorithm takes O(nk) time in the worst case.

Implementation

Code in Python3

from itertools import combinations
import networkx as nx


def k_cliques(graph):
    # 2-cliques
    cliques = [{i, j} for i, j in graph.edges() if i != j]
    k = 2

    while cliques:
        # result
        yield k, cliques

        # merge k-cliques into (k+1)-cliques
        cliques_1 = set()
        for u, v in combinations(cliques, 2):
            w = u ^ v
            if len(w) == 2 and graph.has_edge(*w):
                cliques_1.add(tuple(u | w))

        # remove duplicates
        cliques = list(map(set, cliques_1))
        k += 1


def print_cliques(graph, size_k):
    for k, cliques in k_cliques(graph):
        if k == size_k:
            print('%d-cliques = %d, %s.' % (k, len(cliques), cliques))


nodes, edges = 6, 10
size_k = 3
graph = nx.Graph()
graph.add_nodes_from(range(nodes))
graph.add_edge(1, 2)
graph.add_edge(1, 3)
graph.add_edge(1, 5)
graph.add_edge(2, 3)
graph.add_edge(2, 4)
graph.add_edge(2, 6)
graph.add_edge(3, 4)
graph.add_edge(3, 6)
graph.add_edge(4, 5)
graph.add_edge(4, 6)

print_cliques(graph, size_k)

Output

3-cliques = 5, [{3, 4, 6}, {2, 3, 6}, {2, 4, 6}, {1, 2, 3}, {2, 3, 4}].

Complexity

Time Complexity

  • The k-clique algorithm takes O(nk) (i.e. polynomial) time in the worst case.

Space Complexity

  • The k-clique algoorithm takes O(n2) auxiliary space in the worst case.
Using Bron Kerbosch algorithm to find maximal cliques in O(3^(N/3))
Greedy approach to find a single maximal clique in O(V^2) time complexity

References

Algorithm to find cliques of a given size k【O(n^k) time complexity】
Share this