What is the Greedy approach?

The greedy approach is an algorithm strategy in which a set of resources are recursively divided based on the maximum, immediate availability of that resource at any given stage of execution.

To solve a problem based on the greedy approach, there are two stages

  1. scanning the list of items
  2. optimization.

These stages are covered parallelly, on course of division of the array.

To understand the greedy approach, you will need to have a working knowledge of recursion and context switching. This helps you to understand how to trace the code. You can define the greedy paradigm in terms of your own necessary and sufficient statements.

Two conditions define the greedy paradigm.

  • Each stepwise solution must structure a problem towards its best-accepted solution.
  • It is sufficient if the structuring of the problem can halt in a finite number of greedy steps.

With the theorizing continued, let us describe the history associated with the greedy approach.

In this Greedy tutorial, you will learn:

History of Greedy Algorithms

Here is an important landmark of greedy algorithms:

  • Greedy algorithms were conceptualized for many graph walk algorithms in the 1950s.
  • Esdger Djikstra conceptualized the algorithm to generate minimal spanning trees. He aimed to shorten the span of routes within the Dutch capital, Amsterdam.
  • In the same decade, Prim and Kruskal achieved optimization strategies that were based on minimizing path costs along weighed routes.
  • In the '70s, American researchers, Cormen, Rivest, and Stein proposed a recursive substructuring of greedy solutions in their classical introduction to algorithms book.
  • The greedy paradigm was registered as a different type of optimization strategy in the NIST records in 2005.
  • Till date, protocols that run the web, such as the open-shortest-path-first (OSPF) and many other network packet switching protocols use the greedy strategy to minimize time spent on a network.

Greedy Strategies and Decisions

Logic in its easiest form was boiled down to "greedy" or "not greedy". These statements were defined by the approach taken to advance in each algorithm stage.

For example, Djikstra's algorithm utilized a stepwise greedy strategy identifying hosts on the Internet by calculating a cost function. The value returned by the cost function determined whether the next path is "greedy" or "non-greedy".

In short, an algorithm ceases to be greedy if at any stage it takes a step that is not locally greedy. The problem halts with no further scope of greed.

Characteristics of the Greedy Approach

The important characteristics of a greedy method are:

  • There is an ordered list of resources, with costs or value attributions. These quantify constraints on a system.
  • You will take the maximum quantity of resources in the time a constraint applies.
  • For example, in an activity scheduling problem, the resource costs are in hours, and the activities need to be performed in serial order.

Why use the Greedy Approach?

Here are the reasons for using the greedy approach:

  • The greedy approach has a few tradeoffs, which may make it suitable for optimization.
  • One prominent reason is to achieve the most feasible solution immediately. In the activity selection problem (Explained below), if more activities can be done before finishing the current activity, these activities can be performed within the same time.
  • Another reason is to divide a problem recursively based on a condition, with no need to combine all the solutions.
  • In the activity selection problem, the "recursive division" step is achieved by scanning a list of items only once and considering certain activities.

How to Solve the activity selection problem

In the activity scheduling example, there is a "start" and "finish" time for every activity. Each Activity is indexed by a number for reference. There are two activity categories.

  1. considered activity: is the Activity, which is the reference from which the ability to do more than one remaining Activity is analyzed.
  2. remaining activities: activities at one or more indexes ahead of the considered activity.

The total duration gives the cost of performing the activity. That is (finish - start) gives us the durational as the cost of an activity.

You will learn that the greedy extent is the number of remaining activities you can perform in the time of a considered activity.

Architecture of the Greedy approach

STEP 1)

Scan the list of activity costs, starting with index 0 as the considered Index.

STEP 2)

When more activities can be finished by the time, the considered activity finishes, start searching for one or more remaining activities.

STEP 3)

If there are no more remaining activities, the current remaining activity becomes the next considered activity. Repeat step 1 and step 2, with the new considered activity. If there are no remaining activities left, go to step 4.

STEP 4 )

Return the union of considered indices. These are the activity indices that will be used to maximize throughput.

Code Explanation

#include<iostream>
#include<stdio.h>
#include<stdlib.h>

#define MAX_ACTIVITIES 12

Explanation of code:

  1. Included header files/classes
  2. A max number of activities provided by the user.
using namespace std;

class TIME
{
    public:
    int hours;

    public: TIME()
    {
   	 hours = 0;
    }
};

Explanation of code:

  1. The namespace for streaming operations.
  2. A class definition for TIME
  3. An hour timestamp.
  4. A TIME default constructor
  5. The hours variable.
class Activity
{
    public:
    int index;
    TIME start;
    TIME finish;

    public: Activity()
    {
   	 start = finish = TIME();
    }
};

Explanation of code:

  1. A class definition from activity
  2. Timestamps defining a duration
  3. All timestamps are initialized to 0 in the default constructor
class Scheduler
{
    public:
    int considered_index,init_index;
    Activity *current_activities = new    Activity[MAX_ACTIVITIES];
    Activity *scheduled;

Explanation of code:

  1. Part 1 of the scheduler class definition.
  2. Considered Index is the starting point for scanning the array.
  3. The initialization index is used to assign random timestamps.
  4. An array of activity objects is dynamically allocated using the new operator.
  5. The scheduled pointer defines the current base location for greed.
Scheduler()
{
   	 considered_index = 0;
   	 scheduled = NULL;
...
...

Explanation of code:

  1. The scheduler constructor - part 2 of the scheduler class definition.
  2. The considered index defines the current start of the current scan.
  3. The current greedy extent is undefined at the start.
for(init_index = 0; init_index < MAX_ACTIVITIES; init_index++)
 {
   		 current_activities[init_index].start.hours =
   			 rand() % 12;

   		 current_activities[init_index].finish.hours =
   			 current_activities[init_index].start.hours +
   				 (rand() % 2);

   		 printf("\nSTART:%d END %d\n",
   		 current_activities[init_index].start.hours
   		 ,current_activities[init_index].finish.hours);
 }
…
…

Explanation of code:

  1. A for loop to initialize start hours and end hours of each of the activities currently scheduled.
  2. Start time initialization.
  3. End time initialization always after or exactly at the start hour.
  4. A debug statement to print out allocated durations.
	public:
   		 Activity * activity_select(int);
};

Explanation of code:

  1. Part 4 - the last part of the scheduler class definition.
  2. Activity select function takes a starting point index as the base and divides the greedy quest into greedy subproblems.
Activity * Scheduler :: activity_select(int considered_index)
{
    this->considered_index = considered_index;
    int greedy_extent = this->considered_index + 1;
…
… 

  1. Using the scope resolution operator (::), the function definition is provided.
  2. The considered Index is the Index called by value. The greedy_extent is the initialized just an index after the considered Index.
Activity * Scheduler :: activity_select(int considered_index)
{
    	while( (greedy_extent < MAX_ACTIVITIES ) &&
   	 ((this->current_activities[greedy_extent]).start.hours <
   		 (this->current_activities[considered_index]).finish.hours ))
    	{
   	 printf("\nSchedule start:%d \nfinish%d\n activity:%d\n",
   	 (this->current_activities[greedy_extent]).start.hours,
   	 (this->current_activities[greedy_extent]).finish.hours,
   	 greedy_extent + 1);
   	 greedy_extent++;
    	}
…
...

Explanation of code:

  1. The core logic- The greedy extent is limited to the number of activities.
  2. The start hours of the current Activity are checked as schedulable before the considered Activity (given by considered index) would finish.
  3. As long as this possible, an optional debug statement is printed.
  4. Advance to next index on the activity array
...
if ( greedy_extent <= MAX_ACTIVITIES )
    {

   	 return activity_select(greedy_extent);
    }
    else
    {
   	 return NULL;
    }
}

Explanation of code:

  1. The conditional checks if all the activities have been covered.
  2. If not, you can restart your greedy with the considered Index as the current point., This is a recursive step that greedily divides the problem statement.
  3. If yes, it returns to the caller with no scope for extending greed.
int main()
{
    Scheduler *activity_sched = new Scheduler();
    activity_sched->scheduled = activity_sched->activity_select(
   				activity_sched->considered_index);
    return 0;
}

Explanation of code:

  1. The main function used to invoke the scheduler.
  2. A new Scheduler is instantiated.
  3. The activity select function, which returns a pointer of type activity comes back to the caller after the greedy quest is over.

Output:

START:7 END 7

START:9 END 10

START:5 END 6

START:10 END 10

START:9 END 10

Schedule start:5 
finish6
 activity:3

Schedule start:9 
finish10
 activity:5

Disadvantages of Greedy Algorithms

It is not suitable for problems where a solution is required for every subproblem like sorting.

In such problems, the greedy strategy can be wrong; in the worst case even lead to a non-optimal solution.

Therefore the disadvantage of greedy algorithms is using not knowing what lies ahead of the current greedy state.

Below is a depiction of the disadvantage of the greedy approach.

In the greedy scan shown here as a tree (higher value higher greed), an algorithm state at value: 40, is likely to take 29 as the next value. Further, its quest ends at 12. This amounts to a value of 41.

However, if the algorithm took a sub-optimal path or adopted a conquering strategy. then 25 would be followed by 40, and the overall cost improvement would be 65, which is valued 24 points higher as a suboptimal decision.

Examples of Greedy Algorithms

Most networking algorithms use the greedy approach. Here is a list of few of them −

  • Prim's Minimal Spanning Tree Algorithm
  • Travelling Salesman Problem
  • Graph - Map Coloring
  • Kruskal's Minimal Spanning Tree Algorithm
  • Dijkstra's Minimal Spanning Tree Algorithm
  • Graph - Vertex Cover
  • Knapsack Problem
  • Job Scheduling Problem

Summary:

To summarize, the article defined the greedy paradigm, showed how greedy optimization and recursion, can help you obtain the best solution up to a point. The activity selection example was described as a strategic problem that could achieve maximum throughput using the greedy approach. In the end, the demerits of the usage of the greedy approach were explained.

 

YOU MIGHT LIKE: