3

Suppose that there is a directed graph consists of vertices named below:

"ABC", "ABD", "ACB", "ACD", "ADB", "ADC", "BAC", "BAD",
"BCA", "BCD", "BDA", "BDC", "CAB", "CAD", "CBA", "CBD",
"CDA", "CDB", "DAB", "DAC", "DBA", "DBC", "DCA", "DCB"

These are the 3 letter permutations over 4 different letters. (total = 4*3*2=24) Name of vertices also describes edges between them. Any two vertices are connected to each other if last two character of source is equal to first two character of destination such as

ABC -> BCD

or

DCB -> CBA

The graph is very similar to De Burjin's or Kautz's, but not same. It is strongly connected and I know that it has Hamiltonian cycle.

To solve the problem, I'm not an expert at algorithms, I simply went through latest boost graph library and found hawick_unique_circuits() function which enumerates all cycles and here is my example codes:

#include <iostream>
#include <cstdint>
#include <vector>
#include <string>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/hawick_circuits.hpp>
#include "combination.hpp" // from http://howardhinnant.github.io/combinations.html

using namespace std;
using namespace boost;

typedef boost::adjacency_list<vecS, vecS, directedS, no_property, property<edge_weight_t, uint32_t> > TGraph;

TGraph m_Graph;

vector<string> m_StrVertexList;

void CreateStringVertexList(vector<string>& vl, uint32_t n, uint32_t k)
{
    vl.clear();

    if ((k > 0) && (n > k))
    {
        string code = "A";

        while (--n)
        {
            code += code.back() + 1;
        }   

        // for_each_permutation from Howard Hinnant
        // http://howardhinnant.github.io/combinations.html
        for_each_permutation(code.begin(), code.begin() + k, code.end(), 
                             [&](string::iterator first, string::iterator last)->bool{ vl.push_back(string(first, last)); return(false); });
    }
}

void AddEdgesFromStringVertex(TGraph& g, const vector<string>& vl)
{
    uint32_t connection_len = vl.begin()->size() - 1;

    g.clear();

    for (uint32_t f = 0; f < vl.size(); f++)
    for (uint32_t t = 0; t < vl.size(); t++)
    {
        if ((f != t) &&
            (vl[f].substr(1, connection_len) == vl[t].substr(0, connection_len)))
        {
            add_edge(f, t, 1, g);
        }
    }
}

class hawick_visitor
{
    public:
    void cycle(const vector<TGraph::vertex_descriptor>& circuit, const TGraph& graph) const
    {
        if (circuit.size() == m_StrVertexList.size())
        {
            for (auto ii = circuit.begin(); ii != circuit.end(); ++ii)
            {
                cout << m_StrVertexList[*ii] << " -> ";
            }

            cout << endl;
        }
    }
};

void Circuits(const TGraph& g)  
{
    hawick_unique_circuits(g, hawick_visitor());

    cout << "- end of hawick_unique_circuits() -" << endl;
}

void main(void)
{
    //CreateStringVertexList(m_StrVertexList, 10, 4);

    CreateStringVertexList(m_StrVertexList, 4, 3);
    AddEdgesFromStringVertex(m_Graph, m_StrVertexList);

    Circuits(m_Graph);
}

hawick_visitor class simply checks whether cycle found has same vertices as Graph's. If it has, that means we find one of Hamiltonian cycle we need.

It works perfectly for 24 vertices which is 3 char chosen from 4 unique char and here is one of outputs:

ABC -> BCA -> CAD -> ADB -> DBC -> BCD -> CDA -> DAC -> 
       ACB -> CBD -> BDC -> DCB -> CBA -> BAC -> ACD -> CDB -> 
       DBA -> BAD -> ADC -> DCA -> CAB -> ABD -> BDA -> DAB -> ABC

But when I try to solve similar graph has 5040 vertices named as 4 char chosen from 10 unique char, this function never returns. There should be a far better algorithm than hawick_unique_circuits() to do that. Because I know people doing similar calculation for 10,000 vertices less than a minute, but I don't know how. Any idea highly appreciated.

