public boolean isTree() {
        if (getVertices().isEmpty()) {
            return true;
        }
        Set<V> roots = getUnreachableVertices();
        if (roots.size() != 1) {
            return false;
        }
        V root = roots.iterator().next();
        // no self-loop at root
        if (inDegree(root) != 0) {
            return false;
        }
        Set<V> visitedVertices = new HashSet<V>();
        visitedVertices.add(root);
        List<V> currVertices = new ArrayList<V>();
        currVertices.addAll(getOutNeighbours(root));

        // bfs and checks that you visit each vertex only once
        while (!currVertices.isEmpty()) {
            List<V> nextVertices = new ArrayList<V>();
            for (V currV : currVertices) {
                if (visitedVertices.contains(currV)) {
                    return false;
                } else {
                    if (inDegree(currV) != 1 || containsEdge(currV, currV)) {
                        return false;
                    }
                    visitedVertices.add(currV);
                }
                nextVertices.addAll(getOutNeighbours(currV));
            }
            currVertices = nextVertices;
        }
        return visitedVertices.equals(getVertices());
    }