Search code examples
javatreeminimum-spanning-treespanning-tree

Java Network/Tree simulations goes to infinite loop after certain amount of Nodes


I hope someone can help with the issue I am having. First of all, I tried to remove as much code as I can that wasn't causing the issue.

My problem is this: When I run the program everything runs perfectly until I create a graph with about 130 nodes. Once it hits 130+ nodes the program will run forever in an infinite loop.

I try running the program with 135 nodes at 15 for the desired graph density.

To give some context I am working on research simulations and for this I am creating random graphs and building spanning trees using BFS.

My problem arises during the creation of the Spanning Tree.

Copy and paste code and compile using javac MMB.java all in one file.

import java.util.*;

/**
 *  Custom type Set used to differentiate nodes in the FAST and SLOW sets
*/
enum Set {
    FAST, SLOW;
}

/**
 *  Custom node class used to store our spanning tree
*/
class Node<T> {

    private T data;
    private Node<T> parent;
    private List<Node<T>> children;
    private int level;
    private int rank;
    private Set set;

    // constructor for root Nodes and stand alone nodes
    public Node(T data){
        this.data = data;
        this.parent = null;
        this.level = 0;
        this.rank = Integer.MIN_VALUE;
        this.children = new ArrayList<Node<T>>();
    }

    // constructor for all non root nodes
    public Node(T data, Node<T> parent){
        this.data = data;
        this.setParent(parent);
        this.level = (parent.getLevel()) + 1;
        this.rank = Integer.MIN_VALUE;
        this.children = new ArrayList<Node<T>>();
    }

    // get data
    public T getData(){
        return this.data;
    }

    // set data
    public void setData(T data){
        this.data = data;
    }

    // add child node
    public void addChild(Node<T> child){
        children.add(child);
    }

    // remove child node
    public void removeChild(Node<T> child){
        children.remove(child);
    }

    // get rank
    public int getRank(){
        return this.rank;
    }

    // set rank
    public void setRank(int rank){
        this.rank = rank;
    }

    // get parent
    public Node<T> getParent(){
        return this.parent;
    }

    // set parent - updates parent node to have child
    public void setParent(Node<T> parent){
        this.parent = parent;
        parent.addChild(this);
    }

    // returns level of a Node
    public int getLevel(){
        return this.level;
    }

    // returns a list of children of a given node
    public List<Node<T>> getChildren(){
        return this.children;
    }

    // set the Set a node is in
    public void setSet(Set set){
        this.set = set;
    }

    // get the Set a node is in
    public Set getSet(){
        return this.set;
    }

    // returns the tree as a list of nodes using DFS traversal
    public List<Node<T>> treeToList(){
        List<Node<T>> list = new LinkedList<Node<T>>();
        List<Node<T>> visitedNodes = new LinkedList<Node<T>>();

        list.add(this);
        while(list.size() > 0){
            Node<T> currentNode = list.get(list.size() - 1);
            List<Node<T>> currentNodesChildren = currentNode.getChildren();
            if(!visitedNodes.contains(currentNode)){
                for(Node<T> n : currentNodesChildren){
                    list.add(n);
                }
                visitedNodes.add(currentNode);
            }
            else {
                list.remove(currentNode);
            }
        }
        return visitedNodes;
    }

    // returns the number of levels in the tree
    // Note: levels start at 0
    public int numberOfLevels(){
        List<Node<T>> list = this.treeToList();
        int maxLevel = 0;
        for(Node<T> n : list)
            if(n.getLevel() > maxLevel)
                maxLevel = n.getLevel();
        return maxLevel + 1;
    }

    // returns the max rank in the tree
    public int maxRank(){
        List<Node<T>> list = this.treeToList();
        int maxRank = 0;
        for(Node<T> n : list)
            if(n.getRank() > maxRank)
                maxRank = n.getRank();
        return maxRank;
    }

    // returns a list of nodes with a given rank and level in the FAST set
    public List<Node<T>> nodeRankLevelSubset(int rank, int level){
        List<Node<T>> list = this.treeToList();
        List<Node<T>> subset = new LinkedList<Node<T>>();
        for(Node<T> n : list)
            if(n.getRank() == rank && n.getLevel() == level && n.getSet() == Set.FAST)
                subset.add(n);
        return subset;
    }

    // Print All
    public void printAll(){
        List<Node<T>> list = this.treeToList();
        for(Node<T> n : list){
            System.out.println("{");
            System.out.println(" \"data\": " + n.getData() + ",");
            System.out.println(" \"level\": " + n.getLevel() + ",");
            System.out.println(" \"rank\": " + n.getRank() + ",");
            switch(n.getSet()){
                case FAST:
                    System.out.println(" \"set\": \"FAST\"");
                    break;
                case SLOW:
                    System.out.println(" \"set\": \"SLOW\"");
                    break;
            }
            System.out.print(" \"parent\": ");
            if(n.getParent() != null)
                System.out.println(n.getParent().getData() + ",");
            else
                System.out.println(" ,");
            System.out.print(" \"children\": [");
            for(Node<T> cn : n.getChildren()){
                System.out.print(cn.getData() + ",");
            }
            System.out.println("]\n}");
        }
    }
    // BFS to print
    public void printTree(){
        List<Node<T>> discoveredNodes = new LinkedList<Node<T>>();
        List<Node<T>> queue = new LinkedList<Node<T>>();
        List<Node<T>> children;
        Node<T> currentNode;
        queue.add(this);
        discoveredNodes.add(this);
        while(queue.size() > 0){
            currentNode = queue.remove(0);
            children = currentNode.getChildren();
            System.out.print("\n" + currentNode.getData() + ": ");
            for(Node<T> n : children){
                queue.add(n);
                discoveredNodes.add(n);
                System.out.print(n.getData() + " " + " Rank: " + n.getRank() + " ");
            }
        }
        System.out.print("\n");
    }
}

