Representing an adjacency list (graph) using a hash table of lists While writing a C++ implementation for weighed, labeled graphs, I started to doubt the effectiveness/correctness of my chosen structure and I would like to hear some reviews.

I tried to implement the graph as follows:

template &lt;typename T, typename K&gt; struct graph {
    unordered_map&lt; T, list&lt; pair&lt;T, K&gt; &gt; &gt; adjList;
    bool directed = 1; 
};


This allowed me to store a list of pairs (where first is the destination vertex, and second is the weight) for every vertex, and the adjacency list can be indexed by the vertex content.

So far, I was able to implement the basic operations successfully:


insertEdge()
removeEdge()
searchEdge()
printGraph() (prints all edges)


These operations behave correctly for both directed and undirected graphs.

template &lt;typename T, typename K&gt; struct graph {
    unordered_map&lt; T, list&lt; pair&lt;T, K&gt; &gt; &gt; adjList;
    bool directed = 1;
};

template &lt;typename T, typename K&gt; void reinit(graph&lt;T, K&gt;&amp; g, bool directed) 
{
    g.directed = directed;
    g.adjList.clear();
}


template &lt;typename T, typename K&gt; void insertEdge(graph&lt;T, K&gt;&amp; g, T v1, T v2, K weight)
{
    g.adjList[v1].push_front(std::make_pair(v2, weight));
    if (!g.directed)
        g.adjList[v2].push_front(std::make_pair(v1, weight));
}


template &lt;typename T, typename K&gt; void printGraph(graph&lt;T, K&gt;&amp; g)
{
    cout &lt;&lt; "Graph is " &lt;&lt; (g.directed ? "directed" : "undirected") &lt;&lt; endl;
    for (const auto&amp; v : g.adjList) //for every vertex
        for (const auto&amp; e : v.second)
            cout &lt;&lt; v.first &lt;&lt; "---" &lt;&lt; e.second &lt;&lt; "---" &lt;&lt; e.first &lt;&lt; endl;
}


template &lt;typename T, typename K&gt; K searchEdge(graph&lt;T, K&gt;&amp; g, T v1, T v2)
{   //test if edge(v1,v2) is graph
    auto l = g.adjList.find(v1);
    if (l != g.adjList.end())
        for (auto e : l-&gt;second)
            if (e.first == v2)
                return e.second;

    return numeric_limits&lt;K&gt;::min();
}


template &lt;typename T, typename K&gt; void removeEdge(graph&lt;T, K&gt;&amp; g, T v1, T v2)
{
    if (GRAPH::searchEdge(g, v1, v2) != numeric_limits&lt;K&gt;::min())
    {
        auto l = g.adjList.find(v1);
        for (auto e = l-&gt;second.begin(); e != l-&gt;second.end(); e++)
            if (e-&gt;first == v2)
            {
                l-&gt;second.erase(e);
                return;
            }
    }
}


For the most part, I found trouble implementing breadth-first search and depth-first search algorithms. If the current structure is not that great, please suggest any changes/reformulations. If it's passable, please suggest how I can approach those mentioned algorithms.
General Comments

Write your code for readability.

You don't normally see templates written like this:

template &lt;typename T, typename K&gt; void printGraph(graph&lt;T, K&gt;&amp; g)


It pushes the information you want all the way to the right. Normally, I would expect to see the template code on the first line, followed by the object it templates (looking like a normal bit of code):

template &lt;typename T, typename K&gt;
void printGraph(graph&lt;T, K&gt;&amp; g)


The space between &gt; when closing a template has not been needed in over 5 years. Let it be consigned to the big bit bucket in the sky.

 list&lt;pair&lt;T, K&gt; &gt; &gt;


I notice your lack of using std:: when declaring your types.

 unordered_map&lt; T, list


This not only means you have using namespace std; in your code, but that you have it in a header file (or you have a lot of using XXXX; but causes the same issues). The use of using namespace std; in normal code is considered bad practice. But the use of it in a header file is so bad that people will ban your code from projects. You are basically infecting any code that includes your header. This can lead to subtle, impossible-to-find errors. Don't do it.

I would also prefer if you would use an initial capital letter for type information. The most important part of C++ is the type system. Being able to identify types easily is essential. Thus, most people use an initial capital letter to identify user-defined types, while an initial lower-case letter identifies an object.

Build your types from smaller types. This will make it easier to maintain your code as it localizes changes. For example, rather than using std::pair&lt;T, K&gt; as your edge definition, prefer to use a named type Edge. That way, if you change the definition of the type, you only have to change it in a single location and not trawl through your code looking for all uses. Also, using a name like that provides context and meaning, which in turn makes your code easier to read.

template &lt;typename T, typename K&gt;
struct Graph
{
    using Edge        = std::pair&lt;T, K&gt;;
    using EdgeList    = std::list&lt;Edge&gt;;
    using AdjencyList = std::unordered_map&lt;T, EdgeList&gt;;

    AdjencyList adjList;
    bool        directed = true; // use real bool(s) not int.
};


Design Comments

You are completely ignoring encapsulation. Your functions may work, but because your data is completely exposed, there is no protection from a bad coder coming along and breaking your constraints. Your methods are supposed to be the only way to manipulate the state (so it can be controlled).

Because of this, your code would fail any competent code review and not be allowed into the code base.