/// <summary> /// performs a Branch and Bound search of the state space of partial tours /// stops when time limit expires and uses BSSF as solution /// </summary> /// <returns>results array for GUI that contains three ints: cost of solution, time spent to find solution, number of solutions found during search (not counting initial BSSF estimate)</returns> public string[] bBSolveProblem() { //This is used to give proportional advantages to the level of a state so that it would come off the queue before ones with lower levels. int priorityFactor = 10 * Cities.Length; int statesCreated = 0; int statesPruned = 0; int maxStoredStates = 0; int numBssfUpdates = 0; string[] results = new string[3]; Route = new ArrayList(); int length = Cities.Length; //This is the total cost matrix of all the cities to all of its connecting cities. double[,] costs = new double [length, length]; Stopwatch stopWatch = new Stopwatch(); //Binary min heap BinaryHeap queue = new BinaryHeap(); //This is the upperbound of the BSSF double upperBound = double.PositiveInfinity; stopWatch.Start(); /* * Iterate through the cities and fill up the total cost matrix. * Time comp: O(n^2) * Space comp: O(n^2) */ for (int row = 0; row < length; row++) { for (int col = 0; col < length; col++) { if (row == col) { costs[row, col] = double.PositiveInfinity; } else { costs[row, col] = Cities[row].costToGetTo(Cities[col]); } } } /* Do greedy algorithm starting from every city and save the best to get BSSF. * BestRoute will save the route of the best greedy solution and upperBound will save the best upperbound. * Time comp: O(n^3) because we do the greedy algorithm (O(n^2)) for every city * Space comp: O(n^2) because we only have to store the data for each iteration of the greedy algorithm */ ArrayList bestRoute = new ArrayList(); for (int city = 0; city < length; city++) { ArrayList currRoute = new ArrayList(); currRoute.Add(city); int startPosition = city; double currGreedyUpperbound = getGreedyUpperbound(city, city, costs, currRoute, length, 0); if (currGreedyUpperbound < upperBound) { upperBound = currGreedyUpperbound; bestRoute.Clear(); bestRoute = currRoute; } } //count = how many solutions we found. int count = 1; //First we make the starting state and add it to the priority queue. int[] state1_tour = new int[1]; state1_tour[0] = 0; State state0 = new State(0, 1, costs, 0, state1_tour, length); statesCreated++; queue.AddToAllNodes(state0); /* Add the first state to the priority queue. * Time comp: O(nlogn) */ queue.Add(state0, state0.lowerBound - priorityFactor * state0.level * state0.level); /* Go through the best greedy route and update the bssf object so that we have an initial BSSF to compare the starting state's children to. * Time comp: O(n) because we go through it once for every city * Space comp: O(n) can't get more than the number of cities */ foreach (int i in bestRoute) { Route.Add(Cities[i]); } bssf = new TSPSolution(Route); //If the greedy solution's upperbound is equal to the first state's lowerbound, we know that the calculated greedy solution is the optimal solution. if (upperBound == state0.lowerBound) { Console.WriteLine("States Created: " + statesCreated); Console.WriteLine("States Pruned: " + statesPruned); Console.WriteLine("Max # of Stored States at a given time: " + maxStoredStates); Console.WriteLine("Times BSSF Updated: " + numBssfUpdates); Console.WriteLine("Priority Factor: " + priorityFactor); results[COST] = costOfBssf().ToString(); results[TIME] = stopWatch.Elapsed.ToString(); results[COUNT] = count.ToString(); return(results); } //while loop that stops once the time is up or once we have been through the whole state tree // Time complexity of the branch is O(n^2 * (n-1)!) while (stopWatch.ElapsedMilliseconds < time_limit && queue.Count > 0) { //store the max number of stored states at a given time if (queue.Count > maxStoredStates) { maxStoredStates = queue.Count; } /* Grabs the smallest priority state from the queue. * Time comp: O(nlogn) */ State state = queue.ExtractMin(); //If the BSSF solution's upperbound is equal to the current state's lowerbound, we know that the calculated BSSF solution is the optimal solution. if (state.lowerBound == upperBound) { Console.WriteLine("States Created: " + statesCreated); Console.WriteLine("States Pruned: " + statesPruned); Console.WriteLine("Max # of Stored States at a given time: " + maxStoredStates); Console.WriteLine("Times BSSF Updated: " + numBssfUpdates); Console.WriteLine("Priority Factor: " + priorityFactor); results[COST] = costOfBssf().ToString(); results[TIME] = stopWatch.Elapsed.ToString(); results[COUNT] = count.ToString(); return(results); } //If the BSSF solution's upperbound is lower than the current state's lowerbound, we know that we can prune it and not expand its children. else if (state.lowerBound > upperBound) { statesPruned++; } else //Let's get the state's children! { //If we're at a leaf and its bound is less than the current best so far bound, then we make this solution the new BSSF. if (state.tour.Length == length + 1) { count++; if (state.lowerBound < upperBound) { /* Go through the best route so far and update the bssf object. * Time comp: O(n) because we go through it once for every city * Space comp: O(n) can't get more than the number of cities */ Route = new ArrayList(); for (int i = 0; i < state.tour.Length; i++) { Route.Add(Cities[state.tour[i]]); } bssf = new TSPSolution(Route); numBssfUpdates++; upperBound = state.lowerBound; } } else //Get children and add them to the queue if they are not prunable. { /* Go through all of the current state's children while also getting the tour for each child. * Time comp: O(n^2) because we calculate the tour for each children for the state * Space comp: O(n^2) because we store all the children and their tour */ for (int col = 0; col < length; col++) { double currCost = costs[state.pointsIndex, col]; if (!currCost.Equals(double.PositiveInfinity)) { //Get the tour for the child. int[] currTour = new int[state.level + 1]; for (int i = 0; i < state.tour.Length; i++) { currTour[i] = state.tour[i]; } currTour[state.level] = col; //Build a state for the child. State childState = new State(col, state.level + 1, state.cost, state.lowerBound, currTour, length); statesCreated++; queue.AddToAllNodes(childState); //If it has potential to become the BSSF, add it to the queue so that we can expand it. if (childState.lowerBound < upperBound) { /* Add the first state to the priority queue. * Time comp: O(nlogn) */ queue.Add(childState, childState.lowerBound - priorityFactor * childState.level * childState.level); } //If the BSSF solution's upperbound is lower than the current child's lowerbound, we know that we can prune it and not expand it. else if (childState.lowerBound > upperBound) { statesPruned++; } } } } } } stopWatch.Stop(); //Either we ran out of time or we definitely have the optimal solution. Return the BSSF results. Console.WriteLine("States Created: " + statesCreated); Console.WriteLine("States Pruned: " + statesPruned); Console.WriteLine("Max # of Stored States at a given time: " + maxStoredStates); Console.WriteLine("Times BSSF Updated: " + numBssfUpdates); Console.WriteLine("Priority Factor: " + priorityFactor); results[COST] = costOfBssf().ToString(); // load results into array here, replacing these dummy values results[TIME] = stopWatch.Elapsed.ToString(); results[COUNT] = count.ToString(); return(results); }
///////////////////////////////////////////////////////////////////////////////////////////// // These additional solver methods will be implemented as part of the group project. //////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// finds the greedy tour starting from each city and keeps the best (valid) one /// </summary> /// <returns>results array for GUI that contains three ints: cost of solution, time spent to find solution, number of solutions found during search (not counting initial BSSF estimate)</returns> public string[] greedySolveProblem() { string[] results = new string[3]; Route = new ArrayList(); int length = Cities.Length; //This is the total cost matrix of all the cities to all of its connecting cities. double[,] costs = new double[length, length]; Stopwatch stopWatch = new Stopwatch(); //Binary min heap BinaryHeap queue = new BinaryHeap(); //This is the upperbound of the BSSF double upperBound = double.PositiveInfinity; stopWatch.Start(); /* * Iterate through the cities and fill up the total cost matrix. * Time comp: O(n^2) * Space comp: O(n^2) */ for (int row = 0; row < length; row++) { for (int col = 0; col < length; col++) { if (row == col) { costs[row, col] = double.PositiveInfinity; } else { costs[row, col] = Cities[row].costToGetTo(Cities[col]); } } } /* Do greedy algorithm starting from every city and save the best to get BSSF. * BestRoute will save the route of the best greedy solution and upperBound will save the best upperbound. * Time comp: O(n^3) because we do the greedy algorithm (O(n^2)) for every city * Space comp: O(n^2) because we only have to store the data for each iteration of the greedy algorithm */ ArrayList bestRoute = new ArrayList(); for (int city = 0; city < length; city++) { ArrayList currRoute = new ArrayList(); currRoute.Add(city); int startPosition = city; double currGreedyUpperbound = getGreedyUpperbound(city, city, costs, currRoute, length, 0); if (currGreedyUpperbound < upperBound) { upperBound = currGreedyUpperbound; bestRoute.Clear(); bestRoute = currRoute; } } /* Go through the best greedy route and update the bssf object so that we have an initial BSSF to compare the starting state's children to. * Time comp: O(n) because we go through it once for every city * Space comp: O(n) can't get more than the number of cities */ foreach (int i in bestRoute) { Route.Add(Cities[i]); } stopWatch.Stop(); bssf = new TSPSolution(Route); results[COST] = costOfBssf().ToString(); // load results into array here, replacing these dummy values results[TIME] = stopWatch.Elapsed.ToString(); results[COUNT] = "1"; //*** should this be 1 or the number of greedy solutions that we find??? return(results); }
/// <summary> /// solve the problem. This is the entry point for the solver when the run button is clicked /// right now it just picks a simple solution. /// </summary> public void solveProblem() { Stopwatch timer = new Stopwatch(); timer.Start(); l = Cities.Length; includeMatrix = new double[l, l]; double[,] costMatrix = new double[l, l]; //make the initial cost matrix for (int i = 0; i < l; i++) { for (int j = 0; j < l; j++) { if (i == j) { costMatrix[i, j] = double.PositiveInfinity; } else { costMatrix[i, j] = Cities[i].costToGetTo(Cities[j]); } } } //use greedy algorithm to get an initial bssf int startIndex = 0; while (bssF == double.PositiveInfinity) { bssF = greedy(costMatrix, startIndex); startIndex++; } //reduce the original cost Matrix double cost = reduceMatrix(costMatrix); //--------------------------------------------------------- //put a starter node on the heap with the reduced cost and the matrix, // an empty path, and and edgeCount of 0 //---------------------------------------------------------- BinaryHeap bh = new BinaryHeap(heapSize); int[] pathArray = new int[l]; for (int i = 0; i < l; i++) { pathArray[i] = -1; } bh.insert(new Node(cost, costMatrix, pathArray, 0)); //while queue is not empty while (bh.getQueueSize() > 0) { //since we increase the number of states whether or not they are put on the queue numStatesCreated += 2; //pop the lowest cost node off the queue Node n = bh.deleteMin(); int[] nodePath = n.getPathArray(); //this should be a valid path, we successfully included enough edges if (n.getEdgeCount() == l) { //update bssf, path, bssF = n.getKey(); bestPathSoFar = n.getPathArray(); //keep track of updates numBSSFupdates++; BinaryHeap temp = new BinaryHeap(heapSize); int preCount = bh.getQueueSize(); // go through the queue and get rid of too high LB's while (bh.getQueueSize() > 0) { Node node = bh.deleteMin(); if (node.getKey() < bssF) { temp.insert(node); } bh = temp; } //when I tried to increment numStatesPruned in the while loop it was optimized out // or something, so this is where I find out how many states I pruned int postCount = bh.getQueueSize(); numStatesPruned += preCount - postCount; } else { //was not a complete path. We now go to the method that makes an include and exclude state splitDecision(n.getMatrix(), nodePath, n.getEdgeCount()); //check to see if an include improved the bssf //if not do nothing, don't put it on the queue if (((n.getKey() + maxInclude) < bssF)) { int[] includePath = new int[l]; //must make a copy of the path array since our queue might be expanding for (int i = 0; i < includePath.Length; i++) { includePath[i] = nodePath[i]; } //includes one edge includePath[maxI] = maxJ; //we must also make a copy of the cost matrix double[,] copyIncludeMatrix = new double[l, l]; if (includeMatrix != null) { for (int i = 0; i < l; i++) { for (int j = 0; j < l; j++) { copyIncludeMatrix[i, j] = includeMatrix[i, j]; } } } Node includeNode = new Node(n.getKey() + maxInclude, copyIncludeMatrix, includePath, n.getEdgeCount() + 1); bh.insert(includeNode); } else { numStatesPruned++; } //only insert the exclude state if the lower bound is less than bssf if ((n.getKey() + maxExclude < bssF) && (bh.getQueueSize() < queueLimit)) { //save some time by just using the parents' path and almost the parent's cost matrix n.getMatrix()[maxI, maxJ] = double.PositiveInfinity; reduceMatrix(n.getMatrix()); Node excludeNode = new Node(n.getKey() + maxExclude, n.getMatrix(), nodePath, n.getEdgeCount()); bh.insert(excludeNode); } else { numStatesPruned++; } } //keep track of the highest number of stored states if (bh.getQueueSize() > maxQueueSize) { maxQueueSize = bh.getQueueSize(); } //time out after 30 seconds if (timer.ElapsedMilliseconds > 30000) { numStatesPruned = numStatesCreated - bh.getQueueSize(); break; } } //------------------------------------------------------------------ //report the route and bssf just like in the demo, and time elapsed //------------------------------------------------------------------ Route = new ArrayList(); int city = 0; int count = 0; Route.Add(Cities[0]); while (count != l) { city = bestPathSoFar[city]; Route.Add(Cities[city]); count++; } // call this the best solution so far. bssf is the route that will be drawn by the Draw method. bssf = new TSPSolution(Route); // update the cost of the tour. //some values we needed for the table Program.MainForm.tbCostOfTour.Text = " " + bssf.costOfRoute() + " " + maxQueueSize + " " + numBSSFupdates + " " + numStatesCreated + " " + numStatesPruned + " "; //report the time elapsed timer.Stop(); Program.MainForm.tbElapsedTime.Text = timer.Elapsed.ToString(); // do a refresh. Program.MainForm.Invalidate(); }