public class MMB {
    // boolean 2D array used to make the edges in a random graph
    public static boolean[][] randomGraph;
    // custom Node class used to store our BFS spanning tree
    public static Node<Integer> spanningTree;

    public static void main(String[] args){
        int numberOfNodes, graphDensity;

        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter the desired number of Nodes: ");
        numberOfNodes = scanner.nextInt();
        System.out.print("Enter the desired graph density: ");
        graphDensity = scanner.nextInt();

        randomGraph = randomGraph(numberOfNodes, graphDensity);

        /* Print Out Graph */
        for(int i = 0; i < randomGraph.length; i++){
            System.out.print(i + " ");
            for(int j = 0; j < randomGraph.length; j++){
                System.out.printf(" " + randomGraph[i][j] + " ");
            }
            System.out.println("");
        }
        System.out.println("");
        System.out.println("HERE - CREATED GRAPH");
        spanningTree = spanningTree(0);
        System.out.println("HERE - CREATED SPAnnING TREE");
        // rankNodes(spanningTree, spanningTree.numberOfLevels());
        // System.out.println("HERE - FIRST RANK");
        // determineSet(spanningTree);
        // System.out.println("HERE - DETERMINE SET");
        // //spanningTree.printTree();
        // reRankNodes(spanningTree);
        // System.out.println("HERE - RERANK NODES");
        // //spanningTree.printTree();
        // spanningTree.printAll();
    }


    /**
     *  Create an undirected graph at random
     *  A 2D boolean array will represent the edges between nodes
     *  @param  numberOfNodes   number of nodes in the graph
     *  @param  graphDensity    integer percentage of graph density
    */
    public static boolean[][] randomGraph(int numberOfNodes, int graphDensity){
        boolean[][] graph = new boolean[numberOfNodes][numberOfNodes];
        Random randomNumber = new Random();

        boolean hasEdge;
        for(int i = 0; i < numberOfNodes; i++){
            hasEdge = false;
            for(int j = 0; j < numberOfNodes; j++){
                // i != j ensures no loops
                if(i != j && (randomNumber.nextInt(100) + 1) < graphDensity){
                    graph[i][j] = true;
                    graph[j][i] = true;
                    hasEdge = true;
                }
            }
            // to ensure no disconnected nodes, keep track with hasEdge
            // if no edge exists create a random one
            int randomNum;
            while(!hasEdge){
                if((randomNum = randomNumber.nextInt(numberOfNodes)) != i){
                    graph[i][randomNum] = true;
                    graph[randomNum][i] = true;
                    hasEdge = true;
                }
            }
        }
        return graph;
    }


    /**
     *  Create a Spanning Tree from an undirected graph using BFS
     *  A custom Node structure will represent our spanning tree
     *  @param  root    root of undirected graph from 0 to numberOfNodes-1
    */
    public static Node<Integer> spanningTree(int root){
        Node<Integer> tree = new Node<Integer>(root);
        Node<Integer> currentNode;
        Integer currentNodeData;

        LinkedList<Node<Integer>> discoveredNodes = new LinkedList<Node<Integer>>();
        LinkedList<Node<Integer>> queue = new LinkedList<Node<Integer>>();

        queue.add(tree);
        discoveredNodes.add(tree);
        while(queue.size() > 0){
            currentNode = queue.removeFirst();
            currentNodeData = currentNode.getData();
            for(int i = 0; i < randomGraph.length; i++){
                if(randomGraph[currentNodeData][i] && !listContainsNode(discoveredNodes, i)){
                    Node<Integer> newNode = new Node<Integer>(i, currentNode);
                    queue.add(newNode);
                    discoveredNodes.add(newNode);
                }
            }
        }
        return tree;
    }

    /* Helper Methods */
    // search a list of Nodes for a value
    public static boolean listContainsNode(List<Node<Integer>> list, Integer data){
        for(Node<Integer> n : list)
            if(n.getData() == data)
                return true;
        return false;
    }
}

Solution

  • In cauda venenum

    /* Helper Methods */
    // search a list of Nodes for a value
    public static boolean listContainsNode(List<Node<Integer>> list, Integer data){
        for(Node<Integer> n : list)
            if(n.getData() == data)  // <-- Can you spot the bug?
                return true;
        return false;
    }
    

    The problem is that you compare Integer's with ==. With numbers lower than 128 it works because integers are interned, but then the comparison on objects doesn't work anymore.

    Just use:

    if (n.getData().equals(data))
    

    to compare node contents (and keep up writing great code ;) - it's not easy to see such quality in StackOverflow's questions)