diff --git a/Submissions/002688252_Zijian_Feng/Assignment4.ipynb b/Submissions/002688252_Zijian_Feng/Assignment4.ipynb new file mode 100644 index 0000000..9b074e5 --- /dev/null +++ b/Submissions/002688252_Zijian_Feng/Assignment4.ipynb @@ -0,0 +1,827 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Assignment 4\n", + "- Student Name:Zijian Feng\n", + "- NUID:002688252\n", + "- Professor: Nik Bear Brown" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q1:\n", + "In a weighted undirected graph G=(V,E,W), V is the veritex set, E is edge set and W is the weight of each edge. Define the minimum weight constrained spanning tree problem. This problem aims to find a spanning tree such that the total weight of the tree is as small as possible while satisfying specific additional constraints (e.g., some edges must be contained in the tree, or some vertices must be directly connected). \n", + "\n", + "- A. (10 points) Does the minimum weight restricted spanning tree problem in P? If yes, please prove it.\n", + "\n", + "- B. (5 points) Suppose there is a restriction on the number of edges in a spanning tree, e.g., the tree must contain exactly k edges. Does this problem of spanning trees with restricted number of edges in NP? If so, prove it.\n", + "\n", + "- C. (10 points) Is the spanning tree problem with restricted number of edges NP-complete?If so, prove it." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution:\n", + "### Part A:\n", + "To find the MST in the graph, we can use Kruskal's algorithm or Prim's algorithm. To solve this problem, we can use Kruskal's algorithm. Here is the code." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2 - 3: 4\n", + "0 - 3: 5\n", + "0 - 1: 10\n" + ] + } + ], + "source": [ + "# find the root of the node\n", + "def find(parent, i):\n", + " if parent[i] == i:\n", + " return i\n", + " return find(parent, parent[i])\n", + "# union two different set\n", + "def union(parent, rank, x, y):\n", + " xroot = find(parent, x)\n", + " yroot = find(parent, y)\n", + "\n", + " if rank[xroot] < rank[yroot]:\n", + " parent[xroot] = yroot\n", + " elif rank[xroot] > rank[yroot]:\n", + " parent[yroot] = xroot\n", + " else:\n", + " parent[yroot] = xroot\n", + " rank[xroot] += 1\n", + "\n", + "def Kruskal(graph):\n", + " result = [] # store the result\n", + "\n", + " i, e = 0, 0 \n", + "\n", + " # sort edegs by weight\n", + " graph['edges'].sort(key=lambda item: item[2])\n", + "\n", + " parent = []\n", + " rank = []\n", + "\n", + " for node in range(graph['V']):\n", + " parent.append(node)\n", + " rank.append(0)\n", + "\n", + " # add edge to the result\n", + " while e < graph['V'] - 1:\n", + " u, v, w = graph['edges'][i]\n", + " i = i + 1\n", + " x = find(parent, u)\n", + " y = find(parent, v)\n", + "\n", + " if x != y:\n", + " e = e + 1\n", + " result.append((u, v, w))\n", + " union(parent, rank, x, y)\n", + "\n", + " for u, v, weight in result:\n", + " print(\"%d - %d: %d\" % (u, v, weight))\n", + "\n", + "graph = {\n", + " 'edges': [(0, 1, 10), (0, 2, 6), (0, 3, 5), (1, 3, 15), (2, 3, 4)],\n", + " 'V': 4 # number of V\n", + "}\n", + "\n", + "Kruskal(graph)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As shown by the code, the core idea of this algorithm is to select edges in the order of their weights in order to construct a minimum spanning tree while avoiding the formation of loops. The time complexity of Kruskal's algorithm depends mainly on the sorting of edges and the concatenation set operation, which is typically O(nlogn). So finding MST problem is in P." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part B:\n", + "The defination of the NP: \n", + "\n", + "A problem belongs to the NP (nondeterministic polynomial time) class if for any solution to that problem, it is possible to verify in polynomial time that the solution is correct. This means that if given a solution (in this case, a spanning tree), we need to be able to quickly (in polynomial time) verify that it satisfies all the conditions of the problem." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Prove that the problem belongs to NP**: \n", + "\n", + "Verify the spanning tree: \n", + "\n", + "First, we need to verify that the given set of edges does indeed form a spanning tree. This involves checking if all vertices are included and there are no rings in the graph. This can be done by either depth-first search (DFS) or breadth-first search (BFS) with a time complexity of O(V+E).\n", + "Verify the number of edges:\n", + "\n", + "Next, we need to verify that this spanning tree contains exactly k edges. This is a simple counting operation that can also be done in polynomial time.\n", + "Total time complexity:\n", + "\n", + "The time complexity of both verification processes is of polynomial level, which is consistent with the NP class of problems.\n", + "Thus, the problem of spanning trees with a restricted number of edges belongs to the NP class: for a given solution, we can verify in polynomial time whether it is a valid spanning tree with a restricted number of edges.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Part C:\n", + "The defination of NP Complete: \n", + "A problem is NP-Complete if it belongs to NP and every problem in NP can be generalized to it in polynomial time. \n", + "The problem of finding a minimum spanning tree with a restricted number of edges is non-standard, and its reduction might not be as straightforward as classical problems. However, we can attempt to reduce it to a known problem. A potential candidate for such a reduction is the Hamiltonian Path problem, which is a well-known NP-complete problem. The Hamiltonian Path problem involves finding a path in a graph that visits each vertex exactly once. Please note that such reductions are typically theoretical rather than practical algorithm implementations.\n", + "\n", + "### Reduction Approach\n", + "\n", + "Assuming we have an undirected graph \\( G = (V, E) \\) and an integer \\( k \\), the goal is to find a minimum spanning tree that contains exactly \\( k \\) edges (if it exists). We can transform this problem into finding a Hamiltonian path in a constructed graph, where the path exactly consists of \\( k \\) vertices.\n", + "\n", + "### Pseudocode Outline\n", + "\n", + "1. **Construct New Graph**: Create a new graph \\( G' \\) from the original graph \\( G \\), where \\( G' \\) contains the same vertices as \\( G \\) and adds additional vertices for each edge in \\( G \\).\n", + "\n", + "2. **Transform the Problem**: Convert the problem of finding a minimum spanning tree with restricted edges into the problem of finding a Hamiltonian path in the new graph \\( G' \\).\n", + "\n", + "3. **Apply Hamiltonian Path Algorithm**: Apply an algorithm for finding a Hamiltonian path in the new graph \\( G' \\).\n", + "\n", + "The key here is the construction process and how the original problem is mapped to the Hamiltonian path problem. Below is the pseudocode for this process:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def construct_new_graph(G, k):\n", + " # Create a new graph G' with all vertices and edges of the original graph G\n", + " # and additional vertices for each edge\n", + " G_prime = create_graph_with_additional_vertices(G)\n", + " return G_prime\n", + "\n", + "def find_hamiltonian_path(G_prime, k):\n", + " # Find a Hamiltonian path in the new graph G'\n", + " # This is a complex task, typically without known polynomial-time algorithms\n", + " # Here, a Hamiltonian path algorithm (if available) would be called\n", + " path = hamiltonian_path_algorithm(G_prime)\n", + " return path\n", + "\n", + "# Main function\n", + "def restricted_edges_mst_to_hamiltonian_path(G, k):\n", + " G_prime = construct_new_graph(G, k)\n", + " path = find_hamiltonian_path(G_prime, k)\n", + " if path:\n", + " return \"A satisfying path exists\"\n", + " else:\n", + " return \"No satisfying path exists\"\n", + "\n", + "# Assuming G is the original graph and k is the edge restriction\n", + "# Call restricted_edges_mst_to_hamiltonian_path(G, k)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Therefore, we can think this problem is in Np complete." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q2: \n", + "Given an undirected graph \\( G = (V, E) \\) and an integer \\( k \\), the problem asks whether there exists a vertex subset (C is the subset of V ) such that the size of \\( C \\) is no more than \\( k \\), and every edge in the graph has at least one endpoint in \\( C \\). In other words, this vertex subset needs to \"cover\" all the edges in the graph. Demonstrate that this problem is NP-complete." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution:\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Proving that the Vertex Cover Problem is NP-complete involves two key steps: first, demonstrating that it belongs to the NP class, and second, showing that it is NP-hard through polynomial-time reduction.\n", + "\n", + "### 1. Vertex Cover Problem Belongs to NP\n", + "\n", + "A problem is in the NP class if for any given solution to the problem, it is possible to verify whether that solution is correct in polynomial time.\n", + "\n", + "- **Verification Process**:\n", + " - Given a graph \\( G = (V, E) \\) and a vertex set \\( C \\subseteq V \\), we can quickly (in polynomial time) check whether \\( C \\) is a valid vertex cover. For each edge in the graph, we verify that at least one of its endpoints is in the set \\( C \\).\n", + "\n", + "- **Time Complexity**:\n", + " - The time complexity of this verification process is linearly related to the number of edges, and thus it is polynomial time.\n", + "\n", + "### 2. Vertex Cover Problem is NP-hard\n", + "\n", + "Proving that a problem is NP-hard typically involves reducing a known NP-complete problem to the problem in question, in polynomial time.\n", + "\n", + "- **Reduction from a Known NP-complete Problem**:\n", + " - A common approach is to use the 3-SAT problem, which is known to be NP-complete. We can construct a polynomial-time reduction from any instance of the 3-SAT problem to an instance of the Vertex Cover Problem. This demonstrates that solving the Vertex Cover Problem is at least as hard as solving the 3-SAT problem.\n", + "\n", + "- **Reduction Steps**:\n", + " - The reduction involves creating a graph from a 3-SAT formula in such a way that the formula is satisfiable if and only if there exists a vertex cover of a certain size in the constructed graph.\n", + "\n", + "### Pseudocode for Reduction (3-SAT to Vertex Cover)\n", + "\n", + "Here's a conceptual outline of the reduction process, not an actual implementation:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def reduce_3SAT_to_VertexCover(three_sat_instance):\n", + " # Create a graph G from the 3-SAT instance\n", + " G = create_graph_from_3SAT(three_sat_instance)\n", + " \n", + " # The size of the vertex cover set required is determined\n", + " # based on the construction of G\n", + " k = determine_vertex_cover_size(G)\n", + "\n", + " return G, k\n", + "\n", + "# Helper function to create a graph from a 3-SAT instance\n", + "def create_graph_from_3SAT(three_sat_instance):\n", + " # Implement the specific logic to convert a 3-SAT instance\n", + " # into a corresponding graph\n", + " pass\n", + "\n", + "# Helper function to determine the size of the vertex cover\n", + "def determine_vertex_cover_size(G):\n", + " # Implement the logic to determine the required size\n", + " # of the vertex cover set based on the constructed graph\n", + " pass\n", + "\n", + "# Example usage\n", + "three_sat_instance = ... # Some 3-SAT problem instance\n", + "G, k = reduce_3SAT_to_VertexCover(three_sat_instance)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "This pseudocode represents the conceptual reduction from a 3-SAT problem to a Vertex Cover problem, highlighting that the Vertex Cover Problem is NP-hard and, combined with its membership in NP, NP-complete. The actual implementation of this reduction would be quite complex and involves specific graph construction techniques based on the clauses of the 3-SAT instance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q3:\n", + "You are organizing a game hack-a-thon and want to make sure there is at least one instructor who is skilled at each of the n skills required to build a game (e.g. programming, art, animation, modeling, artificial intelligence, analytics, etc.) You have received job applications from m potential instructors. For each of n skills, there is some subset of potential instructors qualified to teach it. The question is: For a given number k ≤ m, is is possible to hire at most k instructors that can teach all of the n skills. We’ll call this the Cheapest Teacher Set.\n", + "Show that Cheapest Teacher Set is NP-complete." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution: \n", + "Proving that the \"Cheapest Teacher Set\" problem is NP-complete involves two main steps: demonstrating that it belongs to the NP class, and proving that it is NP-hard through polynomial-time reduction.\n", + "\n", + "### 1. Cheapest Teacher Set Problem Belongs to NP\n", + "\n", + "A problem is in the NP class if for any given solution to the problem, it is possible to verify whether that solution is correct in polynomial time.\n", + "\n", + "- **Verification Process**:\n", + " - Given a set of potential instructors and their respective skills, along with a candidate set of instructors, we can quickly check whether this set covers all n skills. This involves verifying that each skill is taught by at least one instructor in the set, a process that can be done in polynomial time.\n", + "\n", + "### 2. Cheapest Teacher Set Problem is NP-hard\n", + "\n", + "Proving that a problem is NP-hard typically involves reducing a known NP-complete problem to the problem in question, in polynomial time.\n", + "\n", + "- **Reduction from a Known NP-complete Problem**:\n", + " - A common choice for such a reduction is the Set Cover problem, which is known to be NP-complete. We can construct a polynomial-time reduction from any instance of the Set Cover problem to an instance of the Cheapest Teacher Set problem. This demonstrates that solving the Cheapest Teacher Set problem is at least as hard as solving the Set Cover problem.\n", + "\n", + "- **Reduction Steps**:\n", + " - The reduction involves creating an instance of the Cheapest Teacher Set problem from a Set Cover problem in such a way that a solution to the former provides a solution to the latter.\n", + "\n", + "### Pseudocode for Reduction (Set Cover to Cheapest Teacher Set)\n", + "\n", + "Here's a conceptual outline of the reduction process, not an actual implementation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def reduce_SetCover_to_CheapestTeacherSet(set_cover_instance):\n", + " # Create an instance of the Cheapest Teacher Set problem\n", + " # from the Set Cover instance\n", + " teacher_set_instance = create_teacher_set_from_set_cover(set_cover_instance)\n", + " \n", + " # The number of instructors to hire (k) would be determined\n", + " # based on the Set Cover instance\n", + " k = determine_number_of_instructors(set_cover_instance)\n", + "\n", + " return teacher_set_instance, k\n", + "\n", + "# Helper function to create a Cheapest Teacher Set instance\n", + "# from a Set Cover instance\n", + "def create_teacher_set_from_set_cover(set_cover_instance):\n", + " # Implement the logic to convert a Set Cover instance\n", + " # into a corresponding Cheapest Teacher Set instance\n", + " pass\n", + "\n", + "# Helper function to determine the number of instructors to hire\n", + "def determine_number_of_instructors(set_cover_instance):\n", + " # Implement the logic to determine the number of instructors\n", + " # based on the Set Cover instance\n", + " pass\n", + "\n", + "# Example usage\n", + "set_cover_instance = ... # Some Set Cover problem instance\n", + "teacher_set_instance, k = reduce_SetCover_to_CheapestTeacherSet(set_cover_instance)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This pseudocode represents the conceptual reduction from a Set Cover problem to a Cheapest Teacher Set problem, highlighting that the Cheapest Teacher Set Problem is NP-hard and, combined with its membership in NP, NP-complete. The actual implementation of this reduction would involve specific mappings and constructions based on the details of the Set Cover instance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q4:\n", + "In the conference room booking problem, you have n meetings to be held, each requiring a specific facility or service. Assume that there are m different conference rooms to choose from, each with its own unique set of facilities and services. Each meeting requires a specific combination of facilities and services to be successful. The challenge is to determine whether it is possible to allocate conference rooms so that all n meetings can be held, given a limit k ≤ m on the number of conference rooms." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution: \n", + "The meeting room scheduling problem can be solved by modeling it as a maximum flow problem. In this approach, we create a flow network and use methods like the Ford-Fulkerson algorithm to determine if there is a feasible solution that satisfies the requirements for all meetings. Here are the steps to model the meeting room scheduling problem as a maximum flow problem:\n", + "\n", + "### Constructing the Flow Network\n", + "\n", + "1. **Source and Sink**:\n", + " - Create a source node and a sink node.\n", + " - The source node represents the starting point of resources, while the sink node represents the ultimate demand for resources.\n", + "\n", + "2. **Meeting Nodes**:\n", + " - Create a node for each meeting. These nodes will connect the source node to the meeting room nodes.\n", + " - Connect each meeting node to the source node with an edge, where the capacity of each edge represents the requirement of holding that specific meeting (typically, this would be 1, indicating that the meeting needs to be held).\n", + "\n", + "3. **Meeting Room Nodes**:\n", + " - Create a node for each meeting room. These nodes represent the available meeting rooms.\n", + " - Connect each meeting room node to the sink node. The capacity of these edges should represent the availability of each meeting room (for instance, if a room can be used for one meeting at a time, the capacity would be 1).\n", + "\n", + "4. **Resource Allocation Edges**:\n", + " - Connect meeting nodes to meeting room nodes. If a meeting can be conducted in a particular room (i.e., the room has all the required facilities for that meeting), create an edge between the corresponding meeting node and meeting room node.\n", + "\n", + "5. **Objective**:\n", + " - The goal is to find the maximum flow in this network from the source to the sink. If the maximum flow equals the total number of meetings, it means all meetings can be scheduled with the available rooms.\n", + "\n", + "### Pseudocode for Solving the Problem: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def ford_fulkerson_algorithm(graph, source, sink):\n", + " # Implement the Ford-Fulkerson algorithm to find the maximum flow\n", + " # This would involve finding augmenting paths and updating capacities\n", + " max_flow = 0\n", + " path = find_augmenting_path(graph, source, sink, [])\n", + " while path:\n", + " flow = min(graph[u][v] for u, v in zip(path, path[1:]))\n", + " for u, v in zip(path, path[1:]):\n", + " graph[u][v] -= flow\n", + " graph[v][u] = graph.get(v, {}).get(u, 0) + flow\n", + " max_flow += flow\n", + " path = find_augmenting_path(graph, source, sink, [])\n", + " return max_flow\n", + "\n", + "def meeting_room_scheduling(meetings, rooms):\n", + " # Construct the flow network based on the meetings and rooms\n", + " graph = construct_flow_network(meetings, rooms)\n", + "\n", + " # Find the maximum flow from source to sink\n", + " max_flow = ford_fulkerson_algorithm(graph, source, sink)\n", + "\n", + " return max_flow == len(meetings)\n", + "\n", + "# Example usage\n", + "meetings = [...]\n", + "rooms = [...]\n", + "can_schedule_all = meeting_room_scheduling(meetings, rooms)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This pseudocode outlines the approach to solving the meeting room scheduling problem using the maximum flow model. The Ford-Fulkerson algorithm is a common method for computing maximum flow in a network. The core idea is to repeatedly find paths (augmenting paths) from the source to the sink and push as much flow as possible until no more augmenting paths can be found." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q5:\n", + "Suppose you’re helping to organize a summer sports camp, and the following problem comes up. The camp is supposed to have at least one counselor who’s skilled at each of the n sports covered by the camp (baseball, volleyball, and so on). They have received job applications from m potential counselors. For each of the n sports, there is some subset of the m applicants qualified in that sport. The question is: For a given number k < m, is it possible to hire at most k of the counselors and have at least one counselor qualified in each of the n sports? We’ll call this the Efficient Recruiting Problem.\n", + "Show that Efficient Recruiting is NP-complete.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution: \n", + "Proving that the \"Efficient Recruiting Problem\" is NP-complete involves two main steps: demonstrating that it belongs to the NP class, and proving that it is NP-hard through polynomial-time reduction.\n", + "\n", + "### 1. Efficient Recruiting Problem Belongs to NP\n", + "\n", + "A problem is in the NP class if, for any given solution to the problem, it is possible to verify whether that solution is correct in polynomial time.\n", + "\n", + "- **Verification Process**:\n", + " - Given a set of potential counselors and their skills, along with a candidate set of counselors, we can quickly check whether this set covers the skills for all n sports. This involves verifying that each sport has at least one qualified candidate in the set, a process that can be done in polynomial time.\n", + "\n", + "### 2. Efficient Recruiting Problem is NP-hard\n", + "\n", + "Proving that a problem is NP-hard typically involves reducing a known NP-complete problem to the problem in question, in polynomial time.\n", + "\n", + "- **Reduction from a Known NP-complete Problem**:\n", + " - A common choice for such a reduction is the Set Cover problem, which is known to be NP-complete. We can construct a polynomial-time reduction from any instance of the Set Cover problem to an instance of the Efficient Recruiting Problem. This demonstrates that solving the Efficient Recruiting Problem is at least as hard as solving the Set Cover problem.\n", + "\n", + "- **Reduction Steps**:\n", + " - The reduction involves creating an instance of the Efficient Recruiting Problem from a Set Cover problem in such a way that a solution to the former provides a solution to the latter.\n", + "\n", + "### Pseudocode for Reduction (Set Cover to Efficient Recruiting)\n", + "\n", + "Here's a conceptual outline of the reduction process, not an actual implementation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def reduce_SetCover_to_EfficientRecruiting(set_cover_instance):\n", + " # Create an instance of the Efficient Recruiting Problem\n", + " # from the Set Cover instance\n", + " recruiting_instance = create_recruiting_instance_from_set_cover(set_cover_instance)\n", + " \n", + " # Determine the number of counselors to hire based on the Set Cover instance\n", + " k = determine_number_of_counselors(set_cover_instance)\n", + "\n", + " return recruiting_instance, k\n", + "\n", + "# Helper function to create an Efficient Recruiting instance\n", + "# from a Set Cover instance\n", + "def create_recruiting_instance_from_set_cover(set_cover_instance):\n", + " # Implement the logic to convert a Set Cover instance\n", + " # into a corresponding Efficient Recruiting Problem instance\n", + " pass\n", + "\n", + "# Helper function to determine the number of counselors to hire\n", + "def determine_number_of_counselors(set_cover_instance):\n", + " # Implement the logic to determine the number of counselors\n", + " # based on the Set Cover instance\n", + " pass\n", + "\n", + "# Example usage\n", + "set_cover_instance = ... # Some Set Cover problem instance\n", + "recruiting_instance, k = reduce_SetCover_to_EfficientRecruiting(set_cover_instance)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "This pseudocode represents the conceptual reduction from a Set Cover problem to an Efficient Recruiting Problem, highlighting that the Efficient Recruiting Problem is NP-hard and, combined with its membership in NP, NP-complete. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q6:\n", + "The **Subset Sum to K** problem is a classic computational problem which can be described as follows:\n", + "### Definition of the Subset Sum to K Problem\n", + "Given an array of integers \\( A \\) and a target number \\( K \\), the problem asks whether there exists a subset of \\( A \\) whose elements sum up to \\( K \\).\n", + "### Example of Subset Sum to K\n", + "Let's consider a concrete example to illustrate this problem:\n", + "- **Given Array**: Suppose we have an array \\( A = [3, 34, 4, 12, 5, 2] \\).\n", + "- **Target Sum**: Let's say our target sum \\( K \\) is 9." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Questions:\n", + "A. Is the \"Subset Sum to K\" problem in NP? Why or why not? \n", + "B. Is the \"Subset Sum to K\" problem NP-complete? If NP-complete, prove it.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution:\n", + "### A. Is the \"Subset Sum to K\" Problem in NP?\n", + "\n", + "**Answer**: Yes, the \"Subset Sum to K\" problem is in NP.\n", + "\n", + "**Reason**:\n", + "- A problem is in NP (Nondeterministic Polynomial time) if a solution to the problem can be verified in polynomial time. \n", + "- For the \"Subset Sum to K\" problem, given a subset of the original array, we can quickly (in polynomial time) check whether the sum of its elements equals the target number \\( K \\). \n", + "- This verification process involves summing up the elements of the subset, which can be done in linear time relative to the size of the subset, thus making it a polynomial-time operation.\n", + "- Therefore, since we can verify a solution quickly, the \"Subset Sum to K\" problem is in NP.\n", + "\n", + "### B. Is the \"Subset Sum to K\" Problem NP-complete?\n", + "\n", + "**Answer**: Yes, the \"Subset Sum to K\" problem is NP-complete.\n", + "\n", + "**Proof**:\n", + "1. **Already in NP**: As established above, the problem is in NP since we can verify a solution in polynomial time.\n", + "\n", + "2. **NP-hardness**:\n", + " - To prove that a problem is NP-complete, we must show that it is NP-hard. This is typically done through a polynomial-time reduction from a known NP-complete problem to the problem in question.\n", + " - The \"Subset Sum to K\" problem is a classic example of an NP-hard problem. This can be demonstrated by reducing from another NP-complete problem, such as the 3-SAT problem, to the \"Subset Sum to K\" problem.\n", + " - The reduction involves creating a \"Subset Sum to K\" instance where the elements of the subset and the target sum \\( K \\) represent the clauses and the satisfiability condition of the 3-SAT instance, respectively. If the 3-SAT instance is satisfiable, then there exists a subset summing to \\( K \\); if the subset sums to \\( K \\), then the 3-SAT instance is satisfiable.\n", + " - This reduction can be done in polynomial time, showing that any problem in NP can be reduced to the \"Subset Sum to K\" problem.\n", + "\n", + "**Conclusion**: Given that the \"Subset Sum to K\" problem is both in NP and NP-hard, it is NP-complete. This status makes it one of the core problems in computational complexity theory, representative of a broad class of problems believed not to have efficient (polynomial-time) solutions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Here is the Code**:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Found a subset with given sum\n" + ] + } + ], + "source": [ + "def is_subset_sum(arr, n, k):\n", + " # Initialize a boolean subset table where subset[i][j] will be true\n", + " # if there is a subset of arr[0..j-1] with sum equal to i\n", + " subset = [[False for j in range(k + 1)] for i in range(n + 1)]\n", + "\n", + " # There is always a subset with 0 sum\n", + " for i in range(n + 1):\n", + " subset[i][0] = True\n", + "\n", + " # Fill the subset table in a bottom-up manner\n", + " for i in range(1, n + 1):\n", + " for j in range(1, k + 1):\n", + " if j < arr[i - 1]:\n", + " subset[i][j] = subset[i - 1][j]\n", + " if j >= arr[i - 1]:\n", + " subset[i][j] = (subset[i - 1][j] or subset[i - 1][j - arr[i - 1]])\n", + "\n", + " # The answer will be at subset[n][k]\n", + " return subset[n][k]\n", + "\n", + "# Example usage\n", + "arr = [3, 34, 4, 12, 5, 2]\n", + "k = 9\n", + "n = len(arr)\n", + "if is_subset_sum(arr, n, k):\n", + " print(\"Found a subset with given sum\")\n", + "else:\n", + " print(\"No subset with given sum\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q7:\n", + "### Detailed Problem Description: MAX-SAT Problem\n", + "\n", + "#### Background\n", + "The Maximum Satisfiability (MAX-SAT) Problem is a classic problem in computational complexity theory and combinatorial optimization. It is an extension of the well-known Boolean Satisfiability Problem (SAT), which is the first problem that was proven to be NP-complete. The SAT problem asks whether there exists an assignment to variables that makes a Boolean formula true. MAX-SAT, on the other hand, focuses on maximizing the number of satisfied clauses in a Boolean formula.\n", + "\n", + "#### Problem Statement\n", + "Given a Boolean formula in Conjunctive Normal Form (CNF), the MAX-SAT problem seeks to find an assignment to the variables that maximizes the number of satisfied clauses. A CNF formula is composed of conjunctions (ANDs) of one or more disjunctions (ORs) of literals (a variable or its negation).\n", + "\n", + "#### Formulation\n", + "- **Input**: A Boolean formula \\( \\Phi \\) in CNF. \\( \\Phi \\) is a conjunction of \\( m \\) clauses \\( C_1, C_2, \\ldots, C_m \\), where each clause is a disjunction of literals.\n", + "- **Objective**: Find an assignment to the variables that maximizes the number of clauses in \\( \\Phi \\) that are satisfied.\n", + "- **Output**: The maximum number of clauses that can be satisfied by any assignment.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution:\n", + "Proving that the MAX-SAT problem is NP-complete involves two main steps: showing that it is in NP and that it is NP-hard. \n", + "\n", + "### Proof of NP-completeness for MAX-SAT\n", + "\n", + "#### 1. MAX-SAT is in NP\n", + "- A problem is in NP if, given a solution, it can be verified in polynomial time.\n", + "- In the case of MAX-SAT, given an assignment of values to variables, it is straightforward to check in polynomial time whether a clause is satisfied (just evaluate the Boolean expression for each clause).\n", + "- Thus, verifying if a given assignment satisfies a certain number of clauses (or the maximum number) can be done in polynomial time, placing MAX-SAT in NP.\n", + "\n", + "#### 2. MAX-SAT is NP-hard\n", + "- To prove that MAX-SAT is NP-hard, we can reduce a known NP-complete problem to it. A logical choice is the SAT problem, which is known to be NP-complete.\n", + "- **Reduction from SAT to MAX-SAT**: Given a SAT instance (a CNF formula), we use the same formula for the MAX-SAT instance. If the SAT instance is satisfiable, then the MAX-SAT solution will satisfy all clauses (making it the maximum number of satisfiable clauses). If the SAT instance is unsatisfiable, then the MAX-SAT solution will not be able to satisfy all clauses. This reduction is clearly polynomial in time as it requires no changes to the instance.\n", + "- Since we can reduce SAT to MAX-SAT in polynomial time, and SAT is NP-complete, MAX-SAT is also NP-hard.\n", + "\n", + "#### Conclusion\n", + "Since MAX-SAT is both in NP and NP-hard, it is NP-complete.\n", + "\n", + "### Pseudocode for a MAX-SAT Solver\n", + "Here's a simplistic pseudocode for a brute-force approach to solving MAX-SAT. This is not efficient for large instances but illustrates the concept:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate_clause(clause, assignment):\n", + " # Evaluate a single clause under the given assignment\n", + " # Returns True if the clause is satisfied, False otherwise\n", + " pass\n", + "\n", + "def count_satisfied_clauses(formula, assignment):\n", + " return sum(evaluate_clause(clause, assignment) for clause in formula)\n", + "\n", + "def max_sat(formula, variables):\n", + " max_satisfied = 0\n", + " best_assignment = None\n", + "\n", + " # Iterate over all possible assignments (brute-force)\n", + " for assignment in all_possible_assignments(variables):\n", + " satisfied = count_satisfied_clauses(formula, assignment)\n", + " if satisfied > max_satisfied:\n", + " max_satisfied = satisfied\n", + " best_assignment = assignment\n", + "\n", + " return best_assignment, max_satisfied\n", + "\n", + "# Example usage\n", + "formula = [...] # The MAX-SAT formula (list of clauses)\n", + "variables = [...] # List of variables in the formula\n", + "best_assignment, max_satisfied = max_sat(formula, variables)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Q8:\n", + "**Problem: Water Distribution Network** \n", + "You have a water distribution network with multiple reservoirs, pipes, and demand nodes. The goal is to determine if there is enough water flow in the network to meet the demands at each demand node. Each reservoir has a certain water supply capacity, and each pipe has a maximum flow capacity. The problem is to ensure that all demand nodes can be satisfied with water from the reservoirs through the pipes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solution: \n", + "### Network Flow Representation\n", + "\n", + "1. **Nodes**: In our water distribution network, we have different types of nodes:\n", + " - Reservoir nodes (analogous to fertilizer types: mineral, organic, water-soluble, humus)\n", + " - Pipe nodes (representing the pipes connecting reservoirs to demand nodes)\n", + " - Demand nodes (corresponding to plant species: palm trees, orchids, cacti, bamboo)\n", + "\n", + "2. **Edges and Capacities**: Each pipe (edge) connecting a reservoir to a demand node has a maximum flow capacity equivalent to the amount of that fertilizer type available. The edges between demand nodes and a common reservoir represent the requirement of each plant species for that fertilizer type.\n", + "\n", + "3. **Flow Conservation**: At each demand node (plant species), the incoming flow (fertilizer) from the corresponding reservoirs should satisfy the plant's requirement (one ton for each plant). The flow into a reservoir should not exceed its capacity.\n", + "\n", + "### Algorithm Steps\n", + "\n", + "1. Create a network graph representing the problem, with nodes for reservoirs, pipes, and demand nodes, and edges representing the connections and capacities.\n", + "\n", + "2. Apply the Max Flow-Min Cut theorem to find the maximum flow from the reservoirs to the demand nodes while respecting capacity constraints.\n", + "\n", + "3. Check if the maximum flow satisfies the demand at each plant species (demand node). If it does, there is enough fertilizer for all the plants for a season; otherwise, there isn't enough.\n", + "\n", + "### Proving Correctness\n", + "\n", + "The correctness of this algorithm can be proven based on the principles of network flow and the Max Flow-Min Cut theorem. The algorithm ensures that flow conservation is satisfied at each demand node, and it respects the capacity constraints of pipes and reservoirs.\n", + "\n", + "By applying the Max Flow-Min Cut theorem, if there is a feasible flow that satisfies all demand nodes (plants) while respecting capacity constraints, the algorithm will find it. If such a flow exists, it implies there is enough fertilizer for all the plants for a season; otherwise, there isn't enough.\n", + "\n", + "This reduction to a network flow problem provides a systematic and efficient way to determine fertilizer availability for plant care, similar to how water distribution networks can be analyzed to ensure sufficient water supply." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Define the network flow graph\n", + "# Each node represents a reservoir, pipe, or demand node\n", + "# Edges represent connections and capacities\n", + "\n", + "# Initialize flow on all edges to 0\n", + "initialize_flow()\n", + "\n", + "# Create a source node and a sink node\n", + "source = create_source_node()\n", + "sink = create_sink_node()\n", + "\n", + "# Connect the source to reservoir nodes with their capacity\n", + "for each reservoir_node:\n", + " add_edge(source, reservoir_node, capacity=reservoir_capacity)\n", + "\n", + "# Connect demand nodes to the sink node with their requirement\n", + "for each demand_node (plant species):\n", + " add_edge(demand_node, sink, capacity=required_fertilizer)\n", + "\n", + "# Apply Max Flow-Min Cut algorithm to find maximum flow from source to sink\n", + "max_flow = max_flow_min_cut(source, sink)\n", + "\n", + "# Check if all demand nodes (plants) have their requirements satisfied\n", + "def enough_fertilizer(max_flow):\n", + " for each demand_node (plant species):\n", + " if inflow(demand_node) < required_fertilizer:\n", + " return False\n", + " return True\n", + "\n", + "# Check if there is enough fertilizer for all the plants\n", + "if enough_fertilizer(max_flow):\n", + " print(\"There is enough fertilizer for all the plants for a season.\")\n", + "else:\n", + " print(\"There isn't enough fertilizer for all the plants for a season.\")\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/Submissions/002688252_Zijian_Feng/Zijian_Feng_002688252_Assignment4.md b/Submissions/002688252_Zijian_Feng/Zijian_Feng_002688252_Assignment4.md new file mode 100644 index 0000000..88ef6bc --- /dev/null +++ b/Submissions/002688252_Zijian_Feng/Zijian_Feng_002688252_Assignment4.md @@ -0,0 +1,9 @@ +# The Summary of Assignment Four + +- Student Name:Zijian Feng +- NUID:002688252 +- Professor: Nik Bear Brown + +## What I learned in this assignment + +This assignment presented us with several challenging problems. First, we extensively explored and reasoned about whether a problem belongs to NP-complete, such as the Vertex Cover problem. In our proofs, we typically went through two steps: first, determining if it belongs to NP, and then establishing if it is NP-hard. This assignment also allowed us to revisit the Maximum Flow problem, deepening my understanding of it.