Here is the graph has 5040 vertices that I need to solve: enter image description here

Mehmet Fide
  • 1,534
  • 1
  • 17
  • 29

2 Answers2

6

Hamiltonian cycle from your graph: http://figshare.com/articles/Hamiltonian_Cycle/1228800

How to find Hamiltonian cycle in your graph in C#:

First file:

using System;
using System.Collections.Generic;

namespace Graph
{
    partial class Program
    {
        static List<string> vertices;
        static void Main(string[] args)
        {
            List<int>[] graph = GetGraph();
            List<int> HamiltonianCycle = Algorithm(graph);
            string a = Translate(HamiltonianCycle);
            Console.Write(a);
            Console.ReadKey();
        }
        static List<int>[] GetGraph()
        {
            List<string> list = new List<string>(){"A","B","C","D","E","F","G","H","I","J"};
            vertices = new List<string>();
            for(int a=0;a<10;++a)
                for(int b=0;b<10;++b)
                    for(int c=0;c<10;++c)
                        for(int d=0;d<10;++d)
            {
                if(a==b || a== c || a==d || b == c || b == d|| c==d)
                    continue;
                string vertex = list[a] + list[b] + list[c] + list[d];
                vertices.Add(vertex);
            }
            List<int>[] graph = new List<int>[vertices.Count];
            for(int i=0;i<graph.Length;++i)
                graph[i] = new List<int>();
            foreach(string s1 in vertices)
                foreach(string s2 in vertices)
                    if(s1 != s2)
                        if(s1[s1.Length-3] == s2[0] && s1[s1.Length-2] == s2[1] && s1[s1.Length-1] == s2[2])
            {
                int v1 = vertices.IndexOf(s1);
                int v2 = vertices.IndexOf(s2);
                graph[v1].Add(v2);
            }
            return graph;
        }
        static string Translate(List<int> HamiltonianCycle)
        {
            string a = "";
            foreach(int b in HamiltonianCycle)
                a += vertices[b] + " -> ";
            return a;
        }
    }
}

