Greedy Algorithms, Design and Analysis of Algorithms

Introduction to Greedy Algorithms

Definition and Key Concepts

Greedy Algorithms:
A greedy algorithm is an approach for solving optimization problems by making the locally optimal choice at each stage with the hope of finding a glo…


This content originally appeared on DEV Community and was authored by Harsh Mishra

Introduction to Greedy Algorithms

Definition and Key Concepts

Greedy Algorithms:
A greedy algorithm is an approach for solving optimization problems by making the locally optimal choice at each stage with the hope of finding a global optimum. The fundamental principle of greedy algorithms is to make a sequence of choices, each of which looks best at the moment, with the aim of reaching an overall optimal solution.

Key Concepts:

  1. Locally Optimal Choice: At each step, choose the option that looks the best at that moment.
  2. Greedy Choice Property: This property states that a globally optimal solution can be arrived at by selecting the local optimums.
  3. Optimal Substructure: An optimal solution to the problem contains an optimal solution to subproblems.

Greedy Choice Property and Optimal Substructure

Greedy Choice Property:

  • The greedy choice property states that a problem can be solved by making a series of choices, each of which looks best at the moment. Once a choice is made, it is not reconsidered. For a greedy algorithm to work, it must be possible to determine that there is always a globally optimal solution containing the chosen local optimal solutions.

Optimal Substructure:

  • A problem exhibits optimal substructure if an optimal solution to the problem contains an optimal solution to its subproblems. This means that we can solve the problem by solving smaller instances of the same problem and combining their solutions to form a solution to the original problem.

Differences between Greedy Algorithms and Other Paradigms

Greedy Algorithms vs. Divide and Conquer:

  • Greedy Algorithms: Make a series of choices, each of which looks best at the moment, and never reconsider those choices.
  • Divide and Conquer: Divide the problem into smaller subproblems, solve each subproblem recursively, and then combine their solutions to form a solution to the original problem.

Example:

  • Greedy: Huffman coding for data compression.
  • Divide and Conquer: Merge sort algorithm for sorting an array.

Greedy Algorithms vs. Dynamic Programming:

  • Greedy Algorithms: Make a series of choices, each of which looks best at the moment. This approach works when the problem has the greedy choice property and optimal substructure.
  • Dynamic Programming: Break down the problem into subproblems, solve each subproblem just once, and store their solutions using a table. This approach works when the problem has overlapping subproblems and optimal substructure.

Example:

  • Greedy: Fractional knapsack problem.
  • Dynamic Programming: 0/1 knapsack problem.

Example for Greedy Algorithms

Greedy algorithms are a technique used for solving optimization problems by making the locally optimal choice at each step with the hope of finding a global optimum. This approach is particularly effective in problems where choosing the best local option leads to an optimal solution. Greedy algorithms are widely used in network design, scheduling, and data compression.

Activity Selection Problem

The Activity Selection Problem involves selecting the maximum number of activities that don't overlap, given their start and finish times. The greedy choice here is to always select the activity that finishes earliest.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Activity {
    int start;
    int finish;
};

// Comparator function to sort activities by their finish times
bool compare(Activity a, Activity b) {
    return a.finish < b.finish;
}

vector<Activity> selectActivities(vector<Activity>& activities) {
    // Sort activities by their finish times
    sort(activities.begin(), activities.end(), compare);

    vector<Activity> selected;
    int lastFinishTime = 0;

    for (const auto& activity : activities) {
        if (activity.start >= lastFinishTime) {
            selected.push_back(activity);
            lastFinishTime = activity.finish;
        }
    }

    return selected;
}

int main() {
    vector<Activity> activities = {{1, 3}, {2, 5}, {0, 6}, {5, 7}, {8, 9}, {5, 9}};
    vector<Activity> result = selectActivities(activities);

    cout << "Selected activities:\n";
    for (const auto& activity : result) {
        cout << "Start: " << activity.start << ", Finish: " << activity.finish << "\n";
    }
    return 0;
}

Coin Change Problem

The Coin Change Problem involves making change for a given amount using the fewest number of coins from a given set of denominations. The greedy choice is to always pick the largest denomination coin that is less than or equal to the remaining amount.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> coinChange(vector<int>& denominations, int amount) {
    sort(denominations.rbegin(), denominations.rend());

    vector<int> result;

    for (int coin : denominations) {
        while (amount >= coin) {
            amount -= coin;
            result.push_back(coin);
        }
    }

    return result;
}

