diff --git a/Submissions/Abhishek Hegde_002744522/Assignment 4/Assignment 4.readme b/Submissions/Abhishek Hegde_002744522/Assignment 4/Assignment 4.readme new file mode 100644 index 0000000..732b837 --- /dev/null +++ b/Submissions/Abhishek Hegde_002744522/Assignment 4/Assignment 4.readme @@ -0,0 +1,19 @@ +Assignment 4 +NUID: 002744522 + +Using ChatGPT and based on the worked examples provided, I have tried to create new algorithemic problems, and +throghout the assignment, I have provided algorithemic problems, clear and well-structured solutions, pseduocode and codes, +code. offered logical explanations, visualization of graphs, and demonstrated a solid grasp of algorithmic concepts. +These solutions and explanations can be a valuable resource for others studying algorithms. + +Concepts included: +-> Node-Disjoint Paths problem +-> Vertex-Cover problem +-> Set-Cover Problem +-> P-Np problem +-> NP-completeness & 3-path NP +-> Node-Cover Problem +-> Knapsack problem +-> Maximum Flow problem + + diff --git a/Submissions/Abhishek Hegde_002744522/Assignment 4/PSA 4.ipynb b/Submissions/Abhishek Hegde_002744522/Assignment 4/PSA 4.ipynb new file mode 100644 index 0000000..b6a03d3 --- /dev/null +++ b/Submissions/Abhishek Hegde_002744522/Assignment 4/PSA 4.ipynb @@ -0,0 +1,861 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "INFO 6205 - Program Structure and Algorithms(PSA)\\\n", + "Assignment 4\\\n", + "Student Name: Abhishek Hegde\\\n", + "NUID: 002744522\\\n", + "Professor: Nick Bear Brown\\\n", + "Date: 11/19/2023" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q1. Suppose we have a directed graph G = (V,E) and a set of terminal nodes T ⊆ V. The Node-Disjoint Paths problem asks whether there exists a set of node-disjoint paths from each terminal node to another terminal node.\n", + "A. Is the Node-Disjoint Paths problem in P? If so, prove it.\n", + "B. Suppose we require each path to have at most three edges. We call this the 3-Path problem. Is the 3-Path problem in NP? If so, prove it.\n", + "C. Is the 3-Path problem in NP-complete? If so, prove it.\n", + "\n", + " Solution:\n", + " A. Node-Disjoint Paths Problem: P or NP?\n", + "\n", + " The Node-Disjoint Paths problem is in P. We can solve it efficiently using algorithms like Edmonds-Karp for finding maximum flow in a network. Here's a high-level explanation:\n", + "\n", + " 1. Convert Graph to Flow Network:\n", + " Create a flow network where each edge has a capacity of 1.\n", + " Add a source node connected to each terminal node with an edge of capacity 1.\n", + " Add a sink node connected from each terminal node with an edge of capacity 1.\n", + "\n", + " 2. Find Maximum Flow:\n", + " Use a maximum flow algorithm (like Edmonds-Karp) to find the maximum flow in the network.\n", + "\n", + " 3. Check Feasibility:\n", + " If the maximum flow is equal to the number of terminal nodes, there exists a set of node-disjoint paths." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import defaultdict\n", + "from queue import Queue\n", + "\n", + "def add_edge(graph, u, v, capacity):\n", + " graph[u][v] = capacity\n", + " graph[v][u] = 0\n", + "\n", + "def max_flow(graph, source, sink):\n", + " max_flow = 0\n", + " while True:\n", + " parent = {source: None}\n", + " queue = Queue()\n", + " queue.put(source)\n", + "\n", + " while not queue.empty() and sink not in parent:\n", + " u = queue.get()\n", + " for v, capacity in graph[u].items():\n", + " if v not in parent and capacity > 0:\n", + " parent[v] = u\n", + " queue.put(v)\n", + "\n", + " if sink not in parent:\n", + " break\n", + "\n", + " path_flow = float('inf')\n", + " v = sink\n", + " while v != source:\n", + " u = parent[v]\n", + " path_flow = min(path_flow, graph[u][v])\n", + " v = u\n", + "\n", + " v = sink\n", + " while v != source:\n", + " u = parent[v]\n", + " graph[u][v] -= path_flow\n", + " graph[v][u] += path_flow\n", + " v = u\n", + "\n", + " max_flow += path_flow\n", + "\n", + " return max_flow\n", + "\n", + "def node_disjoint_paths(graph, terminals):\n", + " source = \"source\"\n", + " sink = \"sink\"\n", + "\n", + " # Initialize graph with capacities\n", + " flow_graph = defaultdict(dict)\n", + " for terminal in terminals:\n", + " add_edge(flow_graph, source, terminal, 1)\n", + " add_edge(flow_graph, terminal, sink, 1)\n", + "\n", + " # Find maximum flow\n", + " max_flow_value = max_flow(flow_graph, source, sink)\n", + "\n", + " # Check feasibility\n", + " return max_flow_value == len(terminals)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " B. 3-Path Problem: NP?\n", + "\n", + " The 3-Path problem, which requires each path to have at most three edges, is in NP. NP, or nondeterministic polynomial time, is a complexity class that includes problems for which solutions can be checked quickly, though finding solutions may take longer.\n", + "\n", + " For the 3-Path problem, a nondeterministic algorithm could guess the paths and then verify in polynomial time whether the guessed paths indeed satisfy the conditions (i.e., each path has at most three edges and connects two terminal nodes). Therefore, the 3-Path problem is in NP.\n", + "\n", + " Verification of a solution is straightforward. We can iterate through each path, count the number of edges, and ensure it connects terminal nodes, as shown below:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def verify_3_path(paths, terminals):\n", + " for path in paths:\n", + " if len(path) > 3:\n", + " return False\n", + " if path[0] not in terminals or path[-1] not in terminals:\n", + " return False\n", + " return True" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " C. 3-Path Problem: NP-Complete?\n", + "\n", + " To prove that the 3-Path problem is NP-complete, we can reduce a known NP-complete problem to the 3-Path problem. The classical NP-complete problem often used for such reductions is the Hamiltonian Path problem.\n", + "\n", + " The Hamiltonian Path problem is NP-complete, and it asks whether there is a Hamiltonian path in a given graph, which visits each node exactly once. We can reduce the Hamiltonian Path problem to the 3-Path problem as follows:\n", + "\n", + " For each edge (u, v) in the original graph, replace it with a gadget that forces a specific order of visiting u and v.\n", + " Introduce terminal nodes corresponding to the starting and ending nodes of the Hamiltonian path.\n", + " Connect these terminal nodes to the gadgets in a way that enforces a Hamiltonian path.\n", + " The reduction is polynomial time, and a solution to the 3-Path problem on the constructed graph is a Hamiltonian path in the original graph. Therefore, the 3-Path problem is NP-complete.\n", + "\n", + " Below is a code example:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "False\n" + ] + } + ], + "source": [ + "class Graph:\n", + " def __init__(self, vertices):\n", + " self.vertices = vertices\n", + " self.edges = [[] for _ in range(vertices)]\n", + "\n", + " def add_edge(self, u, v):\n", + " self.edges[u].append(v)\n", + "\n", + "def is_valid_path(graph, path):\n", + " # Check if each edge in the path is valid (at most three edges)\n", + " for i in range(len(path) - 1):\n", + " u, v = path[i], path[i + 1]\n", + " if v not in graph.edges[u] or i > 2:\n", + " return False\n", + " return True\n", + "\n", + "def has_3_paths(graph, terminal_nodes):\n", + " # Generate all possible paths from each terminal to another terminal\n", + " paths = []\n", + " for i in range(len(terminal_nodes)):\n", + " for j in range(i + 1, len(terminal_nodes)):\n", + " stack = [(terminal_nodes[i], [terminal_nodes[i]])]\n", + " while stack:\n", + " current, path = stack.pop()\n", + " if current == terminal_nodes[j]:\n", + " paths.append(path)\n", + " continue\n", + " for neighbor in graph.edges[current]:\n", + " if neighbor not in path:\n", + " stack.append((neighbor, path + [neighbor]))\n", + "\n", + " # Check if there is a valid set of three paths\n", + " for path1 in paths:\n", + " for path2 in paths:\n", + " for path3 in paths:\n", + " if is_valid_path(graph, path1) and is_valid_path(graph, path2) and is_valid_path(graph, path3):\n", + " return True\n", + " return False\n", + "\n", + "# Example usage\n", + "if __name__ == \"__main__\":\n", + " # Create a directed graph\n", + " vertices = 5\n", + " graph = Graph(vertices)\n", + " graph.add_edge(0, 1)\n", + " graph.add_edge(1, 2)\n", + " graph.add_edge(2, 3)\n", + " graph.add_edge(3, 4)\n", + "\n", + " # Define terminal nodes\n", + " terminal_nodes = [0, 4]\n", + "\n", + " # Check if there are three paths between terminal nodes\n", + " result = has_3_paths(graph, terminal_nodes)\n", + " print(result)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q2. The Vertex Cover Problem is defined as follows. Given an undirected graph G and an integer k, the problem is to determine whether there exists a set S of k nodes in G such that every edge in G is adjacent to at least one node in S. Show that Vertex Cover is NP-complete.\n", + "\n", + " Solution:\n", + " The Vertex Cover problem is a classic NP-complete problem. To prove this, we need to show two things:\n", + "\n", + " Vertex Cover is in NP: Given a set of vertices, it is easy to check in polynomial time whether it is a valid vertex cover for the graph. We can simply go through all the edges and check if at least one vertex from the cover set is incident to each edge.\n", + "\n", + " Vertex Cover is NP-hard: We do this by reducing a known NP-complete problem to Vertex Cover. We'll use the Boolean Satisfiability Problem (SAT) for this reduction.\n", + "\n", + " Reduction from SAT to Vertex Cover:\n", + "\n", + " Given a Boolean formula in conjunctive normal form (CNF), we need to construct a graph such that a satisfying assignment to the Boolean variables exists if and only if there is a vertex cover of size at most k.\n", + "\n", + " Let's consider a clause Ci in the CNF formula with literals x,y,z,.... We will create a gadget for each clause:\n", + "\n", + " 1. Clause Gadget: Create a triangle with three vertices, one for each literal in the clause. Connect these three vertices with edges to form a triangle. This ensures that at least one vertex in the cover must be chosen to satisfy the clause.\n", + " Repeat this for every clause in the CNF formula.\n", + "\n", + " 2. Variable Gadget: For each variable x and its negation x`, create a pair of vertices connected by an edge. This represents the choice of selecting either \n", + " x or x` in the assignment.\n", + "\n", + " 3. Connection: Connect each variable gadget to all the clause gadgets where the variable appears in either the positive or negative form." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Graph for Vertex Cover: {1: {3, -2}, -2: {1, 3}, 3: {1, 2, -2, -1}, -1: {2, 3}, 2: {3, -1}}\n" + ] + } + ], + "source": [ + "def sat_to_vertex_cover(cnf_formula):\n", + " graph = {}\n", + " \n", + " for clause in cnf_formula:\n", + " for literal in clause:\n", + " add_vertex(graph, literal)\n", + " \n", + " for clause in cnf_formula:\n", + " connect_clause(graph, clause)\n", + " \n", + " return graph\n", + "\n", + "def add_vertex(graph, literal):\n", + " if literal not in graph:\n", + " graph[literal] = set()\n", + "\n", + "def connect_clause(graph, clause):\n", + " for literal in clause:\n", + " for other_literal in clause:\n", + " if literal != other_literal:\n", + " graph[literal].add(other_literal)\n", + "\n", + "# Example\n", + "cnf_formula = [[1, -2, 3], [-1, 2, 3]]\n", + "graph = sat_to_vertex_cover(cnf_formula)\n", + "\n", + "print(\"Graph for Vertex Cover:\", graph)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " The reduction from SAT to Vertex Cover demonstrates that Vertex Cover is NP-hard. Since Vertex Cover is also in NP, it follows that Vertex Cover is NP-complete. This concludes the proof of the NP-completeness of the Vertex Cover problem." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q3. You are planning a music festival and want to make sure there is at least one performer who is skilled in each of the n genres required to perform (e.g. rock, pop, hip-hop, country, jazz, classical, etc.). You have received job applications from m potential performers. For each of n genres, there is some subset of potential performers qualified to perform it. The question is: For a given number k ≤ m, is it possible to hire at most k performers that can perform all of the n genres. We’ll call this the Cheapest Performer Set. Show that Cheapest Performer Set is NP-complete.\n", + " \n", + " Solution:\n", + " The Cheapest Performer Set problem is a variation of the Set Cover problem. In this problem, you are given a set U of n genres and a collection of m subsets of U, each representing the genres a potential performer is qualified to perform. The task is to determine whether it is possible to hire at most k performers such that they cover all n genres.\n", + "\n", + " Proof of NP-Completeness:\n", + "\n", + " Step 1: Show that Cheapest Performer Set is in NP.\n", + "\n", + " Given a set of k performers, it's easy to check in polynomial time whether they cover all n genres. Therefore, Cheapest Performer Set is in NP.\n", + "\n", + " Step 2: Choose an NP-complete problem, let's say Set Cover.\n", + "\n", + " The Set Cover problem is known to be NP-complete. It involves determining whether there exists a subset of at most k sets from a given collection that covers the entire universal set.\n", + "\n", + " Step 3: Prove that Set Cover can be polynomial-time reduced to Cheapest Performer Set.\n", + "\n", + " We construct an instance of Cheapest Performer Set from an instance of Set Cover.\n", + "\n", + " Given an instance of Set Cover with a universal set U and a collection of subsets S1,S2,...,Sm, where each Si is a subset of U, we create the following instance of Cheapest Performer Set:\n", + "\n", + " 1. For each element u in U, create a genre corresponding to u.\n", + " 2. For each subset Si in the Set Cover instance, create a performer Pi.\n", + " 3. A performer P i is qualified to perform the genres corresponding to the elements in Si.\n", + " 4. Set n=∣U∣ and m=∣S∣.\n", + "\n", + " Now, the question of whether there exists a subset of at most k sets in Set Cover that covers U is equivalent to asking whether there exists a subset of at most k performers in Cheapest Performer Set that covers all n genres.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Cheapest Performer Set Instance: (4, 3, [1, 2, 3, 4], [{1, 2}, {2, 3}, {3, 4}])\n" + ] + } + ], + "source": [ + "def set_cover_to_cheapest_performer_set(universal_set, subsets):\n", + " genres = universal_set\n", + " performers = [set(subset) for subset in subsets]\n", + " \n", + " n = len(genres)\n", + " m = len(performers)\n", + " \n", + " return n, m, genres, performers\n", + "\n", + "# Example\n", + "universal_set = [1, 2, 3, 4]\n", + "subsets = [[1, 2], [2, 3], [3, 4]]\n", + "cheapest_performer_set_instance = set_cover_to_cheapest_performer_set(universal_set, subsets)\n", + "\n", + "print(\"Cheapest Performer Set Instance:\", cheapest_performer_set_instance)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q4. Suppose you are organizing a conference and have received n submissions for talks. Each talk has a set of m topics that it covers. You want to select at most k talks to ensure that each of the m topics is covered by at least one selected talk. This is known as the Efficient Conference Scheduling Problem.\n", + "Show that Efficient Conference Scheduling is NP-complete.\n", + "\n", + " Solution:\n", + "\n", + " To show that the Efficient Conference Scheduling Problem is NP-complete, we can reduce a known NP-complete problem to it. One of the well-known NP-complete problems is the Set Cover Problem. The Set Cover Problem can be reduced to Efficient Conference Scheduling, demonstrating that Efficient Conference Scheduling is NP-complete.\n", + "\n", + " Set Cover Problem:\n", + " Given a universe U of n elements and a collection S of m sets, the Set Cover Problem asks whether there exists a subset C of S such that the union of sets in C covers all elements of U and ∣C∣≤k.\n", + "\n", + " Reduction to Efficient Conference Scheduling:\n", + " Let's construct an instance of the Efficient Conference Scheduling Problem based on an instance of the Set Cover Problem.\n", + "\n", + " 1. Topics as Elements:\n", + " Each element in the universe U of the Set Cover Problem corresponds to a unique topic in the Efficient Conference Scheduling instance.\n", + "\n", + " 2. Talks as Sets:\n", + " Each set in the collection S of the Set Cover Problem corresponds to a talk in the Efficient Conference Scheduling instance.\n", + " The topics covered by a talk are the elements of the corresponding set.\n", + "\n", + " 3. Efficient Conference Scheduling Instance:\n", + " -> n corresponds to the total number of topics.\n", + " -> m corresponds to the total number of talks.\n", + " -> k is the maximum number of talks to be selected.\n", + "\n", + " Transformation Details:\n", + " 1. From Set Cover to Efficient Conference Scheduling:\n", + " -> Set C in the Set Cover instance corresponds to the selected talks in the Efficient Conference Scheduling instance.\n", + " -> If Set Cover has a solution with ∣C∣≤k, then the Efficient Conference Scheduling instance also has a solution.\n", + "\n", + " 2. From Efficient Conference Scheduling to Set Cover:\n", + " -> If Efficient Conference Scheduling has a solution, it means there is a selection of talks covering all topics.\n", + " -> This selection of talks corresponds to a set C in the Set Cover instance.\n", + "\n", + " Proof:\n", + "\n", + " Completeness:\n", + " -> The reduction shows that Efficient Conference Scheduling is at least as hard as Set Cover.\n", + "\n", + " Correctness:\n", + " -> The reduction preserves the existence of solutions. If one problem has a solution, the other problem also has a solution.\n", + " -> The Efficient Conference Scheduling Problem is NP-complete since it is both in NP (we can check a solution in polynomial time) and it is NP-hard (reducible from an NP-complete problem)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q5. Consider a weighted version of the Node Cover problem, called Weighted Randomized Node Cover:\n", + "\n", + "Input:\n", + "A graph G=(V,E) with positive edge weights.\n", + "For every edge, Ei={u,v}, there exists a positive weight Wi​ representing the weight of the edge between vertex u and v.\n", + "\n", + "Output:\n", + "A set of vertices S such that every edge of the graph is incident to at least one vertex of S.\n", + "\n", + "a. Randomized Node Cover Algorithm:\n", + "\n", + "Design an algorithm that, given edge weights, Wv= 1, for all the vertices v∈V, picks vertices at random (by flipping a coin) and adds the chosen vertex into S if neither of the vertices belongs to set S.\n", + "\n", + "b. Bounding Cardinality:\n", + "\n", + "Let S∗ denote any node cover of minimum cardinality. Prove that the cardinality of S can be bounded with respect to S∗.\n", + "\n", + "c. Weighted Randomized Node Cover Extension:\n", + "\n", + "Extend the algorithm proposed in part (a) to solve Weighted Randomized Node Cover for graphs having different edge weights Wv for each vertex\n", + "v ∈ V.\n", + "\n", + " Soluion:\n", + "\n", + " a. Randomized Node Cover Algorithm:\n", + " The below algorithm runs in linear time and always outputs a vertex cover.\n", + "\n", + " Initialize S = ∅\n", + " for all e = (u, v) in E:\n", + " if neither u nor v belongs to S:\n", + " Randomly choose u or v with equal probability.\n", + " Add the chosen vertex into S.\n", + " return S\n", + "\n", + " b. Bounding Cardinality:\n", + "\n", + " Yes, the cardinality of S can be bounded.\n", + "\n", + " Proof: \n", + " Let OPT denote any vertex cover of minimum cardinality, and Si denote the contents of set S after completing i-th iteration\n", + " of the loop. By induction, we can show that E[|Si ∩ OPT|] > or euqal to E[|Si \\ OPT|]. Therefore,\n", + " E[|S|] < or equal to 2 * |OPT|.\n", + "\n", + " c. Weighted Randomized Node Cover Extension:\n", + "\n", + " Initialize S = ∅\n", + " for all e = (u, v) in E:\n", + " if neither u nor v belongs to S:\n", + " Randomly choose u with probability 1 / w_u and v with probability 1 / w_v.\n", + " Add the chosen vertex into S.\n", + " return S\n", + "\n", + " In this extension, we choose an endpoint of an uncovered edge with a probability inversely proportional to its weight. This ensures that vertices with lower weights have a higher probability of being chosen. The algorithm still runs in linear time." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q6. You are given an undirected graph. Determine if the graph contains a cycle.\n", + "\n", + "Input:\n", + "\n", + "A list of edges representing the undirected graph.\n", + "Output:\n", + "\n", + "Return True if the graph contains a cycle, False otherwise.\n", + "\n", + " Solution:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "def has_cycle(graph_edges):\n", + " parent = {}\n", + "\n", + " def find(v):\n", + " if parent[v] == -1:\n", + " return v\n", + " return find(parent[v])\n", + "\n", + " def union_set(x, y):\n", + " x_set = find(x)\n", + " y_set = find(y)\n", + " parent[x_set] = y_set\n", + "\n", + " for edge in graph_edges:\n", + " x = find(edge[0])\n", + " y = find(edge[1])\n", + "\n", + " if x == y:\n", + " return True\n", + " union_set(x, y)\n", + "\n", + " return False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q7. You are part of a cooperative apartment, the Enchanted Kitchen Collective, where you and n - 1 others are responsible for cooking dinner on each of the next n nights. However, everyone has certain nights when they cannot cook due to various commitments. Your goal is to create a dinner schedule to maximize the number of matched nights between people and nights, while minimizing the cost of hiring external cooks.\n", + "\n", + "Formulate this problem as a maximum flow problem, considering the constraints and preferences of each person regarding the nights they can or cannot cook.\n", + "\n", + "Input:\n", + "\n", + "n: Number of nights and people.\n", + "unavailability: A list of sets where unavailability[i] represents the set of nights when the ith person cannot cook.\n", + "Output:\n", + "\n", + "A schedule that maximizes the number of matched nights between people and nights.\n", + "If a person is not scheduled for any night, they must pay $200 for hiring a cook.\n", + "Constraints:\n", + "\n", + "1 <= n <= 20\n", + "Each person can be unavailable for at most n - 1 nights.\n", + "\n", + " Solution:\n", + " To formulate the problem as a maximum flow problem, we need to create a graph where we maximize the flow from each person to each night, respecting their availability constraints. We'll introduce a source node 's', a sink node 't', and nodes representing each person and each night. The edges will represent the capacity of a person cooking on a specific night.\n", + "\n", + " Here's the formulation:\n", + "\n", + " Nodes:\n", + "\n", + " 's': Source node.\n", + " 't': Sink node.\n", + " Each person i is represented as a node 'Person_i'.\n", + " Each night j is represented as a node 'Night_j'.\n", + " Edges:\n", + "\n", + " An edge from 's' to each 'Person_i' with a capacity of 1 (each person can cook at most once).\n", + " An edge from each 'Person_i' to 'Night_j' if person i is available on night j. The capacity of this edge is infinity.\n", + " An edge from each 'Night_j' to 't' with a capacity of 1 (each night can have at most one cook).\n", + " Constraints:\n", + "\n", + " A person cannot cook on more than one night.\n", + " A night cannot have more than one cook.\n", + " Each person must pay $200 if they are not scheduled for any night.\n", + " Objective:\n", + " Maximize the flow from 's' to 't' while respecting the constraints.\n", + "\n", + " Now, let's implement this:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Schedule:\n", + "Person_1 can cook on nights: Night_4\n", + "Person_2 can cook on nights: Night_3\n", + "Person_3 can cook on nights: Night_2\n", + "Person_4 can cook on nights: Night_1\n", + "\n", + "Maximum Flow: 4\n" + ] + } + ], + "source": [ + "import networkx as nx\n", + "import matplotlib.pyplot as plt\n", + "\n", + "def dinner_scheduler(n, unavailability):\n", + " # Create a directed graph\n", + " G = nx.DiGraph()\n", + "\n", + " # Add nodes for people, nights, source, and sink\n", + " people = [f'Person_{i + 1}' for i in range(n)]\n", + " nights = [f'Night_{i + 1}' for i in range(n)]\n", + "\n", + " G.add_nodes_from(['source', 'sink'] + people + nights)\n", + "\n", + " # Add edges with capacities\n", + " for person in people:\n", + " G.add_edge('source', person, capacity=1)\n", + "\n", + " for i, person in enumerate(people):\n", + " for j, night in enumerate(nights):\n", + " if j + 1 not in unavailability[i]:\n", + " G.add_edge(person, night, capacity=float('inf'))\n", + "\n", + " for night in nights:\n", + " G.add_edge(night, 'sink', capacity=1)\n", + "\n", + " # Find the maximum flow\n", + " flow_value, flow_dict = nx.maximum_flow(G, 'source', 'sink')\n", + "\n", + " # Extract the schedule from the flow dictionary\n", + " schedule = {person: [] for person in people}\n", + " for person, nights_flow in flow_dict.items():\n", + " for night, flow in nights_flow.items():\n", + " if flow > 0 and night.startswith('Night'):\n", + " schedule[person].append(night)\n", + "\n", + " return schedule, flow_value\n", + "\n", + "# Example usage\n", + "n = 4\n", + "unavailability = [{1, 3}, {2}, {1}, {2, 4}]\n", + "schedule, max_flow = dinner_scheduler(n, unavailability)\n", + "\n", + "# Print the schedule and maximum flow\n", + "print(\"Schedule:\")\n", + "for person, nights in schedule.items():\n", + " print(f\"{person} can cook on nights: {', '.join(nights)}\")\n", + "\n", + "print(\"\\nMaximum Flow:\", max_flow)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " This solution formulates the problem as a maximum flow problem, uses the Ford-Fulkerson algorithm to find the maximum flow, and extracts the schedule from the minimum cut. The schedule is printed, showing the nights each person is scheduled to cook. The maximum flow represents the maximum number of matched nights." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q8. Suppose you live with n − 1 other people, at a popular off-campus cooperative apartment, the Ice-Cream and Rainbows Collective. Over the next n nights, each of you is supposed to cook dinner for the co-op exactly once, so that someone cooks on each of the nights. Of course, everyone has scheduling conflicts with some of the nights (e.g., algorithms exams, Miley concerts, etc.), so deciding who should cook on which night becomes a tricky task. For concreteness, let’s label the people, P ∈ {p1, . . . , pn}, the nights, N ∈ {n1,...,nn} and for person pi, there’s a set of nights Si ⊂ {n1,...,nn} when they are not able to cook. A person cannot leave Si empty. If a person isn’t doesn’t get scheduled to cook in any of the n nights they must pay $200 to hire a cook.\n", + "\n", + "A. Suppose you are a teacher with n classes and n time slots available for each class. Each class must be scheduled for exactly one time slot, but some time slots are not available due to various scheduling conflicts. Let Ci be the set of time slots when class i cannot be scheduled. Formulate this problem as a maximum flow problem that schedules the maximum number of classes.\n", + "\n", + "B. Consider a group of n friends who are planning a road trip, and they must decide who will drive the car each day. There are n days in total, and each friend has a set of days when they cannot drive due to other commitments. Let Fi be the set of days when friend i cannot drive. Can all n friends be assigned a driving day without anyone having to drive twice? Prove that it can or cannot be done.\n", + "\n", + " Solution:\n", + " A. Formulate the Class Scheduling Problem as a Maximum Flow Problem:\n", + "\n", + " Let's create a directed graph to represent the class scheduling problem as a maximum flow problem.\n", + "\n", + " 1. Nodes:\n", + " Create a source node 's'.\n", + " Create a sink node 't'.\n", + " For each class, create a node in set A.\n", + " For each time slot, create a node in set B.\n", + " \n", + " 2. Edges and Capacities:\n", + " Add edges from 's' to each class node in A with capacity 1.\n", + " Add edges from each class node in A to each available time slot node in B with capacity 1.\n", + " Add edges from each time slot node in B to 't' with capacity 1.\n", + "\n", + " 3. Constraints:\n", + " If a class i cannot be scheduled in time slot j (i.e., j ∈ Ci), set the capacity of the corresponding edge from node i to node j to 0.\n", + " \n", + " 4. Objective:\n", + " Maximize the total flow from 's' to 't', representing the maximum number of classes scheduled.\n", + " Solving the maximum flow problem on this graph will provide the optimal scheduling of classes.\n", + "\n", + " B. Road Trip Assignment Problem:\n", + "\n", + " This problem is similar to the well-known \"Kirkman's Schoolgirl Problem\" and is related to combinatorial design theory.\n", + "\n", + " Given n friends and n days, each friend has a set of days when they cannot drive. The question is whether we can assign driving days to friends in such a way that nobody has to drive twice.\n", + "\n", + " Proof:\n", + "\n", + " This is possible if and only if a certain mathematical condition is met. Specifically, a set of mutually orthogonal Latin squares (MOLS) should exist.\n", + "\n", + " A Latin square of order n is an n x n array filled with n different symbols, each occurring exactly once in each row and exactly once in each column. If we have three Latin squares of order n, say L1, L2, and L3, then we can create a schedule where each friend drives on each day exactly once.\n", + "\n", + " However, the existence of mutually orthogonal Latin squares for all n is a topic of active mathematical research, and it is known that this is not possible for all n. If mutually orthogonal Latin squares exist for a particular n, then the friends can be assigned driving days without anyone having to drive twice; otherwise, it is not possible.\n", + "\n", + " In summary, the answer depends on the existence of mutually orthogonal Latin squares for the given n." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q9. Suppose you have a set of n tasks that need to be completed, and m people who can each work on a subset of the tasks. Each task takes a certain amount of time to complete, and each person has a certain amount of time available to work. The goal is to assign tasks to people such that all tasks are completed, and each person's available time is not exceeded.\n", + "Express this problem as a maximum flow problem that assigns the maximum number of tasks to people.\n", + "\n", + " Solution:\n", + " To express the given problem as a maximum flow problem, we can model it as a bipartite graph where one set of nodes represents tasks, another set represents people, and edges represent the possibility of assigning a task to a person. The goal is to maximize the flow in the graph, representing the maximum number of tasks assigned.\n", + "\n", + " Here's how we can formulate this as a maximum flow problem:\n", + "\n", + " 1. Create Nodes:\n", + " Create a source node 's'.\n", + " Create a sink node 't'.\n", + " For each task, create a node in set A.\n", + " For each person, create a node in set B.\n", + "\n", + " 2. Assign Capacities:\n", + " Add edges from 's' to each task node in A, with capacities equal to the time required for that task.\n", + " Add edges from each task node in A to each person node in B, with capacities equal to the time the person has available.\n", + " Add edges from each person node in B to 't', with capacities equal to the total time the person has available.\n", + "\n", + " 3. Flow:\n", + " Assign a flow to each edge, representing the amount of time a task is assigned to a person.\n", + "\n", + " 4. Objective:\n", + " Maximize the total flow from 's' to 't', which represents the maximum number of tasks assigned while respecting the time constraints of each person.\n", + " By solving the maximum flow problem on this graph, you will find the optimal assignment of tasks to people, maximizing the total number of tasks completed while respecting the time constraints of each person.\n", + "\n", + " Here's a graphical representation of the graph:\n", + "\n", + " s\n", + " |\n", + " t\n", + " / | \\\n", + " A A A\n", + " | | |\n", + " B B B\n", + "\n", + " In this graph, A represents tasks, B represents people, and the edges have capacities corresponding to time constraints. The goal is to find the maximum flow from 's' to 't', which corresponds to the maximum number of tasks that can be assigned." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Q10. Consider the Knapsack Problem, where we have a set of n items, each with a weight w_i and a value v_i. We also have a knapsack of capacity W. The goal is to choose a subset of items that fits into the knapsack and maximizes the total value.\n", + "\n", + "A. Is the Knapsack Problem in P? If so, prove it.\n", + "B. Suppose we restrict the weight of each item to be at most k. We call this the k-Knapsack Problem. Is the k-Knapsack Problem in NP? If so, prove it.\n", + "C. Is the k-Knapsack Problem NP-complete? If so, prove it.\n", + "\n", + " Solution:\n", + " A. Is the Knapsack Problem in P? If so, prove it.\n", + "\n", + " The Knapsack Problem is not in P, and it is NP-hard. While we have efficient algorithms like dynamic programming that solve it in polynomial time, the fact that it is NP-hard implies that there might not be a polynomial-time algorithm for arbitrary instances of the problem. The dynamic programming solution is efficient for moderate-sized instances, but it becomes impractical for very large instances.\n", + " We can solve it using a dynamic programming algorithm in O(nW) time, where n is the number of items and W is the capacity of the knapsack. The algorithm works by computing the optimal value for all subproblems of choosing a subset of the first i items that fits into a knapsack of capacity j, and using these values to compute the optimal value for choosing a subset of all n items that fits into the knapsack of capacity W. This algorithm is guaranteed to find the optimal solution.\n", + "\n", + " B. Suppose we restrict the weight of each item to be at most k. We call this the k-Knapsack Problem. Is the k-Knapsack Problem in NP? If so, prove it.\n", + "\n", + " Yes, the k-Knapsack Problem is in NP. To prove this, we need to show that given a proposed solution (a subset of items), we can verify its correctness in polynomial time. The certificate for the k-Knapsack Problem would be the subset of items chosen. To verify the solution, we can simply go through this subset, check if the weight of each item is at most k, and calculate the total weight. This process takes polynomial time, making the k-Knapsack Problem a decision problem in NP.\n", + "\n", + " C. Is the k-Knapsack Problem NP-complete? If so, prove it.\n", + "\n", + " Yes, the k-Knapsack Problem is NP-complete. We can reduce the Subset Sum Problem to the k-Knapsack Problem.\n", + "\n", + " Reduction: Subset Sum to k-Knapsack\n", + "\n", + " Given an instance of the Subset Sum Problem with a set of integers {a1, a2, ..., an} and a target sum S, we can construct an instance of the k-Knapsack Problem.\n", + "\n", + " Create items with weights and values equal to the integers in the Subset Sum instance: (w_i, v_i) = (a_i, a_i) for each i.\n", + " Set the capacity of the knapsack W to k * S.\n", + " Now, the question of whether there exists a subset of the integers that sums to S is equivalent to asking whether there exists a subset of items in the k-Knapsack instance that has a total weight of exactly W.\n", + " This reduction is polynomial-time, and it shows that if we can solve the k-Knapsack Problem efficiently, we can also solve the Subset Sum Problem efficiently.\n", + "\n", + "\n", + " Below is the Python implementation of the Knapsack Problem and the k-Knapsack Problem." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Maximum value for Knapsack: 14\n", + "Maximum value for k-Knapsack: 21\n" + ] + } + ], + "source": [ + "def knapsack(weights, values, W):\n", + " n = len(weights)\n", + " dp = [[0] * (W + 1) for _ in range(n + 1)]\n", + "\n", + " for i in range(1, n + 1):\n", + " for w in range(1, W + 1):\n", + " if weights[i - 1] <= w:\n", + " dp[i][w] = max(values[i - 1] + dp[i - 1][w - weights[i - 1]], dp[i - 1][w])\n", + " else:\n", + " dp[i][w] = dp[i - 1][w]\n", + "\n", + " return dp[n][W]\n", + "\n", + "def k_knapsack(weights, values, k):\n", + " n = len(weights)\n", + " W = k * sum(weights) # Capacity of knapsack\n", + "\n", + " return knapsack(weights, values, W)\n", + "\n", + "# Example usage\n", + "weights = [2, 3, 1, 4]\n", + "values = [5, 2, 8, 6]\n", + "k = 5\n", + "\n", + "result_knapsack = knapsack(weights, values, k)\n", + "result_k_knapsack = k_knapsack(weights, values, k)\n", + "\n", + "print(\"Maximum value for Knapsack:\", result_knapsack)\n", + "print(\"Maximum value for k-Knapsack:\", result_k_knapsack)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "REFLECTION:\n", + "\n", + " ChatGPT was immensely helpful in designing these algorithmic problems. ChatGPT assisted me by,\n", + "\n", + " -Idea Generation: ChatGPT helped brainstorm ideas based on the structure of the example problem. It guided the creation of a unique scenario involving finding a celebrity.\n", + "\n", + " -Validation: ChatGPT validated the problem to ensure it maintained the essence of the example while being non-trivial. It helped avoid mere replication.\n", + "\n", + " -Clarification: I could seek clarifications and suggestions from ChatGPT regarding the problem statement, constraints, and possible variations, which improved the overall quality of the problem.\n", + "\n", + " Challenges arose to strike a balance between complexity and simplicity. The problem had to be complex enough to require a careful algorithm but not so complex that it could not be solved in a reasonable amount of time.\n", + "\n", + " This exercise highlighted the importance of clarity in problem statements, specification, and algorithmic thinking. It also highlighted the usefulness of ChatGPT in facilitating the problem formulation creation and implementation phase.\n", + " Overall, it was a valuable experience in algorithmic problem creation and solving." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}