# Applications of the greedy algorithm

A greedy algorithm is a strategy for addressing optimization issues that involve making locally optimal decisions at each stage in the hopes of obtaining a global optimum. The name "greedy" stems from the assumption that the algorithm chooses decisions that look ideal at the current instant without considering the potential ramifications in future stages. Greedy algorithms are normally basic, intuitive, and direct to execute.

A greedy algorithm's overall technique is to choose the most ideal choice at each stage, with the expectation that the chosen choice will bring about an ideal arrangement in general.

Characteristic components

1. Greedy Strategy: A strategy that chooses the best local choices at each stage, to achieve a global optimum without having to reevaluate judgments.
2. Selection Criteria: Specific rules are used to pick the best option at each step based on problem characteristics.
3. Feasibility Check: Verification that the chosen option satisfies all specified constraints of the problem.
4. Optimization Objective: Clearly define the goal the algorithm aims to optimize.
5. Iteration: The repetitive application of the greedy strategy until a solution is found or a specific condition is met.
6. Proof of Correctness: Providing logical evidence that the algorithm consistently leads to an optimal solution.
7. Optimal Solution: A feasible solution that achieves the desired extremum, minimizing or maximizing the objective function.
8. Optimality Check: Verifying if a selected solution produces the minimum or maximum value of the objective function while meeting constraints.
9. Optimal Substructure Property: The globally optimal solution includes optimal sub-solutions within it.

Algorithm

1. Choose things according to their value-to-weight ratio.
2. Set the current weight of the knapsack to zero and the total value to zero.
3. Ensure the selected item can fit into the knapsack without exceeding its capacity.
4. Maximize the total value of items in the knapsack.
5. Choose the item with the highest value-to-weight ratio that fits into the knapsack.
6. Add the chosen item to the knapsack. Update the total value and the current weight.
7. Repeat steps 3-6 until the knapsack is full or there are no more items.
8. Verify if the obtained solution is optimal in terms of the maximum total value.
9. Stop the algorithm when the knapsack is full, or there are no more items.

Applications

Task scheduling is the course of effectively distributing tasks or jobs to assets in a framework while considering various limitations and enhancement goals. In PCs and working frameworks, task planning is particularly significant for dealing with the execution of cycles or strings on a PC's computer chip. The fundamental design is to utilize framework assets, limit general execution time, and enhance specific execution measurements.

Example

Suppose we have four tasks: A, B, C, and D.

2. Execution Times:

Each task has a distinct execution time, indicating how long it takes to complete:

• Task A: five units of time.
• Task B involves two units of time.
• Task C requires 8 units of time.
• Task D: four units of time.

3. Scheduling steps:

The FCFS algorithm schedules tasks in the order they arrive or are submitted. Let us stroll through the scheduling steps:

• Task A comes first; therefore, execution begins at time zero.
• After Task A is completed, Task B begins execution at time 5.
• Task C begins at time 7 after Task B is completed.
• Finally, Task D begins at time 15, after Task C is completed.

4. Completion Times:

Each task's completion times are shown below:

• Task A is completed at time 5.
• Task B finishes at time 7.
• Task C is completed at time 15.
• Task D is completed at time 19.

5. Final schedule:

The complete work schedule using FCFS would look like this:

A 5
B 7
C 15
D 19

6. Observations:

The FCFS algorithm executes tasks in the order they come, regardless of their execution times. It might cause varied completion times for various jobs.

7. Considerations:

While FCFS is clear, there may be better timetables. Other booking strategies, for example, Shortest Job Next (SJN) or Priority Scheduling, might be more appropriate for certain cases.

Implementation

Output:

Explanation

• We define a Task structure to represent each task, including a task identifier (name) and its execution time (executionTime).
• The scheduleTasks function performs FCFS scheduling by iterating through the tasks in the order they are received and printing the completion time for each task.
• In the main function, we create an array of tasks and call the scheduleTasks function.

### 2. Huffman Coding for Data Compression:

Huffman coding is a well-known approach for lossless information pressure, planned to diminish record size while holding all data. It's named after David A. Huffman, who made the strategy in 1952. The major guideline fundamental to Huffman coding is to dole out factor length codes to different images (characters or groupings of characters) in light of their recurrence of events in the info information. More normal images are granted more limited codes, while more uncommon images are allowed longer codes.