int main() {
    vector<int> denominations = {1, 2, 5, 10, 20, 50, 100, 200, 500, 2000};
    int amount = 2896;
    vector<int> result = coinChange(denominations, amount);

    cout << "Coins used:\n";
    for (int coin : result) {
        cout << coin << " ";
    }
    return 0;
}

Fractional Knapsack Problem

The Fractional Knapsack Problem involves maximizing the total value in a knapsack by taking fractions of items with given weights and values. The greedy choice is to always pick the item with the highest value-to-weight ratio that fits in the knapsack.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Item {
    int value;
    int weight;
};

// Comparator function to sort items by their value-to-weight ratio
bool compare(Item a, Item b) {
    double r1 = (double)a.value / a.weight;
    double r2 = (double)b.value / b.weight;
    return r1 > r2;
}

double fractionalKnapsack(vector<Item>& items, int capacity) {
    sort(items.begin(), items.end(), compare);

    double totalValue = 0.0;
    int currentWeight = 0;

    for (const auto& item : items) {
        if (currentWeight + item.weight <= capacity) {
            currentWeight += item.weight;
            totalValue += item.value;
        } else {
            int remain = capacity - currentWeight;
            totalValue += item.value * ((double)remain / item.weight);
            break;
        }
    }

    return totalValue;
}

int main() {
    vector<Item> items = {{60, 10}, {100, 20}, {120, 30}};
    int capacity = 50;
    double maxValue = fractionalKnapsack(items, capacity);

    cout << "Maximum value in Knapsack = " << maxValue << "\n";
    return 0;
}

Dijkstra's Algorithm

Dijkstra's Algorithm finds the shortest path from a source vertex to all other vertices in a weighted graph with non-negative weights. The greedy choice is to always pick the vertex with the smallest known distance.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

struct Edge {
    int destination;
    int weight;
};

void dijkstra(const vector<vector<Edge>>& graph, int source) {
    int numVertices = graph.size();
    vector<int> distances(numVertices, INT_MAX);
    distances[source] = 0;

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
    minHeap.push({0, source});

    while (!minHeap.empty()) {
        int currentVertex = minHeap.top().second;
        minHeap.pop();

        for (const auto& edge : graph[currentVertex]) {
            int neighbor = edge.destination;
            int edgeWeight = edge.weight;

            if (distances[currentVertex] + edgeWeight < distances[neighbor]) {
                distances[neighbor] = distances[currentVertex] + edgeWeight;
                minHeap.push({distances[neighbor], neighbor});
            }
        }
    }

    cout << "Vertex distances from source " << source << ":\n";
    for (int i = 0; i < numVertices; ++i) {
        cout << "Vertex " << i << ": " << distances[i] << "\n";
    }
}

int main() {
    int numVertices = 5;
    vector<vector<Edge>> graph(numVertices);

    graph[0] = {{1, 10}, {4, 5}};
    graph[1] = {{2, 1}, {4, 2}};
    graph[2] = {{3, 4}};
    graph[3] = {{2, 6}, {0, 7}};
    graph[4] = {{1, 3}, {2, 9}, {3, 2}};

    int source = 0;
    dijkstra(graph, source);

    return 0;
}

Prim's Algorithm

Prim's Algorithm finds the Minimum Spanning Tree (MST) for a graph. The greedy choice is to always add the minimum weight edge that connects a vertex in the MST to a vertex outside the MST.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;

struct Edge {
    int destination;
    int weight;
};

void primMST(const vector<vector<Edge>>& graph) {
    int numVertices = graph.size();
    vector<int> minEdgeWeight(numVertices, INT_MAX);
    vector<int> parent(numVertices, -1);
    vector<bool> inMST(numVertices, false);

    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;
    minHeap.push({0, 0});
    minEdgeWeight[0] = 0;

    while (!minHeap.empty()) {
        int currentVertex = minHeap.top().second;
        minHeap.pop();

        inMST[currentVertex] = true;

        for (const auto& edge : graph[currentVertex]) {
            int neighbor = edge.destination;
            int edgeWeight = edge.weight;

            if (!inMST[neighbor] && minEdgeWeight[neighbor] > edgeWeight) {
                minEdgeWeight[neighbor] = edgeWeight;
                minHeap.push({minEdgeWeight[neighbor], neighbor});
                parent[neighbor] = currentVertex;
            }
        }
    }

    cout << "Edges in the MST:\n";
    for (int i = 1; i < numVertices; ++i) {
        cout << parent[i] << " - " << i << "\n";
    }
}

int main() {
    int numVertices = 5;
    vector<vector<Edge>> graph(numVertices);

    graph[0] = {{1, 2}, {3, 6}};
    graph[1] = {{0, 2}, {2, 3}, {3, 8}, {4, 5}};
    graph[2] = {{1, 3}, {4, 7}};
    graph[3] = {{0, 6}, {1, 8}};
    graph[4] = {{1, 5}, {2, 7}};

    primMST(graph);

    return 0;
}

Kruskal's Algorithm

Kruskal's Algorithm finds the Minimum Spanning Tree (MST) for a graph. The greedy choice is to always pick the smallest weight edge that does not form a cycle in the MST.

Here’s a C++ implementation:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Edge {
    int source;
    int destination;
    int weight;
};

bool compare(Edge a, Edge b) {
    return a.weight < b.weight;
}

class DisjointSet {
public:
    vector<int> parent, rank;

    DisjointSet(int numVertices) {
        parent.resize(numVertices);
        rank.resize(numVertices, 0);
        for (int i = 0; i < numVertices; ++i)
            parent[i] = i;
    }

    int find(int vertex) {
        if (vertex != parent[vertex])
            parent[vertex] = find(parent[vertex]);
        return parent[vertex];
    }

    void unionSets(int vertex1, int vertex2) {
        int root1 = find(vertex1);
        int root2 = find(vertex2);

        if (root1 != root2) {
            if (rank[root1] > rank[root2]) {
                parent[root2] = root1;
            } else if (rank[root1] < rank[root2]) {
                parent[root1] = root2;
            } else {
                parent[root2] = root1;
                rank[root1]++;
            }
        }
    }
};

void kruskalMST(vector<Edge>& edges, int numVertices) {
    sort(edges.begin(), edges.end(), compare);

    DisjointSet ds(numVertices);

    vector<Edge> mst;

    for (const auto& edge : edges) {
        int vertex1 = edge.source;
        int vertex2 = edge.destination;

        if (ds.find(vertex1) != ds.find(vertex2)) {
            mst.push_back(edge);
            ds.unionSets(vertex1, vertex2);
        }
    }

    cout << "Edges in the MST:\n";
    for (const auto& edge : mst) {
        cout << edge.source << " - " << edge.destination << " (Weight: " << edge.weight << ")\n";
    }
}

int main() {
    int numVertices = 4;
    vector<Edge> edges = {
        {0, 1, 10}, {0, 2, 6}, {0, 3, 5}, {1, 3, 15}, {2, 3, 4}
    };

    kruskalMST(edges, numVertices);

    return 0;
}


This content originally appeared on DEV Community and was authored by Harsh Mishra


Print Share Comment Cite Upload Translate Updates
APA

Harsh Mishra | Sciencx (2024-07-03T16:51:44+00:00) Greedy Algorithms, Design and Analysis of Algorithms. Retrieved from https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/

MLA
" » Greedy Algorithms, Design and Analysis of Algorithms." Harsh Mishra | Sciencx - Wednesday July 3, 2024, https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/
HARVARD
Harsh Mishra | Sciencx Wednesday July 3, 2024 » Greedy Algorithms, Design and Analysis of Algorithms., viewed ,<https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/>
VANCOUVER
Harsh Mishra | Sciencx - » Greedy Algorithms, Design and Analysis of Algorithms. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/
CHICAGO
" » Greedy Algorithms, Design and Analysis of Algorithms." Harsh Mishra | Sciencx - Accessed . https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/
IEEE
" » Greedy Algorithms, Design and Analysis of Algorithms." Harsh Mishra | Sciencx [Online]. Available: https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/. [Accessed: ]
rf:citation
» Greedy Algorithms, Design and Analysis of Algorithms | Harsh Mishra | Sciencx | https://www.scien.cx/2024/07/03/greedy-algorithms-design-and-analysis-of-algorithms/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.