Second file:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Graph
{
    partial class Program
    {
        static List<int>[] graph, oppositeGraph;
        static List<int> HamiltonianCycle;
        static bool endOfAlgorithm;
        static int level, v1, v2;
        static List<int> Algorithm(List<int>[] graphArgument)
        {
            graph = SaveGraph(graphArgument);
            HamiltonianCycle = new List<int>();
            endOfAlgorithm = false;
            level = 0;
            RemoveMultipleEdgesAndLoops(graph); //3.1
            CreateOppositeGraph(graph);
            bool HamiltonianCycleCantExist = AnalyzeGraph(new List<Edge>()); //6.1.a
            ReverseGraph();
            if (!HamiltonianCycleCantExist)
                FindHamiltonianCycle(GetNextVertex()); //5.3
            HamiltonianCycle.Reverse();
            return HamiltonianCycle;
        }
        static void ReverseGraph()
        {
            graph = SaveGraph(oppositeGraph);
            CreateOppositeGraph(graph);
        }
        static void FindHamiltonianCycle(int a)
        {
            if (!endOfAlgorithm)
            {
                ++level;
                if (HamiltonianCycleFound())
                    endOfAlgorithm = true;
                SortList(a); //5.4
                while (graph[a].Count > 0 && !endOfAlgorithm)
                {
                    List<Edge> removedEdges = new List<Edge>();
                    int chosenVertex = graph[a][0];
                    graph[a].Remove(chosenVertex);
                    List<int>[] currentGraph = SaveGraph(graph);
                    #region 6.2
                    foreach (int b in graph[a])
                    {
                        removedEdges.Add(new Edge(a, b));
                        oppositeGraph[b].Remove(a);
                    }
                    graph[a].Clear();
                    #endregion
                    graph[a].Add(chosenVertex);
                    v1 = a;
                    v2 = chosenVertex;
                    bool HamiltonianCycleCantExist = AnalyzeGraph(removedEdges); //6.1.b
                    if (!HamiltonianCycleCantExist)
                    {
                        FindHamiltonianCycle(GetNextVertex()); //5.5
                        RestoreGraphs(currentGraph); //6.4
                    }
                    else
                    {
                        foreach (Edge e in removedEdges) //6.3
                        {
                            graph[e.from].Add(e.to);
                            oppositeGraph[e.to].Add(e.from);
                        }
                        RemoveEdge(new Edge(a, chosenVertex), graph, oppositeGraph);
                    }
                }
                if (!endOfAlgorithm)
                {
                    --level;
                    if (level == 0)
                        endOfAlgorithm = true;
                }
            }
        }
        static bool HamiltonianCycleFound()
        {
            foreach (List<int> list in graph)
                if (list.Count != 1)
                    return false;
            HamiltonianCycle = GetHamiltonianCycle(graph);
            return true;
        }
        static List<int> GetHamiltonianCycle(List<int>[] graphArgument)
        {
            List<int> cycle = new List<int>() { 0 };
            while (true)
            {
                if (cycle.Count == graphArgument.Length && graphArgument[cycle.Last()].Contains(cycle[0]))
                    return cycle;
                if (cycle.Contains(graphArgument[cycle.Last()][0]))
                    return new List<int>();
                else
                    cycle.Add(graphArgument[cycle.Last()][0]);
            }
        }
        static int GetNextVertex()
        {
            List<int> correctOrder = GetCorrectOrder(graph);
            foreach (int a in correctOrder)
                if (graph[a].Count != 1)
                    return a;
            return 0;
        }
        static bool AnalyzeGraph(List<Edge> removedEdges)
        {
            bool HamiltonianCycleCantExist = false;
            int a;
            do
            {
                a = removedEdges.Count;
                HamiltonianCycleCantExist = RemoveUnnecessaryEdges(graph, oppositeGraph, removedEdges, false);
                if (!HamiltonianCycleCantExist)
                    HamiltonianCycleCantExist = RemoveUnnecessaryEdges(oppositeGraph, graph, removedEdges, true);
            }
            while (a != removedEdges.Count && !HamiltonianCycleCantExist);
            if (!HamiltonianCycleCantExist)
                HamiltonianCycleCantExist = GraphIsDisconnected(graph);
            return HamiltonianCycleCantExist;
        }
        static bool RemoveUnnecessaryEdges(List<int>[] graphArgument, List<int>[] oppositeGraphArgument, List<Edge> removedEdges, bool oppositeGraph)
        {
            bool HamiltonianCycleCantExist = false;
            for (int a = 0; a < graphArgument.Length; ++a)
            {
                if (graphArgument[a].Count == 0 //4.1
                    || (graphArgument[a].Count == 1 && SearchForCycleAmongVerticesOfDegreeEqual1(graphArgument, a)) //4.2.1
                    || (graphArgument[a].Count > 1 && SearchForCycleAmongVerticesOfDegreeGreaterThan1(a, graphArgument, oppositeGraphArgument))) //4.2.2
                    return true;
                List<Edge> edges = new List<Edge>();
                #region 3.2
                if (graphArgument[a].Count == 1 && oppositeGraphArgument[graphArgument[a][0]].Count != 1)
                {
                    foreach (int c in oppositeGraphArgument[graphArgument[a][0]])
                        if (c != a)
                            if (!oppositeGraph)
                                edges.Add(new Edge(c, graphArgument[a][0]));
                            else
                                edges.Add(new Edge(graphArgument[a][0], c));
                }
                #endregion
                #region 3.4
                if (graphArgument[a].Count == 1 && graphArgument[graphArgument[a][0]].Contains(a))
                {
                    if (!oppositeGraph)
                        edges.Add(new Edge(graphArgument[a][0], a));
                    else
                        edges.Add(new Edge(a, graphArgument[a][0]));
                }
                #endregion
                foreach (Edge edge in edges)
                {
                    removedEdges.Add(edge);
                    if (!oppositeGraph)
                        RemoveEdge(edge, graphArgument, oppositeGraphArgument);
                    else
                        RemoveEdge(edge, oppositeGraphArgument, graphArgument);
                }
            }
            return HamiltonianCycleCantExist;
        }
        static bool SearchForCycleAmongVerticesOfDegreeEqual1(List<int>[] graphArgument, int a)
        {
            if(!(a==v1 || a == v2))
                return false;
            List<int> cycle = new List<int>() { a };
            while (true)
                if (graphArgument[cycle.Last()].Count == 1 && cycle.Count < graphArgument.Length)
                    if (cycle.Contains(graphArgument[cycle.Last()][0]))
                        return true;
                    else
                        cycle.Add(graphArgument[cycle.Last()][0]);
                    else
                        return false;
        }
        static bool SearchForCycleAmongVerticesOfDegreeGreaterThan1(int a, List<int>[] graphArgument, List<int>[] oppossiteGraphArgument)
        {
            if (!ListsAreEqual(graphArgument[a], oppossiteGraphArgument[a], true))
                return false;
            int b = 1;
            for (int c = 0; c < graphArgument.Length && graphArgument.Length - c > graphArgument[a].Count - b; ++c)
            {
                if (c == a)
                    continue;
                if (ListsAreEqual(graphArgument[c], graphArgument[a], false) && ListsAreEqual(graphArgument[c], oppossiteGraphArgument[c], true))
                    ++b;
                if (b == graphArgument[a].Count)
                    return true;
            }
            return false;
        }
        static bool ListsAreEqual(List<int> firstList, List<int> secondList, bool EqualCount)
        {
            if (EqualCount && firstList.Count != secondList.Count)
                return false;
            foreach (int a in firstList)
                if (!secondList.Contains(a))
                    return false;
            return true;
        }
        static void SortList(int a)
        {
            List<int> correctOrder = GetCorrectOrder(oppositeGraph);
            for (int b = 1; b < graph[a].Count; ++b)
                for (int c = 0; c < graph[a].Count - 1; ++c)
                    if (correctOrder.IndexOf(graph[a][c]) > correctOrder.IndexOf(graph[a][c + 1]))
            {
                int n = graph[a][c];
                graph[a][c] = graph[a][c + 1];
                graph[a][c + 1] = n;
            }
        }
        static List<int> GetCorrectOrder(List<int>[] graphArgument) //5.1
        {
            Dictionary<int, int> vertices = new Dictionary<int, int>();
            List<int> order = new List<int>();
            for (int a = 0; a < graphArgument.Length; ++a)
                vertices.Add(a, graphArgument[a].Count);
            IEnumerable<int> v = from pair in vertices orderby pair.Value ascending select pair.Key;
            foreach (int a in v)
                order.Add(a);
            return order;
        }
        static void RemoveEdge(Edge e, List<int>[] graphArgument, List<int>[] oppositeGraphArgument)
        {
            graphArgument[e.from].Remove(e.to);
            oppositeGraphArgument[e.to].Remove(e.from);
        }
        static void RemoveMultipleEdgesAndLoops(List<int>[] graphArgument)
        {
            for (int a = 0; a < graphArgument.Length; ++a)
            {
                graphArgument[a] = graphArgument[a].Distinct().ToList();
                graphArgument[a].Remove(a);
            }
        }
        static void CreateOppositeGraph(List<int>[] graphArgument)
        {
            oppositeGraph = new List<int>[graphArgument.Length];
            for (int a = 0; a < graphArgument.Length; ++a)
                oppositeGraph[a] = new List<int>();
            for (int a = 0; a < graphArgument.Length; ++a)
                foreach (int b in graphArgument[a])
                    oppositeGraph[b].Add(a);
        }
        static void RestoreGraphs(List<int>[] graphArgument)
        {
            graph = new List<int>[graphArgument.Length];
            for (int a = 0; a < graphArgument.Length; ++a)
            {
                graph[a] = new List<int>();
                graph[a].AddRange(graphArgument[a]);
            }
            CreateOppositeGraph(graph);
        }
        static List<int>[] SaveGraph(List<int>[] graphArgument)
        {
            List<int>[] savedGraph = new List<int>[graphArgument.Length];
            for (int a = 0; a < graphArgument.Length; ++a)
            {
                savedGraph[a] = new List<int>();
                savedGraph[a].AddRange(graphArgument[a]);
            }
            return savedGraph;
        }
        static bool GraphIsDisconnected(List<int>[] graphArgument)
        {
            Stack<int> stack = new Stack<int>();
            Color[] colors = new Color[graphArgument.Length];
            colors[0] = Color.Gray;
            stack.Push(0);
            while (stack.Count > 0)
            {
                int a = stack.Pop();
                foreach (int b in graphArgument[a])
                {
                    if (colors[b] == Color.White)
                    {
                        colors[b] = Color.Gray;
                        stack.Push(b);
                    }
                }
                colors[a] = Color.Black;
            }
            foreach (Color c in colors)
                if (c != Color.Black)
                    return true;
            return false;
        }
    }
    class Edge
    {
        public int from, to;
        public Edge(int f, int t)
        {
            from = f;
            to = t;
        }
    }
    enum Color { White, Gray, Black };
}