Example

1. Frequency analysis:

Frequencies: A(5), B(2), R(2), C(1), and D(1).

2. Building the Huffman Tree:

Create the Huffman tree based on the frequencies.

3. Assigning codes:

To assign codes, traverse the tree: A(0), B(10), R(11), C(100), and D(101).

4. Creating Huffman Tables:

The Huffman table: A=0, B=10, R=11, C=100, D=101.

5. Encoding:

The original data "ABRACADABRA" was encoded as "0101110100111110100."

6. Decoding:

The encoded information gets decoded back to its unique: "ABRACADABRA."

Huffman coding compresses more common symbols by using shorter codes, resulting in a variable-length prefix code. It guarantees that the most often used symbols are represented more efficiently, resulting in total data reduction.

Implementation

Output:

Explanation

1. Node Structure:

• We define a structure called Node to represent characters and their frequencies. Each node has a character (data), a frequency, and pointers to the left and right children.

2. New Node Function:

• There is a function called newNode that creates a new node with a given character and frequency.

3. Main Huffman Coding Function:

• The HuffmanCodes function is the main function for Huffman coding.
• It initializes example data and frequencies (characters and their occurrence counts).

4. Create Huffman Tree:

• The buildHuffmanTree function (not shown here) constructs a Huffman tree based on the provided characters and frequencies.

5. Print Huffman Codes:

• The program then prints the Huffman codes for each character using the printCodes function (not shown here).

6. Main Function:

• In the main function, we call the HuffmanCodes function with the example data and frequencies.

### 3. Minimum Spanning Tree (MST)

A minimum spanning tree (MST) is a key concept in network design, especially in graph theory. It refers to the smallest tree that can connect all of the nodes in a connected, undirected graph while minimizing overall edge weight. MSTs are extensively utilized for a variety of purposes, including developing efficient network architectures, assuring connection, and reducing expenses.

Working Principle:

Edge weights:

Every edge in the graph has a weight or cost connected with it. The aim is to find a tree that connects all nodes with the least amount of total edge weight.

Connectivity:

The MST must retain connectedness, which means that all nodes may be reached from any other node in the tree.

Example:

Consider a scenario in which there are several cities connected by highways, each having a specific cost. The objective is to create a network that connects all cities at the lowest overall cost.

Let us portray cities as nodes and highways as edges, with their corresponding costs:

Cities (Nodes): A, B, C, and D

B-C (cost: 2)

C - D (Cost: 1).

D-A (Cost: 4)

A-C (cost: 5)

Graph Representation

MST Solution:

The minimum spanning tree for this network is:

In this example, the MST links all of the cities (nodes) at the lowest feasible total cost, which is the sum of the edge weights. The MST, being a tree, does not create any cycles and provides the most efficient and cost-effective communication between cities.

Implementation

Output:

Explanation

1. struct Edge: Represents an edge in the graph with source (src), destination (dest), and weight.
2. struct Subset: Represents a subset for the union-find data structure.
3. find and Union: Functions to find the set of a vertex and perform union of two subsets using the union-find algorithm.
4. compareEdges: Comparator function used by qsort to sort edges based on their weights.
5. KruskalMST: Main function implementing Kruskal's algorithm to find the Minimum Spanning Tree.
6. main: Example usage with a graph of 4 vertices and 5 edges. The program finds and prints the edges of the Minimum Spanning Tree.

### 4. Knapsack problem

The Knapsack Problem is a standard optimization problem in computer science and mathematics. The formal definition is as follows:

Given a collection of objects, each with a weight and a value, and a knapsack with a maximum weight capacity, the goal is to find the best combination of things to put in the knapsack such that the total value is maximized. Still, the total weight does not exceed the capacity of the knapsack.

There are several variations of the Knapsack Problem, but one notable distinction is between the 0/1 Knapsack Problem and the Fractional Knapsack Problem.

0/1 Knapsack Problem: In this version, an item may only be included in the knapsack or excluded. Therefore you cannot take a fraction of it. The decision is binary: either the entire thing is included or not.

Fractional Knapsack Problem: In this variation, an item can be divided into fractions. It enables a more flexible approach in which you can take a portion of an item if it helps to maximize the entire value.

Example

Problem Statement: Your knapsack has a weight capacity of 10 units. You are handed the following things, each with its weight and value:

The aim is to find a combination of goods to put in the knapsack that maximizes the overall value while keeping the total weight under 10 units.

Solution:

Let's utilize a dynamic programming strategy to address this problem.

1. Create a two-dimensional array dp[n+1][capacity+1], where n is the number of items and capacity is the knapsack capacity.

2. Fill the array with the following recurrence relationship:

Here, dp[i][w] denotes the highest value possible with the first i items and a knapsack capacity of w.

3. The result is kept in dp[n][capacity].

Implementation

Output:

Explanation

1. The max function is a utility function that finds the maximum of two numbers.
2. knapsack function: A dynamic programming solution for the 0/1 Knapsack Problem. It creates a 2D array dp[][] to hold the highest value possible for each subproblem.
3. main function: This is an example of use with a backpack capacity of ten units and four things. It computes and prints the highest value that can be obtained within the provided limitations.

### 5. Activity Selection Problem

The Activity Selection issue is a classic algorithmic issue that involves selecting a maximum number of non-overlapping activities, each with a start and end time, from a list of activities. The goal is to determine the greatest number of tasks that may be completed by a single person or resource, given that only one action can be performed at a time.

Example:

Consider the following set of activities:

Solution: The maximum number of non-overlapping tasks is [C, A, D, E].

Explanation:

• Activity C is the earliest activity to complete, concluding at time 6.
• Activity A begins after C has finished and ends at time 4.
• Activity D begins after A and ends at time 7.
• Activity E begins after D has finished and ends at time 9.

As a consequence, C, A, D, and E may be chosen without overlap, yielding the greatest number of activities.

Implementation

Output:

Explanation:

1. struct Activity: Represents an activity with start and finish times.
2. compareActivities: Comparator function used by qsort to sort activities based on their finish times.
3. printMaxActivities: Function to find and print the maximum number of non-overlapping activities using the greedy approach.
4. main: Example usage with a set of activities. The program prints the selected activities with their start and finish times.

Limitations

1. No Backtracking: Greedy algorithms make locally optimum decisions at each step, regardless of the global context. Once taken, a decision cannot be reversed or undone. This absence of retracing might result in inferior solutions.
2. There is no assurance that greedy algorithms will identify the globally optimal solution for all occurrences of a problem. The locally optimum decisions taken at each phase may not result in the best overall solution.
3. Doesn't Consider Future ramifications: Greedy algorithms prioritize quick benefits above the long-term ramifications of their decisions. This narrow perspective may lead to solutions that are suboptimal in the larger context.
4. Problem Dependency: A greedy algorithm's efficacy is determined by the unique features of the problem being solved. If a problem lacks the greedy-choice property or optimum substructure, a greedy technique may fail to produce an optimal result.
5. Greedy algorithms are not appropriate for addressing NP-hard problems in which finding an optimal solution is computationally intractable. Greedy algorithms may produce approximate answers, but only sometimes optimum ones.
6. Greedy algorithms are most successful when a problem has both the greedy-choice property and optimum substructure. Problems that lack these characteristics may not be appropriate for a greedy approach.

1. Simplicity: Greedy algorithms are frequently straightforward to comprehend and implement. The simple logic and absence of sophisticated data structures make them usable even for persons without substantial algorithmic knowledge.
2. Efficiency: Greedy algorithms are often time-efficient. The simplicity frequently results in algorithms that have linear or near-linear time complexity, making them acceptable for huge datasets.
3. Greedy algorithms are widely utilized while designing approximation algorithms. While they cannot always guarantee perfect answers, they can deliver solutions that are nearly optimal in a reasonable length of time.
4. Greedy algorithms have applications in a variety of real-world contexts, such as network design, task scheduling, and resource allocation. Their simplicity and effectiveness make them ideal for practical issue resolution.
5. Optimality in Some Cases: Greedy algorithms can produce optimal solutions in situations where the greedy-choice property and optimum substructure are met. The locally optimum choices at each phase result in a globally optimal solution.
6. Memory Efficiency: Greedy algorithms frequently demand less memory since they deal with small amounts of information at each stage. This makes them ideal for applications that have restricted memory resources.
7. Fast Execution: Because greedy algorithms are efficient, they may be executed quickly, particularly when working with huge datasets. It makes them appropriate for real-time or resource-constrained applications.