diff --git a/markdown/3-Solving-Problems-By-Searching/README.md b/markdown/3-Solving-Problems-By-Searching/README.md index 314fbcc024..35af74f704 100644 --- a/markdown/3-Solving-Problems-By-Searching/README.md +++ b/markdown/3-Solving-Problems-By-Searching/README.md @@ -26,7 +26,7 @@ | 22 | Unanswered | [`Question`](exercises/ex_22/question.md)| | 23 | Unanswered | [`Question`](exercises/ex_23/question.md)| | 24 | Unanswered | [`Question`](exercises/ex_24/question.md)| -| 25 | Unanswered | [`Question`](exercises/ex_25/question.md)| +| 25 | answered | [`Question`](exercises/ex_25/question.md)| | 26 | Unanswered | [`Question`](exercises/ex_26/question.md)| | 27 | Unanswered | [`Question`](exercises/ex_27/question.md)| | 28 | Unanswered | [`Question`](exercises/ex_28/question.md)| diff --git a/markdown/3-Solving-Problems-By-Searching/exercises/ex_25_Solution/answer01.md b/markdown/3-Solving-Problems-By-Searching/exercises/ex_25_Solution/answer01.md new file mode 100644 index 0000000000..e5079d3383 --- /dev/null +++ b/markdown/3-Solving-Problems-By-Searching/exercises/ex_25_Solution/answer01.md @@ -0,0 +1,31 @@ +--- +layout: exercise +title: Answers to Exercise 3.25 +permalink: /search-exercises/ex_25/answers/ +breadcrumb: 3-Solving-Problems-By-Searching +canonical_id: ch3ex25_ans +--- + +{% include mathjax_support %} + +## Answers + +### 1. Breadth-First Search as a Special Case of Uniform-Cost Search + +Breadth-first search (BFS) can be seen as a special case of uniform-cost search (UCS) when all edge costs are equal. In UCS, the algorithm expands the least-cost node, but if all costs are the same, it effectively behaves like BFS, expanding nodes in the order they were added. + +- **Proof**: In BFS, each level of the search tree is expanded one level at a time, where each level corresponds to nodes that are equally distant from the root node. In UCS, nodes are expanded based on cumulative cost, and if all edges have equal cost, the cumulative cost is directly proportional to the depth of the node. Hence, UCS will expand nodes in the same order as BFS when edge costs are uniform. + +### 2. Depth-First Search as a Special Case of Best-First Tree Search + +Depth-first search (DFS) can be interpreted as a special case of best-first tree search if the evaluation function used always prefers deeper nodes. + +- **Proof**: In DFS, nodes are expanded by going as deep as possible down one branch before backtracking. This behavior can be mimicked in a best-first tree search by using an evaluation function that prioritizes nodes with greater depth (negative depth as priority). This ensures that the search always proceeds to the deepest unexplored node, effectively replicating the behavior of DFS. + +### 3. Uniform-Cost Search as a Special Case of A* Search + +Uniform-cost search (UCS) can be viewed as a special case of A* search where the heuristic function \( h(n) = 0 \) for all nodes \( n \). + +- **Proof**: A* search algorithm expands nodes based on the evaluation function \( f(n) = g(n) + h(n) \), where \( g(n) \) is the cost from the start node to node \( n \), and \( h(n) \) is the heuristic estimate from \( n \) to the goal. When \( h(n) = 0 \), the evaluation function simplifies to \( f(n) = g(n) \), which is exactly the same as the cost criterion used in UCS. Thus, A* with a zero heuristic behaves identically to UCS. + +These proofs demonstrate the relationship between the different search algorithms and show how specific conditions in one algorithm can replicate the behavior of another. diff --git a/markdown/3-Solving-Problems-By-Searching/exercises/ex_38_solution/answer01.md b/markdown/3-Solving-Problems-By-Searching/exercises/ex_38_solution/answer01.md new file mode 100644 index 0000000000..5730f61820 --- /dev/null +++ b/markdown/3-Solving-Problems-By-Searching/exercises/ex_38_solution/answer01.md @@ -0,0 +1,114 @@ +--- +layout: exercise +title: Answers to Exercise 3.38 +permalink: /search-exercises/ex_38/answers/ +breadcrumb: 3-Solving-Problems-By-Searching +canonical_id: ch3ex38_ans +--- + +{% include mathjax_support %} + +## Answers + +### 1. Deriving the MST Heuristic from a Relaxed Version of the TSP + +The MST heuristic for the TSP is derived from a relaxed version of the problem where instead of finding a tour (a path that visits all cities and returns to the start), we only find a minimum-spanning tree (MST) that connects all cities. + +To derive the MST heuristic: +- **Relaxation**: Consider the problem where we need to connect all cities but not necessarily return to the starting city. This relaxed version is equivalent to finding an MST of the graph formed by the cities and their pairwise distances. +- **Heuristic Derivation**: The cost of the MST provides a lower bound on the cost of the optimal TSP tour. This is because any valid tour must span all the cities and hence must include at least the edges of the MST, which is the minimum sum of edges connecting all cities. Thus, the MST cost is used as an estimate or heuristic for the TSP. + +### 2. Showing that the MST Heuristic Dominates Straight-Line Distance + +To show that the MST heuristic dominates the straight-line distance heuristic: +- **Straight-Line Distance Heuristic**: This heuristic estimates the tour cost by assuming a straight-line path between cities. It is typically less accurate because it ignores the actual network of paths and only considers the direct Euclidean distance. +- **Comparison**: The MST heuristic dominates the straight-line distance heuristic because: + - The MST heuristic is guaranteed to be at least as large as the straight-line distance between cities (considering any optimal tour will involve distances that are at least as large as the MST). + - The MST heuristic provides a lower bound on the tour cost, while the straight-line distance might underestimate the cost significantly by ignoring the network of paths that would be used in a true tour. + +### 3. Problem Generator for Random TSP Instances + +To generate instances of the TSP where cities are represented by random points in the unit square: + +```python +import numpy as np + +def generate_random_tsp(num_cities): + """ + Generates a TSP instance with cities as random points in the unit square. + + Parameters: + num_cities (int): Number of cities to generate. + + Returns: + np.array: A matrix where element [i, j] represents the distance between city i and city j. + """ + # Generate random points in the unit square + points = np.random.rand(num_cities, 2) + + # Compute the distance matrix + distances = np.sqrt(np.sum((points[:, np.newaxis] - points)**2, axis=2)) + + return distances +``` + +### 4. Efficient Algorithm for Constructing the MST + +One efficient algorithm for constructing the MST is Kruskal's Algorithm. This algorithm can be used as follows: + +1. Sort: Sort all edges of the graph in ascending order of their weights. +2. Union-Find: Use a union-find data structure to add edges to the MST, ensuring no cycles are formed. +3. Construct MST: Add edges to the MST in increasing order of their weight until the MST spans all vertices. + +##### Implementation in Python: +```python +import numpy as np + +def kruskal_mst(distances): + """ + Computes the MST of a graph using Kruskal's algorithm. + + Parameters: + distances (np.array): The distance matrix representing the graph. + + Returns: + float: Total weight of the MST. + """ + num_cities = len(distances) + edges = [(distances[i, j], i, j) for i in range(num_cities) for j in range(i + 1, num_cities)] + edges.sort() + + parent = list(range(num_cities)) + rank = [0] * num_cities + + def find(u): + if parent[u] != u: + parent[u] = find(parent[u]) + return parent[u] + + def union(u, v): + root_u = find(u) + root_v = find(v) + if root_u != root_v: + if rank[root_u] > rank[root_v]: + parent[root_v] = root_u + elif rank[root_u] < rank[root_v]: + parent[root_u] = root_v + else: + parent[root_v] = root_u + rank[root_u] += 1 + + mst_weight = 0 + for weight, u, v in edges: + if find(u) != find(v): + union(u, v) + mst_weight += weight + + return mst_weight +``` + +## Conclusion + +- The MST heuristic provides a useful approximation for the TSP by estimating the lower bound of the tour cost. +- It dominates the straight-line distance heuristic due to its consideration of network paths. +- The provided problem generator and MST algorithm can be used to generate TSP instances and solve them efficiently. diff --git a/markdown/3-Solving-Problems-By-Searching/exercises/ex_6_Solution/answers_01.md b/markdown/3-Solving-Problems-By-Searching/exercises/ex_6_Solution/answers_01.md new file mode 100644 index 0000000000..8e727b9789 --- /dev/null +++ b/markdown/3-Solving-Problems-By-Searching/exercises/ex_6_Solution/answers_01.md @@ -0,0 +1,208 @@ +--- +Name: Udit Kumar Nayak +Email: uditkumar.23bcs10059@ms.sst.scaler.com +--- +## Intro +1. The state of the problem are number of missionaries and cannibals on each side of the river +This is why i made a structure that takes these data into account. (not sure about the implementation +of the actions though, because i have to filter it two times) + +2. Solve1 is a custom graph-search BFS, i found that the general implementation was present +at [this](https://github.com/aimacode/aima-python/blob/master/search.py) repo, but it was to late +so i just used solve2 to see a DFS. + +3. Probably because people tend to be frustrated by hard problems and make the same error +over and over again + +## Implementation +### Program solution +```python +# search is the module here +# https://github.com/aimacode/aima-python/blob/master/search.py +from search import Problem, Node, depth_first_graph_search +from queue import Queue + +SHIP_SIZE = 2 + +class RiverSide(): + """ + Structure representing the people on one side of the river + """ + + def __init__(self, missionary=0, cannibals=0, tuple=None): + """ + tuple(missionaries, cannibals) on one riverside + """ + if tuple == None: + # security check + if missionary < 0 or cannibals < 0: + raise ValueError("Coudn't have negative missionaries or cannibals") + + self.missionaries = missionary + self.cannibals = cannibals + else: + # security check + if tuple[0] < 0 or tuple[1] < 0: + raise ValueError("Coudn't have negative missionaries or cannibals") + + self.missionaries = tuple[0] + self.cannibals = tuple[1] + self.shipSize = SHIP_SIZE + + def isValid(self): + """ + Check if current state is valid + """ + # check if one or more missionaire is overwhelmed by a cannibal + if self.cannibals > self.missionaries and self.missionaries > 0: + return False + + return True + + def set(self, tuple): + self.missionaries = tuple[0] + self.cannibals = tuple[1] + + def action(self): + """ + Returns possible ship transportations, by ship size. + """ + # Tuples of (missionary, cannibals) + actions = list() + for i in range(self.missionaries + 1): + for j in range(self.cannibals + 1): + if i + j != 0 and i + j <= self.shipSize: + actions.append((i,j)) + + return actions + + def __eq__(self, other): + if self.missionaries == other.missionaries and self.cannibals == other.cannibals: + return True + else: + return False + + def __sub__(self, other): + missionaries = self.missionaries - other.missionaries + cannibals = self.cannibals - other.cannibals + if missionaries < 0 or cannibals < 0: + raise ValueError("Coudn't have negative missionaries or cannibals") + return RiverSide(missionaries, cannibals) + + def __add__(self, other): + missionaries = self.missionaries + other.missionaries + cannibals = self.cannibals + other.cannibals + return RiverSide(missionaries, cannibals) + + def __str__(self): + return f"Riverside with {self.missionaries} missionaries and {self.cannibals} cannibals" + + def __repr__(self): + return f"{self.missionaries} {self.cannibals}" + + def __hash__(self): + return hash((self.missionaries, self.cannibals)) + +class Mc(Problem): + def __init__(self, initial): + self.state = (RiverSide(initial, 0), RiverSide(0, initial)) + # i needed this to make solve2 work, compatibility stuff + self.initial = self.state + self.goalState = (RiverSide(0, initial), RiverSide(initial, 0)) + + def actions(self, state): + """ + The second member of the tuple is direction, 0 is from left to right, 1 is from right + to left, the first is the possible ship setup by missionaries and cannibals. + Data like this: + ((missionaries, cannibals), 0) + """ + act = list() + for sideAction in state[0].action(): + act.append(((sideAction), 0)) + + for sideAction in state[1].action(): + act.append(((sideAction), 1)) + + return act + + def result(self, state, action): + sideAction, code = action + sideAction = RiverSide(tuple=sideAction) + leftRiver, rightRiver = state + + if code == 0: + state = (leftRiver - sideAction, rightRiver + sideAction) + else: + state = (leftRiver + sideAction, rightRiver - sideAction) + + # check if these results are valid: + # print(state, f"and valid check is { state[0].isValid() and state[1].isValid()}") + if state[0].isValid() and state[1].isValid(): + return state + else: + return (leftRiver, rightRiver) + + def goal_test(self, state): + if state == self.goalState: + return True + + return False + + def solve(self): + """ + solving using BFS + """ + # initial parameters + frontier = Queue() + explored = set() + + currentNode = Node(self.state) + frontier.put(currentNode) + explored.add(currentNode) + + while True: + if frontier.empty(): + print("no solution") + return False + currentNode = frontier.get() + if self.goal_test(currentNode.state): + print("found solution") + print(currentNode.solution()) + return True + explored.add(currentNode) + + for node in currentNode.expand(self): + if node not in explored: + frontier.put(node) + + def solve2(self): + solNode = depth_first_graph_search(self) + if solNode != None: + print(solNode.solution()) + +def main(): + # REGION TEST + # print("hello, these are some tests") + # print(RiverSide(0,0) == RiverSide(0,1)) + + # # test __init__ clas + # a = RiverSide(1,1) + # b = RiverSide(tuple=(1,2)) + + # # test string rapresentation + # print(a) + # print(b) + + # # addition and subtraction + # print(a + RiverSide(2, 3)) + # print(a - RiverSide(0, 1)) + # ENDREGION + + # testing problem and solving it + probbi = Mc(3) + probbi.solve2() + +if __name__ == "__main__": + main() +``` \ No newline at end of file