Problem 1. Implementation of a standard hill-climbing algorithm for nqueens. You need to implement four functions, first of which is one that creates a random state get-random-state (n)
where the returned state is a list of n values, with the i-th value in the list corresponding to the (i + l)-th queen's row location in the (i + l)-th column. You should use some random number generators to do this The second function you need to implement is for computing the number of attacking queens in a given state:
compute -attacking-pairs (st at e)
The computation should be done in a way identical to what was covered in our class
With these implemented, you can proceed to implement hill-descending-n-queens (state, comp-att-pairs)
where the argument to be passed in is a state plus the function for computing the number of conflicting pairs. This should be a standard hill-climbing (in this case, descending) algorithm without random restarts nor side moves. It should return the final state by the search.
Finally, implement n-queens (n, get-rand-st, comp-att-pairs, hill-descending)
that will be called with the previous three functions as parameters. This function should try random restarts if a randomly generated state state fails to yield a feasible n-queens configuration. Note that your code should finish running in a few second for up to n — 20 queens.
Problem 2 Search algorithms. In this problem you are to implement the queue management functionality for DES, BFS, Uniform-cost, and A* best-first search. Only graph search is required. In main.py, there is an illustration of the data structure used to encode a graph, which is a dictionary indexed by numerical node index numbers from 1 to n for an n-vertex graph. For each node, a multi-type list is used to store (i) the node index (node-id), (ii) the visited flag, (iii) the parent after the cost-to-come of the node becomes final (iv) the cost-to-come for the node, (v) the heuristic value for the node, (vi) the neighbors as a list of id-cost tuples, and (vii) the id-cost tuple stored in a dictionary.
For each X where X e {DFS, BFS, UC, ASTAR}, you are to implement three functions
add_to_queue_X (node_id, parent_node_id, cost, initialize) is -queue-empty-X ( ) pop-front-X ( )
Where an item in a queue contains a tuple of the form (node-id, parent_node_id). For DFS and BFS, there is no need to maintain the cost. You need to use one or more global variables in your code to hold the queue between calls to these functions. In add-to-queue-X, the last parameter initialize is set to true only when it is called the first time in a search when the start node is inserted into the queue. You should use this chance to initialize your internal queue data structure.
For DFS and BFS there should be a deterministic path and for UC and A* , the solution should also be mostly deterministic (there may be some variance due to tie-breaking). For UC and A* with consistent heuristics, the solution path should always be optimal. Note that the optimal cost is maintained for you already; there is nothing you need to do regarding the final cost if your implementation is correct.
Keep in mind that for actual grading, we will use larger graphs with tens of nodes.
A sample run of main.py, if you have correctly implemented your side, should produce the results shown as follows.
Graph search result dump
DFS path: [1, 3, 4, 5] , cost: 9, #expansions: 4
BFS path: [1, 2, 5] , cost: 13, UC path: cost :
A* (admissible) path: A* (consistent) path:
The n—queens problem
A random state: 7,
Final state after hill—climbing : A valid solution: [2,
8, #expansions : 5
5] , cost: 9, #expansions : 4
4, 5] , cost: 8, #expansions: 5
5, 1] , conflicting pairs: 5
[4, 2, 7, 5, 2, 5, 1] , conflicting pairs: 2