diff --git a/Submissions/Yuxuan_Zhang_002778556/README.md b/Submissions/Yuxuan_Zhang_002778556/README.md index e75fe19..be719f9 100644 --- a/Submissions/Yuxuan_Zhang_002778556/README.md +++ b/Submissions/Yuxuan_Zhang_002778556/README.md @@ -1,4 +1,4 @@ -# Assignment-3 +# Assignment-5 [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) ## Install This project uses anaconda and python. Go check them out if you don't have them locally installed. diff --git a/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment4.ipynb b/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment4.ipynb new file mode 100644 index 0000000..47052af --- /dev/null +++ b/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment4.ipynb @@ -0,0 +1,719 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "e29804b9", + "metadata": {}, + "source": [ + "# INFO 6205 - Program Structure and Algorithms\n", + "# Worked Assignment 4 Solutions\n", + "### student Name: yuxuan zhang\n", + "### Professor: Nik Bear Brown\n", + "### Date: 16/11/2023" + ] + }, + { + "cell_type": "markdown", + "id": "60a58a34", + "metadata": {}, + "source": [ + "### Q1 (20 Points)\n", + "\n", + "Given a directed graph ` G = (V,E) `, a Hamiltonian decomposition is a set of Hamiltonian cycles such that each vertex ` v `in ` V ` belongs to exactly one cycle. In other words, a Hamiltonian decomposition of a graph ` G ` is a set of Hamiltonian cycles which are sub-graphs of ` G ` and together include all vertices of ` G `. If the cycles in the decomposition do not share any edges, the decomposition is called an edge-disjoint Hamiltonian decomposition.\n", + "\n", + "The edge-disjoint Hamiltonian decomposition problem asks whether a given directed graph has an edge-disjoint Hamiltonian decomposition." + ] + }, + { + "cell_type": "markdown", + "id": "a0a7bb1d", + "metadata": {}, + "source": [ + "### reflection\n", + "\n", + "1. **Understanding Graph Theoretical Concepts**: The question delves into advanced concepts in graph theory, particularly Hamiltonian cycles and graph decomposition. These concepts are fundamental in understanding complex networks and are applicable in various fields like computer science, biology, and logistics.\n", + "\n", + "2. **Exploration of Computational Complexity Classes**: The question traverses through different complexity classes - P, NP, and NP-complete. It highlights the nuanced differences between these classes, particularly the distinction between a problem being solvable in polynomial time (P), verifiable in polynomial time (NP), and being as hard as the hardest problems in NP (NP-complete).\n", + "\n", + "3. **The Challenge of Problem Reduction**: The aspect of reducing a known NP-complete problem (like the Hamiltonian Cycle problem) to the 2-Hamiltonian-decomposition problem underscores the methodology used in proving NP-completeness. This reduction approach is a cornerstone in computational complexity theory, illustrating how seemingly different problems are interconnected.\n", + "\n", + "4. **Applicability and Real-World Implications**: Problems like the 2-Hamiltonian-decomposition have theoretical importance, but they also have practical implications. For instance, solving such problems can have applications in network design, circuit layout, and even in understanding complex systems in nature.\n", + "\n", + "5. **Dynamic Nature of Computational Complexity**: The discussion also emphasizes that the field of computational complexity is dynamic. What is considered hard or unsolvable today might change with advancements in algorithms or computational power. This reflects the ever-evolving nature of computer science and mathematics.\n", + "\n", + "6. **Educational and Research Value**: Such questions are valuable for educational purposes, as they encourage deep thinking and problem-solving skills. They are also indicative of ongoing research areas in theoretical computer science, where understanding the complexity of various problems remains a significant challenge." + ] + }, + { + "cell_type": "markdown", + "id": "7093832c", + "metadata": {}, + "source": [ + "### A. (10 points) \n", + "Is the edge-disjoint Hamiltonian decomposition problem in P? If so, provide a proof." + ] + }, + { + "cell_type": "markdown", + "id": "71f189a5", + "metadata": {}, + "source": [ + "### solution\n", + "**Problem Statement for A**: Is the edge-disjoint Hamiltonian decomposition problem in P?\n", + "\n", + "1. **Understanding the Problem**: The edge-disjoint Hamiltonian decomposition problem asks if we can decompose a directed graph into edge-disjoint Hamiltonian cycles. A Hamiltonian cycle is a cycle that visits every vertex exactly once and returns to the starting vertex.\n", + "\n", + "2. **Complexity of Hamiltonian Cycle Problem**: It's well-known that the Hamiltonian cycle problem is NP-complete for general graphs. This means there is no known polynomial-time algorithm to find a Hamiltonian cycle in a general graph unless P = NP.\n", + "\n", + "3. **Edge-Disjoint Hamiltonian Decomposition**: The edge-disjoint Hamiltonian decomposition problem is a more complex variant. Not only do we need to find a Hamiltonian cycle, but we also need to decompose the entire graph into such cycles without sharing edges between them.\n", + "\n", + "4. **No Known Polynomial-Time Algorithm**: Given the complexity of finding even a single Hamiltonian cycle, extending this to an edge-disjoint decomposition increases the complexity. As of my last update, there is no known polynomial-time algorithm for this problem in the general case.\n", + "\n", + "5. **Conclusion**: Based on the current understanding and the complexity of the underlying Hamiltonian cycle problem, the edge-disjoint Hamiltonian decomposition problem is not in P, as there is no known polynomial-time solution." + ] + }, + { + "cell_type": "markdown", + "id": "ee357101", + "metadata": {}, + "source": [ + "### B. (5 points) \n", + "Suppose we limit the decomposition to have at most two Hamiltonian cycles. We call this the 2-Hamiltonian-decomposition problem. Is the 2-Hamiltonian-decomposition problem in NP? If so, provide a proof." + ] + }, + { + "cell_type": "markdown", + "id": "32cf38ca", + "metadata": {}, + "source": [ + "### solution\n", + "**Problem Statement for B**: Is the 2-Hamiltonian-decomposition problem in NP?\n", + "\n", + "1. **Understanding the Problem**: The 2-Hamiltonian-decomposition problem asks if a given directed graph can be decomposed into at most two Hamiltonian cycles that are edge-disjoint. \n", + "\n", + "2. **Definition of NP**: A problem is in NP (nondeterministic polynomial time) if a solution to the problem can be verified in polynomial time. This doesn't mean the solution can be found in polynomial time, but if given a solution, we can verify its correctness quickly.\n", + "\n", + "3. **Verifying a 2-Hamiltonian Decomposition**: If we are given a proposed solution to this problem – that is, two Hamiltonian cycles that purportedly decompose the graph – we can verify this solution in polynomial time. \n", + " - We can check if each cycle is Hamiltonian (visits every vertex exactly once and returns to the start) in polynomial time relative to the number of vertices.\n", + " - We can also verify that the two cycles are edge-disjoint in polynomial time by comparing the edges in each cycle.\n", + "\n", + "4. **Conclusion**: Since a solution to the 2-Hamiltonian-decomposition problem can be verified in polynomial time, this problem is in NP." + ] + }, + { + "cell_type": "markdown", + "id": "4658fd1b", + "metadata": {}, + "source": [ + "### C. (5 points) \n", + "Is the 2-Hamiltonian-decomposition problem NP-complete? If so, provide a proof." + ] + }, + { + "cell_type": "markdown", + "id": "d933ac58", + "metadata": {}, + "source": [ + "### solution\n", + "**Problem Statement for C**: Is the 2-Hamiltonian-decomposition problem NP-complete?\n", + "\n", + "1. **Understanding NP-Completeness**: For a problem to be NP-complete, it must satisfy two conditions:\n", + " - It is in NP (as established in question B).\n", + " - Every problem in NP can be reduced to it in polynomial time.\n", + "\n", + "2. **The Problem is in NP**: As established in the solution to question B, the 2-Hamiltonian-decomposition problem is in NP because any solution (two Hamiltonian cycles that decompose the graph) can be verified in polynomial time.\n", + "\n", + "3. **Reduction from an NP-Complete Problem**: To establish NP-completeness, we need to show that a known NP-complete problem can be reduced to the 2-Hamiltonian-decomposition problem in polynomial time. The Hamiltonian Cycle problem, which is known to be NP-complete, is a good candidate for this.\n", + "\n", + "4. **Reduction Approach**:\n", + " - **Hamiltonian Cycle to 2-Hamiltonian-Decomposition**: Assume we have a directed graph for which we want to determine if a Hamiltonian cycle exists (the Hamiltonian Cycle problem). We can construct a new graph where we duplicate this graph and add edges to connect corresponding vertices in the two graphs. This new graph is used for the 2-Hamiltonian-decomposition problem.\n", + " - **Interpreting the Solution**: If we can find a 2-Hamiltonian decomposition in this new graph, it implies the original graph has a Hamiltonian cycle (since one of the decompositions must correspond to a Hamiltonian cycle in the original graph).\n", + "\n", + "5. **Conclusion**: Given that we can reduce the Hamiltonian Cycle problem (an NP-complete problem) to the 2-Hamiltonian-decomposition problem in polynomial time, and that the 2-Hamiltonian-decomposition problem is in NP, we can conclude that the 2-Hamiltonian-decomposition problem is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "9bed956b", + "metadata": {}, + "source": [ + "### Q2 (20 Points)\n", + "The Directed Disjoint Spanning Trees Problem is defined as follows. We are given a directed graph ` G ` and an integer ` k `. The problem is to decide whether there exist ` k ` spanning trees ` T_1, T_2, dots, T_k ` in ` G ` such that each tree ` T_i ` is a directed spanning tree of ` G ` and any two trees ` T_i ` and ` T_j ` are edge-disjoint.\n", + "\n", + "Show that Directed Disjoint Spanning Trees is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "06fcd8d3", + "metadata": {}, + "source": [ + "### reflection\n", + "To solve the question regarding the Directed Disjoint Spanning Trees problem and prove that it is NP-complete, we follow a similar approach as with other NP-completeness proofs, which generally involve two steps:\n", + "\n", + "1. **Show the problem is in NP**: This means that if you are given a 'certificate' or solution (in this case, k edge-disjoint spanning trees), you can verify that it is indeed a solution in polynomial time.\n", + "\n", + "2. **Show the problem is NP-hard**: This is typically done by reducing a known NP-complete problem to the problem in question in polynomial time.\n", + "\n", + "### solution\n", + "\n", + "1. **Directed Disjoint Spanning Trees is in NP**:\n", + " - Given a directed graph ` G ` and ` k ` spanning trees ` T_1, T_2, dots, T_k `, we can verify whether each ` T_i ` is a spanning tree of ` G ` by checking that it includes all vertices of ` G ` and forms a tree (no cycles, and ` |V| - 1 ` edges where ` |V| ` is the number of vertices).\n", + " - We also need to verify that these trees are edge-disjoint. This can be done by comparing the edges of each pair of trees, which is a process that can be completed in polynomial time.\n", + "\n", + "2. **Directed Disjoint Spanning Trees is NP-hard**:\n", + " - To prove this, we need to reduce a known NP-complete problem to our problem. A suitable candidate for reduction could be the Edge-Disjoint Paths Problem, which is known to be NP-complete.\n", + " - **Reduction**: Given an instance of the Edge-Disjoint Paths Problem (a graph and a set of pairs of nodes to be connected by edge-disjoint paths), we transform it into an instance of the Directed Disjoint Spanning Trees problem. We could construct a graph where each pair of nodes in the Edge-Disjoint Paths Problem corresponds to a distinct spanning tree in the new problem. The solution to the Directed Disjoint Spanning Trees problem would then give us a solution to the Edge-Disjoint Paths Problem.\n", + " - This transformation needs to be shown to be doable in polynomial time.\n", + "\n", + "3. **Conclusion**: Since the Directed Disjoint Spanning Trees problem is in NP (as solutions can be verified in polynomial time) and it is NP-hard (as it can be reduced from an NP-complete problem), it is NP-complete.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "dbac3564", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Spanning trees are edge-disjoint: False\n", + "Spanning trees are valid: True\n" + ] + } + ], + "source": [ + "def is_edge_disjoint(spanning_trees):\n", + " edges_used = set()\n", + " for tree in spanning_trees:\n", + " for edge in tree:\n", + " if edge in edges_used:\n", + " return False\n", + " edges_used.add(edge)\n", + " return True\n", + "\n", + "def is_valid_spanning_tree(graph, tree):\n", + " visited = set()\n", + " for edge in tree:\n", + " visited.add(edge[0])\n", + " visited.add(edge[1])\n", + " \n", + " if len(visited) != len(graph):\n", + " return False\n", + "\n", + " # Convert tree to graph format for cycle checking\n", + " tree_graph = {node: [] for node in graph}\n", + " for (u, v) in tree:\n", + " tree_graph[u].append(v)\n", + "\n", + " return not has_cycle(tree_graph, list(tree_graph.keys())[0], -1, set())\n", + "\n", + "def has_cycle(graph, current, parent, visited):\n", + " visited.add(current)\n", + " for neighbor in graph[current]:\n", + " if neighbor not in visited:\n", + " if has_cycle(graph, neighbor, current, visited):\n", + " return True\n", + " elif neighbor != parent:\n", + " return True\n", + " return False\n", + "\n", + "# Example Graph and Spanning Trees\n", + "graph = {\n", + " 1: [2, 3],\n", + " 2: [4],\n", + " 3: [4],\n", + " 4: []\n", + "}\n", + "spanning_trees = [\n", + " [(1, 2), (2, 4), (1, 3)],\n", + " [(1, 3), (3, 4), (2, 4)]\n", + "]\n", + "\n", + "are_disjoint = is_edge_disjoint(spanning_trees)\n", + "are_valid = all(is_valid_spanning_tree(graph, tree) for tree in spanning_trees)\n", + "\n", + "print(\"Spanning trees are edge-disjoint:\", are_disjoint)\n", + "print(\"Spanning trees are valid:\", are_valid)\n" + ] + }, + { + "cell_type": "markdown", + "id": "dcb83455", + "metadata": {}, + "source": [ + "### Q3 (20 Points)\n", + "You are coordinating a multinational conference and need interpreters who can cover all the languages spoken by the delegates. There are `n` languages that need interpretation. You have received applications from `m` potential interpreters. Each interpreter is fluent in a subset of these `n` languages. The question is: For a given number `k ≤ m`, is it possible to hire at most `k` interpreters who collectively cover all `n` languages? We’ll call this the Optimal Interpreter Set problem.\n", + "\n", + "Show that the Optimal Interpreter Set problem is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "e34dabfc", + "metadata": {}, + "source": [ + "### reflection\n", + "\n", + "1. **Problem Complexity and Real-world Applications**: The question elegantly ties a real-world scenario - hiring interpreters for a conference - to a complex computational problem. It highlights how everyday problems, such as resource allocation and optimization, can be framed within the realm of computational complexity. This connection underscores the relevance of theoretical computer science concepts in practical situations.\n", + "\n", + "2. **Understanding NP-Completeness**: The question serves as a teaching tool for understanding what it means for a problem to be NP-complete. It requires recognizing that a problem is not only difficult to solve (NP-hard) but also that any proposed solution can be quickly verified (in NP). This dual requirement is a cornerstone of NP-completeness and is critical in the field of computational complexity.\n", + "\n", + "3. **Reduction Technique in Proving Complexity**: The solution approach, using a reduction from a known NP-complete problem (like the Set Cover problem), is a standard method in computational complexity. This approach demonstrates the interconnectedness of different problems in the NP class and how solving one NP-complete problem can potentially lead to solutions for others.\n", + "\n", + "4. **Insights into Optimization Challenges**: The problem involves an optimization challenge common in many fields, such as logistics, event planning, and technology. It illustrates the inherent difficulties in making optimal decisions when faced with constraints (like hiring a limited number of interpreters to cover all languages).\n", + "\n", + "5. **Implications for Algorithm Design**: The classification of a problem as NP-complete often implies that there is no known efficient algorithm to solve all instances of the problem. This understanding is crucial for algorithm designers and computer scientists, as it guides them in seeking approximate or heuristic solutions, especially for large-scale instances.\n", + "\n", + "6. **Educational Value in Complexity Theory**: Such questions are vital in educational contexts, where students of computer science can apply theoretical concepts to tangible problems. Understanding NP-completeness through practical examples enhances comprehension and appreciation of the complexity in computational tasks." + ] + }, + { + "cell_type": "markdown", + "id": "29f5fc4b", + "metadata": {}, + "source": [ + "### solution\n", + "\n", + "**Problem Statement**: Is it possible to hire at most `k` interpreters who collectively cover all `n` languages?\n", + "\n", + "1. **The Optimal Interpreter Set Problem is in NP**:\n", + " - Being in NP means that if you're given a solution (a set of at most `k` interpreters), you can verify that it is indeed a solution in polynomial time.\n", + " - Given a set of interpreters, you can quickly check whether all `n` languages are covered by these interpreters, which involves checking each interpreter's language skills and ensuring all required languages are included.\n", + "\n", + "2. **The Optimal Interpreter Set Problem is NP-hard**:\n", + " - To establish NP-hardness, we need to show that an already known NP-complete problem can be reduced to our problem in polynomial time.\n", + " - A suitable candidate for this reduction is the Set Cover problem, which is a well-known NP-complete problem. The Set Cover problem asks if a certain number of subsets (from a collection of subsets) can be chosen such that they cover a universal set.\n", + " - **Reduction from Set Cover to Optimal Interpreter Set**:\n", + " - Consider an instance of the Set Cover problem with a universal set `U` representing languages and a collection of subsets `S` where each subset represents the languages an interpreter can cover.\n", + " - The question in Set Cover is whether we can choose at most `k` subsets that cover all elements in `U`.\n", + " - This instance maps directly to our Optimal Interpreter Set problem: each subset in `S` represents an interpreter's language skills, and the goal is to find at most `k` interpreters to cover all languages in `U`.\n", + " - Since we can transform any instance of the Set Cover problem into an instance of the Optimal Interpreter Set problem, and the Set Cover problem is NP-complete, the Optimal Interpreter Set problem is NP-hard.\n", + "\n", + "3. **Conclusion**: As the Optimal Interpreter Set problem is in NP and NP-hard, it is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "102baf5a", + "metadata": {}, + "source": [ + "### Q4 (20 Points)\n", + "Imagine you are coordinating a multi-disciplinary academic conference, and you face the following challenge. The conference will cover a variety of n academic fields (like mathematics, physics, literature, etc.). You have received applications from m potential speakers. Each speaker specializes in a subset of these n fields. The question is: For a given number k < m, is it possible to invite at most k of the speakers and ensure that all n academic fields are covered by at least one speaker? We’ll call this the Academic Conference Speaker Selection Problem.\n", + "\n", + "Show that the Academic Conference Speaker Selection Problem is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "855d546b", + "metadata": {}, + "source": [ + "### reflection\n", + "\n", + "1. **Linking Theoretical and Practical Problems**: The problem bridges a gap between theoretical computer science and practical, real-world scenarios. It exemplifies how abstract concepts in computational complexity, such as NP-completeness, have direct applications in everyday situations like organizing a conference. This connection enhances the appreciation of theoretical computer science in solving tangible problems.\n", + "\n", + "2. **The Essence of NP-Completeness**: This problem serves as an educational tool for understanding NP-completeness. It illustrates the dual aspects of NP-complete problems: the difficulty in finding a solution efficiently for all cases (NP-hardness) and the ability to verify a given solution quickly (being in NP). This is a fundamental concept in computational complexity theory and critical for students and professionals in the field.\n", + "\n", + "3. **Reduction as a Proof Technique**: The approach to proving NP-completeness typically involves reducing a known NP-complete problem to the problem in question. This technique demonstrates how various seemingly unrelated problems in the NP class are interconnected, and solving one can potentially offer insights into solving others.\n", + "\n", + "4. **Real-World Optimization Challenges**: The problem is an example of optimization challenges faced in various fields, including event planning, resource allocation, and logistics. It underscores the inherent complexities in making optimal decisions within constraints (like selecting a limited number of speakers to cover all fields).\n", + "\n", + "5. **Algorithmic Implications**: Identifying a problem as NP-complete suggests that there is no known efficient solution for all instances of the problem. This understanding is crucial for algorithm designers, as it shifts the focus towards developing heuristic or approximate methods, especially for large or complex instances.\n", + "\n", + "6. **Educational Value and Research Implications**: Such questions are not only valuable in educational settings for teaching computational theory but also indicate ongoing research areas. Understanding the complexity of these problems is vital for researchers and practitioners in computer science, as it guides the search for new algorithms and computational methods." + ] + }, + { + "cell_type": "markdown", + "id": "617ddd26", + "metadata": {}, + "source": [ + "### solution\n", + "\n", + "**Problem Statement**: For a given number k < m, is it possible to invite at most k of the speakers and ensure that all n academic fields are covered by at least one speaker?\n", + "\n", + "1. **The Academic Conference Speaker Selection Problem is in NP**:\n", + " - Being in NP means that if you're given a solution (a set of at most `k` speakers), you can verify that it is indeed a solution in polynomial time.\n", + " - Given a set of speakers, you can quickly check whether all `n` academic fields are covered by these speakers, which involves checking each speaker's specialties and ensuring all required fields are included.\n", + "\n", + "2. **The Academic Conference Speaker Selection Problem is NP-hard**:\n", + " - To establish NP-hardness, we need to show that an already known NP-complete problem can be reduced to our problem in polynomial time.\n", + " - A suitable candidate for this reduction is the Set Cover problem, which is a well-known NP-complete problem. The Set Cover problem asks if a certain number of subsets (from a collection of subsets) can be chosen such that they cover a universal set.\n", + " - **Reduction from Set Cover to Academic Conference Speaker Selection**:\n", + " - Consider an instance of the Set Cover problem with a universal set `U` representing academic fields and a collection of subsets `S` where each subset represents the fields a speaker specializes in.\n", + " - The question in Set Cover is whether we can choose at most `k` subsets that cover all elements in `U`.\n", + " - This instance maps directly to our Academic Conference Speaker Selection problem: each subset in `S` represents a speaker's specialties, and the goal is to find at most `k` speakers to cover all fields in `U`.\n", + " - Since we can transform any instance of the Set Cover problem into an instance of the Academic Conference Speaker Selection problem, and the Set Cover problem is NP-complete, the Academic Conference Speaker Selection problem is NP-hard.\n", + "\n", + "3. **Conclusion**: As the Academic Conference Speaker Selection problem is in NP and NP-hard, it is NP-complete." + ] + }, + { + "cell_type": "markdown", + "id": "356338ec", + "metadata": {}, + "source": [ + "### Q5 (20 Points)\n", + "Suppose you are organizing a community volunteer event lasting `n` days, with `n - 1` different volunteer tasks to be completed, one each day. Each of the `n` volunteers has signed up to help, with each volunteer agreeing to take on exactly one task. However, every volunteer has certain days when they are unavailable due to other commitments (like work, classes, or personal matters). Let's label the volunteers ` V `in `{v_1, dots, v_n\\} `, the days ` D `in `{d_1, dots, d_n} `, and for each volunteer ` v_i `, there's a set of days ` T_i `subset `{d_1, dots, d_n} ` when they are not available to help. A volunteer cannot have ` T_i ` empty." + ] + }, + { + "cell_type": "markdown", + "id": "4a3f65fb", + "metadata": {}, + "source": [ + "### reflection\n", + "\n", + "1. **Practical Application of Graph Theory**: The question is a practical example of how graph theory, specifically flow network concepts like maximum flow, can be applied to everyday problems. It illustrates how abstract mathematical concepts are not just theoretical but have real-world applicability in areas such as scheduling and resource allocation.\n", + "\n", + "2. **Complexity of Scheduling Problems**: The problem highlights the inherent complexity in scheduling tasks, a common challenge in many fields ranging from event planning to project management. It underscores the necessity of systematic approaches and algorithms to handle such complexities efficiently.\n", + "\n", + "3. **Understanding Maximum Flow Problems**: By framing the scheduling issue as a maximum flow problem, the question provides a concrete context to understand and apply the principles of flow networks. This approach is educational, helping to grasp how maximum flow algorithms like the Ford-Fulkerson algorithm can be used to find optimal solutions in matching problems.\n", + "\n", + "4. **Exploring Feasibility and Constraints**: Part B of the question, which asks whether a perfect matching is always possible, delves into the exploration of constraints and feasibility in optimization problems. This aspect is crucial in decision-making processes, where understanding the limits and capabilities of a system or situation is essential.\n", + "\n", + "5. **Algorithmic Thinking and Problem Solving**: The task of expressing a real-world problem in terms of a maximum flow model demonstrates algorithmic thinking - a key skill in computer science and mathematics. It involves breaking down a problem into its fundamental components and understanding how these components interact within a system.\n", + "\n", + "6. **Educational Value in Applied Mathematics**: Such questions have high educational value, particularly in teaching applied mathematics and computer science. They bridge the gap between theoretical knowledge and practical application, encouraging students to apply mathematical concepts to solve real-life problems." + ] + }, + { + "cell_type": "markdown", + "id": "b9119110", + "metadata": {}, + "source": [ + "### A. (10 points) \n", + "Express this problem as a maximum flow problem that schedules the maximum number of matches between the volunteers and the days.\n" + ] + }, + { + "cell_type": "markdown", + "id": "3a17ac4d", + "metadata": {}, + "source": [ + "### solution \n", + "\n", + "1. **Construct a Flow Network**:\n", + " - Create a source node `S` and a sink node `T`.\n", + " - For each volunteer ` v_i `, create a node. Connect each volunteer node to the source node `S` with an edge of capacity 1. This represents that each volunteer can only be assigned to one day.\n", + " - For each day ` d_j `, create a node. Connect each day node to the sink node `T` with an edge of capacity 1. This represents that each day requires one volunteer.\n", + " - For each volunteer ` v_i `, and for each day ` d_j ` that they are available (i.e., ` d_j ` is not in ` T_i `), create an edge from the volunteer node to the day node with a capacity of 1. This represents the possibility of assigning volunteer ` v_i ` to day ` d_j `.\n", + "\n", + "2. **Find the Maximum Flow**:\n", + " - Use a maximum flow algorithm like Ford-Fulkerson to find the maximum flow in this network.\n", + " - The maximum flow value will be the maximum number of matches (volunteer-day assignments) you can make.\n", + "\n", + "In this setup, a flow of 1 through an edge from a volunteer to a day indicates that the volunteer is assigned to that day. The capacity constraints ensure that each volunteer is assigned to at most one day and each day has at most one volunteer. The maximum flow in this network is then the maximum number of such assignments you can make while respecting everyone's availability." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "583fb26d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The maximum number of volunteer-day is: 4\n" + ] + }, + { + "data": { + "image/png": "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+t9JuFxwwQW6++67ddlll5k9lDYpKyvTGWecoW7duun2229vNFhmf5mvgNzyDjhH1d9tV3X+zobb2bhKnuQUJfYYLE9CslyehvteXOfekiR/6eHLu/kDhrK35ofuF4Kptm7dqkWLFunbb781eyiIkOwv84/6aVZMhwx1GjFVRnWlSj95XZLqffxd+7h2qYpplypf6aE/Op02P9j9eCFJK1as0Nlnn60+ffro+eefb5A36u4PLtfhvHFkXvB9v1e588eoeN2/VfK/F7X7kanKu+9n+vbpmar6Zkujr113f3jvvfeUlpamMWPGqLy8PFy/bsRYNlCedtppWrduna6//nqtW7dOBw8ebPRxv/nNb3TLLbfovPPO04oVK/THP/5R/339da1/8Dr5K4rb/Prfv/OU/CUFShn1W6X/7Da5kzqq9NOVyl9+hwzDUKeR12r5v17S9ddfr927d9c+Lzs7W2eccYa+//57/fWvf9XLL7+sk08+WePGjdOTTz7Z5vGgaT6fT4FAQOXl5br77rsbBMuyKp/yflgInTzwPEmuBi1l9b48VX+7Vd6TRtSuj2zMgdyNktujmJTj6t2eV1ih8ipfaH8xRNz27dt12mmn6aabbtLixYu5LnQUqDs/HE1ijyGSy62qXZ83+ZiD338nX0mBYus0WcwP1nLgwAFJ0ldffaUJEyYoLy9P69at07Rp05S99j3lFpS0anulH7+myp0blHLO1Uq76EYZB6uU/487FDjQeEjMK6zQ08/9Xeecc44uv/xyvfzyy/J6vUH/Xmaz7EUn58+fry1btmjx4sVavHixYmNjNXToUF100UW67rrrlJycrC1btmjJkiW69tpra09cK0kduvbSFWPOUUnOy+p01i/a9PoxHTsr/dLZtT8Hqiu1/+3HFN+1n465Yq5cLpde+G+2OgRK5XK5dN1110mSnn/+ebVv314nnXSS1qxZI0nyer3q3r27fvvb3yonJ8eUNVeTJ0/W4MGDg/4manV1tWbMmBGiUYVGVVVV7T/XBMs//vGPmjt3riZPnqwb7rqv9vJYsZ26KL5bf5VvylanrF/WtpDlPwTMQ4GzcZVff6zyTdlqN+QieRLb17vPkLSzsFz9u3QI6e+GyCkvL9e8efM0duxYDRkyRNOmTZPP59PNN98c8mtCwzpyC8vV0pX27rgEuZPay19W1Oj9RsCv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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "from collections import defaultdict\n", + "\n", + "# This class represents a directed graph using adjacency matrix representation\n", + "class Graph:\n", + " def __init__(self, graph):\n", + " self.graph = graph # residual graph\n", + " self.ROW = len(graph)\n", + " \n", + " def BFS(self, s, t, parent):\n", + " visited = [False] * (self.ROW)\n", + " queue = []\n", + " queue.append(s)\n", + " visited[s] = True\n", + " \n", + " while queue:\n", + " u = queue.pop(0)\n", + " for ind, val in enumerate(self.graph[u]):\n", + " if visited[ind] == False and val > 0:\n", + " queue.append(ind)\n", + " visited[ind] = True\n", + " parent[ind] = u\n", + "\n", + " return True if visited[t] else False\n", + " \n", + " def FordFulkerson(self, source, sink):\n", + " parent = [-1] * (self.ROW)\n", + " max_flow = 0\n", + "\n", + " while self.BFS(source, sink, parent):\n", + " path_flow = float(\"Inf\")\n", + " s = sink\n", + " while(s != source):\n", + " path_flow = min(path_flow, self.graph[parent[s]][s])\n", + " s = parent[s]\n", + "\n", + " max_flow += path_flow\n", + "\n", + " v = sink\n", + " while(v != source):\n", + " u = parent[v]\n", + " self.graph[u][v] -= path_flow\n", + " self.graph[v][u] += path_flow\n", + " v = parent[v]\n", + "\n", + " return max_flow\n", + "\n", + "# Function to draw the flow network\n", + "def draw_flow_network(graph, source, sink, n):\n", + " G = nx.DiGraph()\n", + "\n", + " # Adding nodes\n", + " G.add_node(\"Source\", pos=(0, 2))\n", + " G.add_node(\"Sink\", pos=(4, 2))\n", + " for i in range(n):\n", + " G.add_node(f\"V{i+1}\", pos=(1, i + 1))\n", + " G.add_node(f\"D{i+1}\", pos=(3, i + 1))\n", + "\n", + " # Adding edges\n", + " for i in range(n):\n", + " G.add_edge(\"Source\", f\"V{i+1}\", capacity=1)\n", + " for j in range(n):\n", + " if graph[i+1][n+1+j] == 1:\n", + " G.add_edge(f\"V{i+1}\", f\"D{j+1}\", capacity=1)\n", + " G.add_edge(f\"D{i+1}\", \"Sink\", capacity=1)\n", + "\n", + " pos = nx.get_node_attributes(G, 'pos')\n", + " nx.draw(G, pos, with_labels=True)\n", + " labels = nx.get_edge_attributes(G, 'capacity')\n", + " nx.draw_networkx_edge_labels(G, pos, edge_labels=labels)\n", + "\n", + "# Number of volunteers and days\n", + "n = 4\n", + "\n", + "# Volunteers' availability\n", + "availability = [\n", + " [1, 2],\n", + " [0, 2],\n", + " [1, 3],\n", + " [0, 3]\n", + "]\n", + "\n", + "# Create a graph with n volunteers and n days\n", + "graph = [[0] * (2*n + 2) for _ in range(2*n + 2)]\n", + "source = 0\n", + "sink = 2*n + 1\n", + "\n", + "# Connect source to volunteers and volunteers to days\n", + "for i in range(n):\n", + " graph[source][i+1] = 1\n", + " for day in availability[i]:\n", + " graph[i+1][n+1+day] = 1\n", + "\n", + "# Connect days to sink\n", + "for i in range(n):\n", + " graph[n+1+i][sink] = 1\n", + "\n", + "g = Graph(graph)\n", + "max_assignments = g.FordFulkerson(source, sink)\n", + "print(\"The maximum number of volunteer-day is:\", max_assignments)\n", + "\n", + "# Draw the flow network\n", + "draw_flow_network(graph, source, sink, n)\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "id": "dce91b32", + "metadata": {}, + "source": [ + "### B. (10 points) \n", + "Can all `n` volunteers always be matched with one of the `n` days? Prove that it can or cannot.\n" + ] + }, + { + "cell_type": "markdown", + "id": "93f361e1", + "metadata": {}, + "source": [ + "### Solution:\n", + "\n", + "1. **Understanding the Problem in Graph Terms**: This problem can be modeled as a bipartite graph where one set of nodes represents the people, and the other set represents the nights. An edge connects a person to a night if the person is available to cook on that night.\n", + "\n", + "2. **Applying Hall's Marriage Theorem**: This theorem provides a necessary and sufficient condition for a perfect matching in a bipartite graph. It states that a perfect matching exists if and only if for every subset of nodes ` A ` in one partition (say, people), the number of neighbors (nights they can cook) is at least as large as the size of ` A `.\n", + "\n", + "3. **Analysis**:\n", + " - If for any subset of people, the number of nights they are collectively available to cook is less than the number of people in the subset, then it is impossible to assign each person a unique night to cook.\n", + " - Conversely, if for every subset of people, the number of available nights is at least as large as the number of people in the subset, then it is possible to assign each person a unique night.\n", + "\n", + "4. **Conclusion**: All ` n ` people can be matched with one of the ` n ` nights if and only if for every subset of people, the number of nights they can collectively cook is at least as large as the number of people in the subset. Without knowing the specific availabilities of each person, we cannot guarantee that a perfect matching always exists. It depends on the specific sets of nights ` S_i ` when each person ` p_i ` is not able to cook." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4ce86e2a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The maximum number of person-night is: 4\n" + ] + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", + "from collections import defaultdict\n", + "\n", + "# This class represents a directed graph using adjacency matrix representation\n", + "class Graph:\n", + " def __init__(self, graph):\n", + " self.graph = graph # residual graph\n", + " self.ROW = len(graph)\n", + " \n", + " def BFS(self, s, t, parent):\n", + " visited = [False] * (self.ROW)\n", + " queue = []\n", + " queue.append(s)\n", + " visited[s] = True\n", + " \n", + " while queue:\n", + " u = queue.pop(0)\n", + " for ind, val in enumerate(self.graph[u]):\n", + " if visited[ind] == False and val > 0:\n", + " queue.append(ind)\n", + " visited[ind] = True\n", + " parent[ind] = u\n", + "\n", + " return True if visited[t] else False\n", + " \n", + " def FordFulkerson(self, source, sink):\n", + " parent = [-1] * (self.ROW)\n", + " max_flow = 0\n", + "\n", + " while self.BFS(source, sink, parent):\n", + " path_flow = float(\"Inf\")\n", + " s = sink\n", + " while(s != source):\n", + " path_flow = min(path_flow, self.graph[parent[s]][s])\n", + " s = parent[s]\n", + "\n", + " max_flow += path_flow\n", + "\n", + " v = sink\n", + " while(v != source):\n", + " u = parent[v]\n", + " self.graph[u][v] -= path_flow\n", + " self.graph[v][u] += path_flow\n", + " v = parent[v]\n", + "\n", + " return max_flow\n", + "\n", + "# Function to draw the flow network\n", + "def draw_flow_network(graph, source, sink, n):\n", + " G = nx.DiGraph()\n", + "\n", + " # Adding nodes\n", + " G.add_node(\"Source\", pos=(0, 2))\n", + " G.add_node(\"Sink\", pos=(4, 2))\n", + " for i in range(n):\n", + " G.add_node(f\"P{i+1}\", pos=(1, n-i)) # People\n", + " G.add_node(f\"N{i+1}\", pos=(3, n-i)) # Nights\n", + "\n", + " # Adding edges\n", + " for i in range(n):\n", + " G.add_edge(\"Source\", f\"P{i+1}\", capacity=1)\n", + " for j in range(n):\n", + " if graph[i+1][n+1+j] == 1:\n", + " G.add_edge(f\"P{i+1}\", f\"N{j+1}\", capacity=1)\n", + " G.add_edge(f\"N{i+1}\", \"Sink\", capacity=1)\n", + "\n", + " pos = nx.get_node_attributes(G, 'pos')\n", + " nx.draw(G, pos, with_labels=True)\n", + " labels = nx.get_edge_attributes(G, 'capacity')\n", + " nx.draw_networkx_edge_labels(G, pos, edge_labels=labels)\n", + "\n", + "# Number of people and nights\n", + "n = 4\n", + "\n", + "# People's availability: person i is available on nights in availability[i]\n", + "availability = [\n", + " [1, 2],\n", + " [0, 2],\n", + " [1, 3],\n", + " [0, 3]\n", + "]\n", + "\n", + "# Create a graph with n people and n nights\n", + "graph = [[0] * (2*n + 2) for _ in range(2*n + 2)]\n", + "source = 0\n", + "sink = 2*n + 1\n", + "\n", + "# Connect source to people and people to nights\n", + "for i in range(n):\n", + " graph[source][i+1] = 1\n", + " for night in availability[i]:\n", + " graph[i+1][n+1+night] = 1\n", + "\n", + "# Connect nights to sink\n", + "for i in range(n):\n", + " graph[n+1+i][sink] = 1\n", + "\n", + "g = Graph(graph)\n", + "max_assignments = g.FordFulkerson(source, sink)\n", + "print(\"The maximum number of person-night is:\", max_assignments)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65eebb6b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment5.ipynb b/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment5.ipynb new file mode 100644 index 0000000..b4831b1 --- /dev/null +++ b/Submissions/Yuxuan_Zhang_002778556/YuxuanZhang_Assignment5.ipynb @@ -0,0 +1,848 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6e21846e", + "metadata": {}, + "source": [ + "# INFO 6205 - Program Structure and Algorithms\n", + "# Worked Assignment 5 Solutions\n", + "### student Name: yuxuan zhang\n", + "### Professor: Nik Bear Brown\n", + "### Date: 12/01/2023" + ] + }, + { + "cell_type": "markdown", + "id": "49cd0a49", + "metadata": {}, + "source": [ + "## Q1 (10 Points)\n", + "In a collectible card game where players receive cards randomly for completing challenges. There are n distinct types of cards. After completing each challenge, a player receives one card, chosen randomly and with equal probability from the n types. What is the expected number of challenges a player must complete to collect at least one of each type of card?" + ] + }, + { + "cell_type": "markdown", + "id": "64d17717", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **The Nature of Randomness:** The problem highlights how randomness doesn't equate to uniform distribution of outcomes over a short period. A player might receive duplicate cards before completing the set, illustrating the unpredictable nature of random events.\n", + "\n", + "2. **Non-Linear Progression:** The expected number of challenges needed increases non-linearly as the player collects more types. It's easier to find a new card at the beginning when many types are uncollected, but it becomes progressively harder as the collection grows, demonstrating diminishing returns.\n", + "\n", + "3. **Harmonic Numbers:** The solution introduces the concept of Harmonic numbers, which are significant in various areas of mathematics and computer science. This problem provides a practical application for these numbers, showing their relevance in calculating expectations in random distributions.\n", + "\n", + "4. **Applications Beyond Games:** While framed in the context of a game, the principles apply to many real-world scenarios. For instance, it could model situations in ecology (like expecting different species in samples), marketing (like collecting a variety of customer feedback), or even in computer science (like hashing algorithms and collision avoidance).\n", + "\n", + "5. **Educational Value:** This problem is a classic example used in teaching probability and statistics. It helps students understand complex concepts like expected value and probability distributions through a relatable and engaging example.\n" + ] + }, + { + "cell_type": "markdown", + "id": "39a73740", + "metadata": {}, + "source": [ + "## Solution\n", + "To find the expected number of challenges a player must complete to collect at least one of each type of card in a set of $n$ distinct types, we can use the concept of expected value in probability.\n", + "\n", + "The problem is akin to the \"Coupon Collector's Problem.\" Here's how it works:\n", + "\n", + "1. **First Card:** The first card a player collects is always a new type, so it only takes 1 challenge to get the first new card.\n", + "\n", + "2. **Second Card:** For the second card, there are $n-1$ new types out of $n$ total types. The probability of getting a new type in each challenge is$\\frac{n-1}{n}$. The expected number of challenges to get a new card is the reciprocal of this probability, which is $\\frac{n}{n-1}$.\n", + "\n", + "3. **Third Card:** Similarly, for the third card, the probability of getting a new type is $\\frac{n-2}{n}$, and the expected number of challenges is $\\frac{n}{n-2}$.\n", + "\n", + "4. **Continuing this Pattern:** This pattern continues until the player collects all $n$ types. For the $k$-th card, where $k$ ranges from 1 to $n$, the expected number of challenges to get a new card is $\\frac{n}{n-(k-1)}$.\n", + "\n", + "The total expected number of challenges is the sum of the expected values for each of these stages:\n", + "\n", + "$\n", + "\\text{Expected number of challenges} = \\sum_{k=1}^{n} \\frac{n}{n-(k-1)} = n \\left( \\frac{1}{n} + \\frac{1}{n-1} + \\frac{1}{n-2} + \\ldots + \\frac{1}{1} \\right)\n", + "$\n", + "\n", + "This sum is the nth Harmonic number, denoted as $H_n$. Therefore, the formula becomes:\n", + "\n", + "$\n", + "\\text{Expected number of challenges} = n \\times H_n\n", + "$\n", + "\n", + "Where $ H_n = 1 + \\frac{1}{2} + \\frac{1}{3} + \\ldots + \\frac{1}{n} $.\n", + "\n", + "This formula gives us the expected number of challenges a player must complete to collect at least one of each type of card." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "cab142b2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "19" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import random\n", + "\n", + "def coupon_collector(n):\n", + " \"\"\"\n", + " Simulate the coupon collector's problem for n different types of coupons (or cards).\n", + " \n", + " Args:\n", + " n (int): The total number of distinct coupon types.\n", + "\n", + " Returns:\n", + " int: The total number of coupons collected to complete the set.\n", + " \"\"\"\n", + " collected_types = [False] * n\n", + " num_collected = 0\n", + " num_challenges = 0\n", + "\n", + " while num_collected < n:\n", + " num_challenges += 1\n", + " card_type = random.randint(0, n-1) # Simulate obtaining a random card type\n", + " if not collected_types[card_type]:\n", + " collected_types[card_type] = True\n", + " num_collected += 1\n", + "\n", + " return num_challenges\n", + "\n", + "# Example usage\n", + "n = 10 # Let's say there are 10 different types of cards\n", + "total_challenges = coupon_collector(n)\n", + "total_challenges" + ] + }, + { + "cell_type": "markdown", + "id": "b12c9d44", + "metadata": {}, + "source": [ + "## Q2 (10 Points) \n", + "PushPush is a 2-D pushing-blocks game with the following rules:\n", + "#### Initial Setup:\n", + "A rectangular grid is set up with several single-cell tiles placed at various positions. A robot is also positioned on a designated cell within this grid.\n", + "\n", + "#### Robot Movement:\n", + "The robot has the capability to move to any adjacent cell, provided the cell is either vacant or contains a movable single-cell tile.\n", + "\n", + "#### Tile Sliding:\n", + "In this version of the game, when the robot pushes an adjacent tile, the tile moves exactly one cell in the direction of the push. This differs from the traditional PushPush mechanic where the tile moves to the farthest possible extent.\n", + "\n", + "#### Tile Merging:\n", + "When a tile is pushed into another tile, they merge to form a larger tile that occupies two cells. These merged tiles become immovable and cannot be traversed.\n", + "\n", + "#### Goal:\n", + "The aim of the game is to maneuver the robot to a specific target cell, which is initially inaccessible due to the placement of the tiles.\n", + "\n", + "#### Solution Specification:\n", + "\n", + "- MoveRobot(x, y): Command to move the robot from its current position to the coordinates (x, y).\n", + "- SlideTile(x, y): Command to slide a tile one cell towards the specified coordinates (x, y).\n", + "- CheckGoal(robot): Function to determine if the robot has reached its target position.\n", + "\n", + "#### Example Problem Statement:\n", + "\"Given a particular arrangement of tiles and a robot on a rectangular grid, devise a sequence of moves that will allow the robot to reach a designated goal cell.\"" + ] + }, + { + "cell_type": "markdown", + "id": "33bba622", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **Strategic Thinking and Planning:** The game requires players to think several steps ahead. Since tiles merge into immovable objects when pushed together, each move can significantly alter the playing field. Players must plan their moves carefully to avoid creating obstacles that could block the path to the goal.\n", + "\n", + "2. **Spatial Reasoning:** The game is a great exercise in spatial reasoning. Players need to visualize the effects of their moves on the grid, considering how sliding tiles and merging them will change the layout.\n", + "\n", + "3. **Algorithmic Thinking:** From a computational perspective, this game presents an interesting problem in pathfinding and state-space search. Finding the most efficient sequence of moves to reach the goal is akin to solving a puzzle, where each move changes the state of the game board. It's a practical illustration of concepts like search algorithms, heuristics, and optimization.\n", + "\n", + "4. **Complexity from Simple Rules:** This game is a classic example of how a set of simple rules can create a complex and challenging puzzle. The mechanics of moving the robot and sliding tiles are straightforward, but the emergent gameplay from these simple interactions can be deeply engaging and complex.\n", + "\n", + "5. **Educational Value:** For educational purposes, this game can be used to teach problem-solving, logical reasoning, and even basic programming concepts. It can be an excellent tool for engaging students in computational thinking.\n", + "\n", + "6. **Adaptability and Variability:** The game's rules are simple yet flexible, allowing for a wide range of puzzles and difficulties. This adaptability makes it suitable for a variety of skill levels, from beginners to advanced players." + ] + }, + { + "cell_type": "markdown", + "id": "1724169f", + "metadata": {}, + "source": [ + "## Solution\n", + "To provide a solution for the modified PushPush game, we would need a specific grid layout including the starting positions of the robot and the tiles, as well as the target position for the robot. The solution would involve a sequence of `MoveRobot(x, y)` and `SlideTile(x, y)` commands to navigate the robot to the goal while managing the positions and mergers of the tiles.\n", + "\n", + "Since the game is a puzzle, the solution can vary widely depending on the initial setup and there's often more than one way to solve it. In more complex setups, finding the optimal solution may require algorithmic approaches, such as depth-first search, breadth-first search, or even more advanced pathfinding algorithms like A*.\n", + "\n", + "1. **Grid Size:** 4x4.\n", + "2. **Robot's Starting Position:** (0, 0) - top left corner.\n", + "3. **Tiles' Positions:** \n", + " - Tile 1 at (1, 0)\n", + " - Tile 2 at (2, 0)\n", + "4. **Goal Position for the Robot:** (3, 0) - far right on the top row.\n", + "\n", + "The objective is to move the robot to (3, 0). However, the path is blocked by two tiles. The robot can push these tiles to clear the path, but if pushed together, they will merge and block the path.\n", + "\n", + "#### Solution Steps:\n", + "\n", + "1. `MoveRobot(1, 0)`: Move the robot to the position of Tile 1.\n", + "2. `SlideTile(2, 0)`: Push Tile 1 to the right. Now, Tile 1 is at (2, 0), and the robot is at (1, 0).\n", + "3. `MoveRobot(2, 0)`: Move the robot to the position of Tile 2.\n", + "4. `SlideTile(3, 0)`: Push Tile 2 to the right. Now, Tile 2 is at (3, 0), and the robot is at (2, 0).\n", + "5. `MoveRobot(3, 0)`: Finally, move the robot to the goal position.\n", + "\n", + "This sequence of moves allows the robot to reach the goal without merging the tiles, thereby solving the puzzle for this particular setup. Keep in mind that solutions can vary greatly based on the initial configuration of the grid, the robot, and the tiles." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b395ce7f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MoveRobot from (0, 0) to (1, 0)\n", + "SlideTile from (1, 0) to (2, 0)\n", + "MoveRobot from (1, 0) to (2, 0)\n", + "SlideTile from (2, 0) to (3, 0)\n", + "MoveRobot from (2, 0) to (0, 3)\n" + ] + }, + { + "data": { + "text/plain": [ + "([[0, 1, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], (0, 3))" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def move_robot(robot_pos, new_pos):\n", + " \"\"\"\n", + " Simulate moving the robot to a new position.\n", + " \"\"\"\n", + " print(f\"MoveRobot from {robot_pos} to {new_pos}\")\n", + " return new_pos\n", + "\n", + "def slide_tile(tile_pos, new_pos, grid):\n", + " \"\"\"\n", + " Simulate sliding a tile to a new position.\n", + " \"\"\"\n", + " print(f\"SlideTile from {tile_pos} to {new_pos}\")\n", + " grid[new_pos[0]][new_pos[1]] = grid[tile_pos[0]][tile_pos[1]]\n", + " grid[tile_pos[0]][tile_pos[1]] = 0\n", + "\n", + "def solve_pushpush(grid, robot_pos, goal_pos):\n", + " \"\"\"\n", + " Solve a simple PushPush puzzle.\n", + " \"\"\"\n", + " # First, move the robot to the first tile\n", + " robot_pos = move_robot(robot_pos, (1, 0))\n", + "\n", + " # Slide the first tile to the right\n", + " slide_tile((1, 0), (2, 0), grid)\n", + "\n", + " # Move the robot to the second tile\n", + " robot_pos = move_robot(robot_pos, (2, 0))\n", + "\n", + " # Slide the second tile to the goal position\n", + " slide_tile((2, 0), (3, 0), grid)\n", + "\n", + " # Finally, move the robot to the goal\n", + " robot_pos = move_robot(robot_pos, goal_pos)\n", + "\n", + " return grid, robot_pos\n", + "\n", + "# Initial grid setup\n", + "grid = [[0 for _ in range(4)] for _ in range(4)]\n", + "grid[0][1] = 1 # Tile 1\n", + "grid[0][2] = 2 # Tile 2\n", + "\n", + "# Robot's starting position and goal position\n", + "robot_pos = (0, 0)\n", + "goal_pos = (0, 3)\n", + "\n", + "# Solve the puzzle\n", + "final_grid, final_robot_pos = solve_pushpush(grid, robot_pos, goal_pos)\n", + "final_grid, final_robot_pos" + ] + }, + { + "cell_type": "markdown", + "id": "f39fe0cf", + "metadata": {}, + "source": [ + "## Q3 (10 Points)\n", + "What is a 'steady state' in a Hopfield Network?" + ] + }, + { + "cell_type": "markdown", + "id": "ba8351f9", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **Understanding Neural Network Dynamics:** The 'steady state' in a Hopfield Network exemplifies how neural networks can reach equilibrium. This state is achieved when the network's neuron activations stop changing, indicating that the network has settled into a stable pattern. This concept is crucial for understanding how some neural networks process and stabilize information.\n", + "\n", + "2. **Association with Memory and Pattern Recognition:** Hopfield Networks are often used to model associative memory. The steady state is significant because it usually corresponds to a pattern or memory the network has stored. This property illustrates the network's ability to recall specific patterns from incomplete or noisy inputs, making it a powerful tool for pattern recognition tasks.\n", + "\n", + "3. **Convergence and Stability Analysis:** The study of how and when Hopfield Networks reach a steady state involves convergence and stability analysis, which are key topics in the study of dynamic systems. Understanding these aspects is crucial for designing and applying these networks effectively.\n", + "\n", + "4. **Implications for Learning and Memory in Biological Systems:** The concept of a steady state in neural networks like Hopfield's also provides insights into how learning and memory might work in biological systems. It offers a simplified model for how the brain might store and recall information.\n", + "\n", + "5. **Challenges in Network Design:** Designing a Hopfield Network to ensure it reaches a desired steady state (especially in the presence of multiple stable states) poses interesting challenges. It requires careful consideration of network structure, initial conditions, and the learning algorithm used to set the weights.\n", + "\n", + "6. **Applications in Computing:** Beyond theoretical interest, the steady state phenomenon in Hopfield Networks has practical applications in solving optimization problems, error correction in data transmission, and even in developing algorithms for content-addressable memory systems." + ] + }, + { + "cell_type": "markdown", + "id": "7787c34d", + "metadata": {}, + "source": [ + "## Solution\n", + "\n", + "1. **Setting up a Hopfield Network:** Define the network with a set of neurons and initialize their states.\n", + "2. **Defining the Connection Weights:** Establish the connection weights between neurons, which could be based on a set of patterns that the network is meant to learn and recall.\n", + "3. **Simulating the Network Dynamics:** Run the network dynamics by updating the states of neurons iteratively based on the input and the network's weight matrix.\n", + "4. **Observing the Steady State:** After several iterations, the network should settle into a steady state, where further updates do not change the neurons' states." + ] + }, + { + "cell_type": "markdown", + "id": "0539b333", + "metadata": {}, + "source": [ + "## Q4 (15 Points) \n", + "Does the payoff matrix for the 'Prisoner's Dilemma' game contain any Nash equilibria?" + ] + }, + { + "cell_type": "markdown", + "id": "191647bb", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **Understanding Nash Equilibrium:** The concept of Nash equilibrium, a fundamental principle in game theory, illustrates how in certain strategic scenarios, players can reach a state where no one benefits from changing their strategy unilaterally. Understanding whether a game has a Nash equilibrium is crucial for predicting the outcomes and behaviors of rational players.\n", + "\n", + "2. **Insights into Human Behavior:** These types of questions, especially in the context of the \"Prisoner's Dilemma,\" provide deep insights into human cooperation, trust, and conflict. They demonstrate how individual rationality can lead to collective irrationality, where players might not achieve the best collective or individual outcome.\n", + "\n", + "3. **Application Across Disciplines:** The search for Nash equilibria in various games is not just limited to theoretical exercises. It has practical applications in economics, politics, sociology, and evolutionary biology, helping to model and understand competitive and cooperative interactions in these fields.\n", + "\n", + "4. **Strategic Thinking and Real-World Implications:** Analyzing games like the \"Prisoner's Dilemma\" encourages strategic thinking, showcasing how the choices of others impact an individual's decision-making process. It also reflects real-world scenarios where individuals or groups must choose between cooperative and selfish behaviors.\n", + "\n", + "5. **Complexity of Decision-Making:** These games underline the complexity of decision-making in scenarios where outcomes depend not only on one's actions but also on the actions of others. It emphasizes the importance of anticipating others' decisions in strategic planning.\n", + "\n", + "6. **Educational Value in Game Theory:** Exploring Nash equilibria in game theory problems is a valuable educational tool, helping students and learners grasp the intricate nature of strategic interactions and the mathematical underpinnings of decision-making processes." + ] + }, + { + "cell_type": "markdown", + "id": "c58eb0c4", + "metadata": {}, + "source": [ + "## Solution\n", + "To determine if the payoff matrix of a game like \"TradeTrade\" or the \"Prisoner's Dilemma\" has any Nash equilibria, we need to analyze the specific details of the payoff matrix. However, I can demonstrate how this is typically done using the classic example of the \"Prisoner's Dilemma.\"\n", + "\n", + "In the \"Prisoner's Dilemma,\" two players (prisoners) must independently decide whether to cooperate with each other or to betray. The payoff matrix usually looks something like this:\n", + "\n", + "| | Cooperate | Betray |\n", + "|------------|-----------|--------|\n", + "| Cooperate | R, R | S, T |\n", + "| Betray | T, S | P, P |\n", + "\n", + "Where:\n", + "- T is the temptation payoff (received if a player betrays the other while the other cooperates)\n", + "- R is the reward for mutual cooperation\n", + "- P is the punishment for mutual betrayal\n", + "- S is the sucker's payoff (received if a player cooperates while the other betrays)\n", + "\n", + "Typically, the values are set so that T > R > P > S.\n", + "\n", + "A Nash equilibrium occurs when each player's strategy is optimal given the other player's strategy. In the \"Prisoner's Dilemma,\" betraying is always the best individual strategy, regardless of what the other player does. This is because:\n", + "- If the other player cooperates, betraying gives the higher temptation payoff T (compared to the lower reward R for mutual cooperation).\n", + "- If the other player betrays, betraying avoids the sucker's payoff S and leads to the punishment payoff P, which is better than being the sucker.\n", + "\n", + "Therefore, the Nash equilibrium for the \"Prisoner's Dilemma\" is for both players to betray each other." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b1e7637f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[('Betray', 'Betray')]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def find_nash_equilibria(payoff_matrix):\n", + " \"\"\"\n", + " Find Nash equilibria in a 2x2 payoff matrix for a two-player game.\n", + "\n", + " Args:\n", + " payoff_matrix (list of lists): The payoff matrix of the game.\n", + " Format: [[(R, R), (S, T)], [(T, S), (P, P)]]\n", + " where R, S, T, P are the payoffs for Cooperate/Betray choices.\n", + "\n", + " Returns:\n", + " list of tuples: List of strategy pairs that are Nash equilibria.\n", + " \"\"\"\n", + " nash_equilibria = []\n", + "\n", + " # Check each player's strategy against the other's\n", + " for i in range(2):\n", + " for j in range(2):\n", + " player1_strategy = payoff_matrix[i][0][0] >= payoff_matrix[1-i][0][0]\n", + " player2_strategy = payoff_matrix[0][j][1] >= payoff_matrix[0][1-j][1]\n", + " if player1_strategy and player2_strategy:\n", + " nash_equilibria.append((\"Cooperate\" if i == 0 else \"Betray\", \n", + " \"Cooperate\" if j == 0 else \"Betray\"))\n", + "\n", + " return nash_equilibria\n", + "\n", + "# Example: Prisoner's Dilemma Payoff Matrix\n", + "# T > R > P > S, for example, T=5, R=3, P=1, S=0\n", + "payoff_matrix = [[(3, 3), (0, 5)], [(5, 0), (1, 1)]]\n", + "\n", + "# Find Nash Equilibria\n", + "nash_equilibria = find_nash_equilibria(payoff_matrix)\n", + "nash_equilibria" + ] + }, + { + "cell_type": "markdown", + "id": "2b9a8add", + "metadata": {}, + "source": [ + "## Q5 (15 Points) \n", + "Consider a fair coin (with a heads and a tails side, each having an equal probability of landing). How many independent flips X are needed until the first heads is flipped? Express the expectation as a function of the probability of flipping heads." + ] + }, + { + "cell_type": "markdown", + "id": "807c0085", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **Understanding Geometric Distribution:** This problem is a classic example of geometric distribution, which describes the number of Bernoulli trials needed for a success to occur. It's a fundamental concept in probability theory, illustrating how distributions can model real-world random processes.\n", + "\n", + "2. **Simplicity of Expected Value Calculation:** The problem demonstrates how the expected value can be easily calculated in certain probability distributions. In this case, with a fair coin (probability \\( p = \\frac{1}{2} \\) for heads), the expected number of flips is simply the reciprocal of the probability of success (\\( E[X] = \\frac{1}{p} \\)).\n", + "\n", + "3. **Intuitive Understanding of Averages:** The result, that on average it takes two flips to get a heads, aligns well with our intuitive understanding of probability. This provides an intuitive check that our mathematical understanding of probability corresponds to what we might expect in real life.\n", + "\n", + "4. **Application in Decision Making:** This type of problem, though simple, is analogous to many real-world scenarios where decisions or predictions are made based on the probability of certain outcomes. It highlights the importance of understanding the underlying probability distributions for effective decision-making.\n", + "\n", + "5. **Exploring Variability in Random Processes:** While the expected number of flips is 2, the actual number in any given trial could be more or less. This underscores the concept of variability in random processes and the distinction between expected and actual outcomes.\n", + "\n", + "6. **Educational Value:** Problems like this are widely used in teaching basic probability and statistics, as they provide a clear and simple example of how probability theory can be applied to calculate real-world quantities." + ] + }, + { + "cell_type": "markdown", + "id": "826daea5", + "metadata": {}, + "source": [ + "## Solution\n", + "To solve this problem, we need to calculate the expected number of coin flips $ X $ until the first heads is flipped on a fair coin. This is a classic example of a geometric distribution, where we are finding the expected number of trials until the first success.\n", + "\n", + "In a fair coin, the probability of flipping heads (success) is $ p = \\frac{1}{2} $.\n", + "\n", + "The expectation (or expected value) $ E[X] $ of a geometrically distributed random variable, where each trial is independent, is given by:\n", + "\n", + "$ E[X] = \\frac{1}{p} $\n", + "\n", + "Substituting the probability of flipping heads $ p = \\frac{1}{2} $, we get:\n", + "\n", + "$ E[X] = \\frac{1}{\\frac{1}{2}} = 2 $\n", + "\n", + "So, the expected number of coin flips until the first heads is flipped is 2. This means, on average, you would expect to flip the coin twice to get the first heads." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "a5f141f2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1.9772" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import random\n", + "\n", + "def flip_until_heads():\n", + " \"\"\"\n", + " Simulate flipping a fair coin until heads is flipped.\n", + " Count the number of flips needed.\n", + " \"\"\"\n", + " count = 0\n", + " while True:\n", + " count += 1\n", + " # Simulate a coin flip: 0 for tails, 1 for heads\n", + " if random.randint(0, 1) == 1:\n", + " break\n", + " return count\n", + "\n", + "# Simulate the process multiple times to get an average\n", + "num_simulations = 10000\n", + "total_flips = sum(flip_until_heads() for _ in range(num_simulations))\n", + "\n", + "# Calculate the average number of flips\n", + "average_flips = total_flips / num_simulations\n", + "average_flips" + ] + }, + { + "cell_type": "markdown", + "id": "de7dd93e", + "metadata": {}, + "source": [ + "## Q6 (15 Points) \n", + "Consider a Las Vegas algorithm that searches for a specific element in an unsorted array by randomly picking elements to check. Propose a Monte Carlo version of this algorithm." + ] + }, + { + "cell_type": "markdown", + "id": "e69f1c34", + "metadata": {}, + "source": [ + "## Reflection\n", + "\n", + "1. **Understanding Las Vegas vs. Monte Carlo Algorithms:** The core difference between these two types of algorithms lies in their approach to randomness. Las Vegas algorithms always produce a correct or optimal result, but their running time is variable. Monte Carlo algorithms, on the other hand, have a fixed running time but only offer a probabilistic guarantee of correctness. This distinction is crucial in applications where the balance between accuracy and speed is critical.\n", + "\n", + "2. **Trade-offs in Algorithm Design:** The process of converting a Las Vegas algorithm to a Monte Carlo one involves deliberate trade-offs. While Monte Carlo algorithms can significantly improve computational efficiency, this comes at the cost of reduced accuracy or certainty in the results. This trade-off is a common theme in many areas of computer science and engineering.\n", + "\n", + "3. **Applications in Complex Problems:** In many real-world scenarios, especially those involving large datasets or complex computations, the deterministic approach of a Las Vegas algorithm becomes impractical. Monte Carlo methods can provide a viable alternative, offering good enough results within a reasonable timeframe.\n", + "\n", + "4. **Probabilistic Thinking:** The Monte Carlo approach requires a shift from deterministic to probabilistic thinking. It involves understanding and quantifying uncertainty and risk, which is a fundamental aspect of statistical and probabilistic analysis.\n", + "\n", + "5. **Algorithmic Efficiency vs. Result Accuracy:** This transformation highlights the balance between algorithmic efficiency and the accuracy of results. In many practical applications, such as real-time processing or large-scale data analysis, a faster, approximate answer may be more valuable than a slow, exact one.\n", + "\n", + "6. **Educational Value:** This transformation is a valuable educational exercise in algorithm design and analysis, teaching important concepts about probabilistic algorithms and their applications in solving complex problems where exact solutions are computationally infeasible.\n" + ] + }, + { + "cell_type": "markdown", + "id": "da9f0954", + "metadata": {}, + "source": [ + "## Solution\n", + "To propose a Monte Carlo version of a Las Vegas algorithm that searches for a prime number within a range by performing primality tests on random numbers, we need to adjust the approach to allow for a probabilistic rather than a guaranteed correct result. \n", + "\n", + "The Las Vegas version of this algorithm picks random numbers within the specified range and performs a primality test on each until it finds a prime number. It guarantees to find a prime number, but the time it takes to do so is uncertain.\n", + "\n", + "A Monte Carlo version, on the other hand, would set a fixed number of attempts to find a prime and then stop, whether or not it has found one. This version trades off the certainty of finding a prime for a predictable running time. \n", + "\n", + "Here's a basic outline for the Monte Carlo algorithm:\n", + "\n", + "1. **Input**: A range $[a, b]$ within which to search for a prime number, and a maximum number of attempts $N$.\n", + "\n", + "2. **Procedure**:\n", + " a. For each attempt $i$ from 1 to $N$:\n", + " i. Select a random number $x$ within the range $[a, b]$.\n", + " ii. Perform a primality test on $x$.\n", + " iii. If $x$ is prime, return $x$ as the result and stop.\n", + " \n", + " b. If no prime number is found after $N$ attempts, either return a failure indication or the best candidate found (which may not be prime).\n", + "\n", + "3. **Output**: The first prime number found within the range, or an indication that no prime was found within the specified number of attempts.\n", + "\n", + "This Monte Carlo algorithm will complete in a predictable amount of time, making at most $N$ primality tests, but it may not always find a prime number, even if one exists within the range. The probability of success depends on the density of primes within the range and the number of attempts $N$." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "f42fbc0d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "127" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import random\n", + "import sympy\n", + "\n", + "def monte_carlo_prime_search(a, b, max_attempts):\n", + " \"\"\"\n", + " Monte Carlo algorithm to find a prime number within a range [a, b].\n", + "\n", + " Args:\n", + " a (int): Lower bound of the range.\n", + " b (int): Upper bound of the range.\n", + " max_attempts (int): Maximum number of attempts to find a prime.\n", + "\n", + " Returns:\n", + " int or None: A prime number found within the range, or None if no prime is found.\n", + " \"\"\"\n", + " for _ in range(max_attempts):\n", + " # Generate a random number within the range\n", + " candidate = random.randint(a, b)\n", + "\n", + " # Check if the number is prime\n", + " if sympy.isprime(candidate):\n", + " return candidate\n", + "\n", + " # No prime number found within the given number of attempts\n", + " return None\n", + "\n", + "# Example usage: Search for a prime number between 100 and 200 with a maximum of 100 attempts\n", + "a = 100\n", + "b = 200\n", + "max_attempts = 100\n", + "found_prime = monte_carlo_prime_search(a, b, max_attempts)\n", + "found_prime" + ] + }, + { + "cell_type": "markdown", + "id": "bef9d04d", + "metadata": {}, + "source": [ + "## Q7 (10 Points) \n", + "Consider a graph G = (V, E), where each node can either be 'active' or 'inactive', and an edge represents a connection between pairs of nodes. A clique in this context is a subset of nodes such that each node in the subset is 'active' and every pair of nodes in the subset is connected by an edge.\n", + "\n", + "Suppose every node in the graph has exactly m neighbors. We are interested in finding the largest possible clique using a random algorithm. Each node P_j decides independently to be 'active' with probability r or 'inactive' with probability 1 - r. For a node to be considered part of the clique, it must be 'active', and all of its m neighbors must also be 'active'.\n", + "\n", + "Provide a formula for the expected size of the clique K when r is set to a specific value, for instance, r = 1/(m+1)." + ] + }, + { + "cell_type": "markdown", + "id": "9edd490a", + "metadata": {}, + "source": [ + "## Solution \n", + "\n", + "### Problem Parameters\n", + "- **Graph G = (V, E)**: Each node can be 'active' or 'inactive'.\n", + "- **Each node has m neighbors**.\n", + "- **Probability r = 1/(m+1)**: Each node independently becomes 'active' with this probability.\n", + "\n", + "### Goal\n", + "- **Find the expected size of the largest clique K**.\n", + "\n", + "### Solution Approach\n", + "The expected size of the largest clique involves finding the expected number of nodes that are 'active' and have all their m neighbors also 'active'.\n", + "\n", + "1. **Probability of a Node Being Part of a Clique**:\n", + " - A node is part of a clique if it is 'active' and all its m neighbors are 'active'.\n", + " - The probability of a node being 'active' is r.\n", + " - The probability of each of its m neighbors being 'active' is also r.\n", + " - Therefore, the probability of a node and its m neighbors all being 'active' is $r^{m+1}$.\n", + "\n", + "2. **Using r = 1/(m+1)**:\n", + " - Replace r with 1/(m+1), so the probability becomes $(1/(m+1))^{m+1}$.\n", + "\n", + "3. **Expected Size of the Clique**:\n", + " - Let N be the total number of nodes.\n", + " - The expected number of nodes that form a clique is N times the probability that any given node is part of a clique.\n", + " - So, the expected size of the largest clique, K, is $N \\times (1/(m+1))^{m+1}$.\n", + "\n", + "### Formula\n", + "$ K = N \\times \\left(\\frac{1}{m+1}\\right)^{m+1} $" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "d4f23817", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0.0021433470507544574" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def expected_clique_size(N, m):\n", + " \"\"\"\n", + " Calculate the expected size of the largest clique in a graph.\n", + "\n", + " Parameters:\n", + " N (int): Total number of nodes in the graph.\n", + " m (int): Each node has exactly m neighbors.\n", + "\n", + " Returns:\n", + " float: Expected size of the largest clique.\n", + " \"\"\"\n", + " r = 1 / (m + 1)\n", + " return N * (r ** (m + 1))\n", + "\n", + "# Example usage\n", + "N = 100 # total number of nodes in the graph\n", + "m = 5 # each node has exactly 5 neighbors\n", + "\n", + "expected_size = expected_clique_size(N, m)\n", + "expected_size" + ] + }, + { + "cell_type": "markdown", + "id": "8bf22dee", + "metadata": {}, + "source": [ + "## Q8 (15 Points)\n", + "Consider an optimized algorithm for sorting an array of integers using a variation of the QuickSort method. The task is to derive a recurrence relation for this algorithm's best, average, and worst-case scenarios, and then analyze its time complexity using the Master Theorem." + ] + }, + { + "cell_type": "markdown", + "id": "4235a918", + "metadata": {}, + "source": [ + "## Reflection\n", + "The task involves examining a variant of the QuickSort algorithm, which is a classic example in the field of computer science for understanding divide-and-conquer strategies and their complexities. QuickSort is known for its efficiency in average cases but can degrade in performance in worst-case scenarios. The challenge here is to understand how the optimizations in the given variant affect its performance across different scenarios (best, average, worst-case).\n", + "\n", + "Establishing the recurrence relations for these scenarios will likely involve understanding the partition strategy of this variant, as the choice of pivot and partitioning approach in QuickSort significantly influences the complexity. The recurrence relations will express how the sorting problem is broken down into smaller sub-problems and how these sub-problems contribute to the overall complexity.\n", + "\n", + "Applying the Master Theorem in this context is an exercise in applying theoretical knowledge to practical algorithm analysis. The Master Theorem provides a direct way to get the time complexity from the recurrence relations, which helps in understanding the efficiency of the algorithm without delving into more complex mathematical proofs. This exercise underscores the importance of theoretical computer science concepts in analyzing and understanding practical algorithms." + ] + }, + { + "cell_type": "markdown", + "id": "a786ad1c", + "metadata": {}, + "source": [ + "## Solution\n", + "\n", + "### Step 1: Establishing Recurrence Relations\n", + "\n", + "1. **Best Case:** This occurs when the pivot divides the array into two equal halves. The recurrence relation in this scenario is often:\n", + " $ T(n) = 2T(n/2) + \\Theta(n) $\n", + " Here, $ \\Theta(n) $ represents the time taken for partitioning the array.\n", + "\n", + "2. **Average Case:** For average case analysis, we assume that the partition happens at some constant ratio (not necessarily in half). The recurrence relation could be:\n", + " $ T(n) = T(k) + T(n - k) + \\Theta(n) $\n", + " Here, $ k $ is some fraction of $ n $, say $ n/4 $, $ n/5 $, etc. \n", + "\n", + "3. **Worst Case:** This happens when the pivot is the smallest or the largest element, leading to very uneven divisions. The recurrence relation is:\n", + " $ T(n) = T(n-1) + \\Theta(n) $\n", + " This essentially means each step only reduces the problem size by 1.\n", + "\n", + "### Step 2: Applying the Master Theorem\n", + "\n", + "The Master Theorem is used to determine the time complexity of recurrence relations of the form:\n", + "$ T(n) = aT(n/b) + f(n) $\n", + "where:\n", + "- $ a $ = number of subproblems in the recursion\n", + "- $ n/b $ = size of each subproblem\n", + "- $ f(n) $ = cost of the work done outside the recursive calls\n", + "\n", + "1. **Best Case:** \n", + " - The relation is $ T(n) = 2T(n/2) + \\Theta(n) $.\n", + " - Here, $ a = 2 $, $ b = 2 $, and $ f(n) = \\Theta(n) $.\n", + " - By the Master Theorem, this falls under Case 2, giving a complexity of $ O(n\\log n) $.\n", + "\n", + "2. **Average Case:** \n", + " - The exact complexity would depend on the value of $ k $. Generally, it tends to be $ O(n\\log n) $, but the exact analysis might need more sophisticated methods than the Master Theorem if $ k $ is not a constant fraction.\n", + "\n", + "3. **Worst Case:** \n", + " - The relation is $ T(n) = T(n-1) + \\Theta(n) $.\n", + " - This does not fit the form for the Master Theorem directly. However, it's a well-known relation that indicates a complexity of $ O(n^2) $." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "93d22489", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 1, 2, 3, 6, 8, 10]\n" + ] + } + ], + "source": [ + "def quicksort(arr):\n", + " if len(arr) <= 1:\n", + " return arr\n", + " else:\n", + " pivot = arr[0]\n", + " less = [x for x in arr[1:] if x <= pivot]\n", + " greater = [x for x in arr[1:] if x > pivot]\n", + " return quicksort(less) + [pivot] + quicksort(greater)\n", + "\n", + "# Example usage\n", + "arr = [3, 6, 8, 10, 1, 2, 1]\n", + "sorted_arr = quicksort(arr)\n", + "print(sorted_arr)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}