I found Hamilonian cycle with modified version of my algorithm: http://arxiv.org/abs/1405.6347 Modifications that were made are:

  • Algorithm searched in opposite graph
  • Algorithm tested if graph is disconnected
  • Algorithm did not test "unique neighbours" rule
  • Algorithm searched for cycles that are not Hamiltonian, starting only from vertices that creates currently visited edge - only in function SearchForCycleAmongVerticesOfDegreeEqual1
1

Well, calculating Hamilton cycle is actually NP-complete problem. So there is no fast (i.e. polynomial time) algorithm.

You can find more information here: http://mathworld.wolfram.com/HamiltonianCycle.html

Some Monte Carlo algorithms would probably work here (and maybe not give you always right answer) - so I would search there, but don't expect miracles. It's still NP-complete problem.

Esse
  • 3,220
  • 2
  • 19
  • 24
  • I believe that it depends on graph type. For instance De Bruijn graphs, solution is deterministic and very fast see here: http://stackoverflow.com/questions/4008603/how-to-compute-de-bruijn-sequences-for-non-power-of-two-sized-alphabets – Mehmet Fide Oct 27 '14 at 21:19
  • No, you're confusing two types of path: Eulerian path and Hamiltonian path. In linked post, Eulerian path is mentioned which is P. Hamiltonian, however, isn't easy to calculate. – Esse Oct 27 '14 at 21:22
  • No it is exactly visiting each vertices once see http://en.wikipedia.org/wiki/De_Bruijn_sequence – Mehmet Fide Oct 27 '14 at 21:27
  • "The De Bruijn sequences can be constructed by taking a Hamiltonian path of an n-dimensional De Bruijn graph over k symbols (or equivalently, a Eulerian cycle of a (n − 1)-dimensional De Bruijn graph)" – Esse Oct 27 '14 at 21:28
  • my graph is similar to De Bruijn's but all sequences in a window are unique. For example in De Bruijn graph, AAA can go to AAB while I don't have AAA or AAB. – Mehmet Fide Oct 27 '14 at 21:34
  • 1
    Well, I'm not sure (I have practically zero knowledge about De Bruijn sequences) but I think best way for you would by: to try to avoid Hamiltonian path and find equivalent Eulerian one. – Esse Oct 27 '14 at 21:53
  • NP-completeness is about decision problems. Here, we know that a Hamiltonian cycle always exists. So it is not clear what decision problem you are referring to when you say it is NP-complete. – Falk Hüffner Oct 28 '14 at 08:35
  • I believe that algorith that finds Hamiltonian cycle in graph in polynomial time don't exist. – Esse Oct 28 '14 at 18:12