Skill

গ্রিডি অ্যালগরিদম (Greedy Algorithms)

জাভা দিয়ে ডাটা স্ট্রাকচার এবং অ্যালগরিদম (DSA using Java) - Java Technologies

412

গ্রিডি অ্যালগরিদম হল এমন একটি সমস্যা সমাধানের কৌশল, যেখানে প্রতিটি পদক্ষেপে বর্তমান সমস্যার সবচেয়ে ভালো বা অপ্টিমাল (optimal) সমাধান বেছে নেওয়া হয়, অর্থাৎ যে পদক্ষেপটি এখনই সবচেয়ে ভাল মনে হয় তা নেয়া হয়। এটি সাধারণত এমন সমস্যার জন্য ব্যবহৃত হয় যেখানে পেশীসমস্যা (local optimum) চূড়ান্ত সমাধান (global optimum) তৈরি করে।

গ্রিডি অ্যালগরিদম সহজ এবং দ্রুত সমাধান প্রদান করে, তবে এটি সর্বদা অপ্টিমাল সমাধান দেয় না, যেহেতু এটি গ্লোবাল অপ্টিমাম এর পরিবর্তে শুধুমাত্র লোকাল অপ্টিমাম নিয়ে কাজ করে।


1. গ্রিডি অ্যালগরিদমের মৌলিক ধারণা

গ্রিডি অ্যালগরিদমের প্রধান বৈশিষ্ট্যসমূহ:

  1. লোকাল অপ্টিমাম বাছাই: প্রতিটি পদক্ষেপে এমন একটি বিকল্প নির্বাচন করা হয় যা বর্তমানে সবচেয়ে ভাল।
  2. গ্লোবাল অপ্টিমাম তৈরি নয়: যদিও প্রতিটি পদক্ষেপ লোকালভাবে ভাল হয়, তবে সেগুলি একত্রে গ্লোবালভাবে অপ্টিমাল সমাধান নাও দিতে পারে।
  3. স্পেসিফিক স্ট্রাটেজি: একটি সুসংগত কৌশল বা পদ্ধতি থাকতে হবে যা সঠিকভাবে প্রতিটি পদক্ষেপের জন্য উপযুক্ত অপশন বেছে নেয়।

গ্রিডি অ্যালগরিদম কিছু সাধারণ সমস্যা সমাধানে খুবই কার্যকরী:

  • মিনিমাম স্প্যানিং ট্রি (MST): যেমন প্রিমস অ্যালগরিদম (Prim’s Algorithm) বা কর্শকল অ্যালগরিদম (Kruskal’s Algorithm)
  • শর্টেস্ট পাথ (Shortest Path): যেমন ডাইকস্ট্রা অ্যালগরিদম (Dijkstra's Algorithm)
  • জ্যাকার প্যাকিং (Job Scheduling)
  • হাফিং কোডিং (Huffman Coding)

2. গ্রিডি অ্যালগরিদমের উদাহরণ

উদাহরণ 1: মিনিমাম স্প্যানিং ট্রি (MST) – কর্শকল অ্যালগরিদম (Kruskal's Algorithm)

কর্শকল অ্যালগরিদম (Kruskal's Algorithm) হল একটি গ্রিডি অ্যালগরিদম যা একটি অধিকতর নেটওয়ার্ক বা গ্রাফ থেকে মিনিমাম স্প্যানিং ট্রি (MST) তৈরি করতে ব্যবহৃত হয়। এটি গ্রাফের সকল এজকে সজ্জিত করে এবং সবচেয়ে ছোট এজ প্রথমে নির্বাচন করে।

Java Example for Kruskal's Algorithm:

import java.util.*;

class Edge {
    int src, dest, weight;

    public Edge(int src, int dest, int weight) {
        this.src = src;
        this.dest = dest;
        this.weight = weight;
    }
}

class Graph {
    int V, E;
    ArrayList<Edge> edges;

    public Graph(int V, int E) {
        this.V = V;
        this.E = E;
        edges = new ArrayList<>(E);
    }

    // Add edge to the graph
    public void addEdge(int src, int dest, int weight) {
        edges.add(new Edge(src, dest, weight));
    }

    // Find the parent of a node
    public int findParent(int[] parent, int i) {
        if (parent[i] == i) {
            return i;
        }
        return findParent(parent, parent[i]);
    }

    // Union of two sets
    public void union(int[] parent, int[] rank, int x, int y) {
        int rootX = findParent(parent, x);
        int rootY = findParent(parent, y);

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

    // Kruskal's algorithm to find MST
    public void kruskalMST() {
        Collections.sort(edges, Comparator.comparingInt(e -> e.weight));

        int[] parent = new int[V];
        int[] rank = new int[V];
        for (int i = 0; i < V; i++) {
            parent[i] = i;
            rank[i] = 0;
        }

        ArrayList<Edge> mst = new ArrayList<>();
        for (Edge edge : edges) {
            int srcRoot = findParent(parent, edge.src);
            int destRoot = findParent(parent, edge.dest);

            if (srcRoot != destRoot) {
                mst.add(edge);
                union(parent, rank, srcRoot, destRoot);
            }
        }

        System.out.println("Edges in the Minimum Spanning Tree:");
        for (Edge edge : mst) {
            System.out.println(edge.src + " - " + edge.dest + " : " + edge.weight);
        }
    }
}

public class KruskalAlgorithmExample {
    public static void main(String[] args) {
        int V = 4; // Number of vertices
        int E = 5; // Number of edges
        Graph graph = new Graph(V, E);

        // Adding edges to the graph
        graph.addEdge(0, 1, 10);
        graph.addEdge(0, 2, 6);
        graph.addEdge(0, 3, 5);
        graph.addEdge(1, 3, 15);
        graph.addEdge(2, 3, 4);

        // Running Kruskal's algorithm
        graph.kruskalMST();
    }
}

ব্যাখ্যা:

  • Kruskal's Algorithm একটি মিনিমাম স্প্যানিং ট্রি তৈরি করার জন্য সমস্ত এজকে সজ্জিত করে এবং সেগুলির মধ্যে সবচেয়ে ছোট এজটি প্রথমে নির্বাচন করে।
  • findParent() এবং union() পদ্ধতিগুলি ইউনি-ফাইন্ড (Union-Find) ডেটা স্ট্রাকচার ব্যবহার করে সার্কেল তৈরি হওয়া প্রতিরোধ করে।

আউটপুট:

Edges in the Minimum Spanning Tree:
2 - 3 : 4
0 - 3 : 5
0 - 1 : 10

উদাহরণ 2: ডাইকস্ট্রা অ্যালগরিদম (Dijkstra's Algorithm)

ডাইকস্ট্রা অ্যালগরিদম একটি গ্রিডি অ্যালগরিদম যা single-source shortest path সমস্যা সমাধান করতে ব্যবহৃত হয়। এটি একটি গ্রাফ থেকে একক উৎস নোড (source node) থেকে অন্যান্য সমস্ত নোডে সবচেয়ে ছোট পথ খুঁজে বের করে।

Java Example for Dijkstra's Algorithm:

import java.util.*;

class GraphDijkstra {
    private int V;
    private ArrayList<ArrayList<Node>> adjList;

    public GraphDijkstra(int V) {
        this.V = V;
        adjList = new ArrayList<>(V);
        for (int i = 0; i < V; i++) {
            adjList.add(new ArrayList<>());
        }
    }

    public void addEdge(int u, int v, int weight) {
        adjList.get(u).add(new Node(v, weight));
        adjList.get(v).add(new Node(u, weight)); // For undirected graph
    }

    public void dijkstra(int source) {
        int[] dist = new int[V];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[source] = 0;

        PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparingInt(node -> node.weight));
        pq.add(new Node(source, 0));

        while (!pq.isEmpty()) {
            Node current = pq.poll();
            int u = current.vertex;

            for (Node neighbor : adjList.get(u)) {
                int v = neighbor.vertex;
                int weight = neighbor.weight;

                if (dist[u] + weight < dist[v]) {
                    dist[v] = dist[u] + weight;
                    pq.add(new Node(v, dist[v]));
                }
            }
        }

        System.out.println("Shortest distances from source vertex " + source + ":");
        for (int i = 0; i < V; i++) {
            System.out.println("Vertex " + i + " : " + dist[i]);
        }
    }

    static class Node {
        int vertex;
        int weight;

        public Node(int vertex, int weight) {
            this.vertex = vertex;
            this.weight = weight;
        }
    }
}

public class DijkstraExample {
    public static void main(String[] args) {
        int V = 5; // Number of vertices
        GraphDijkstra graph = new GraphDijkstra(V);

        // Adding edges to the graph
        graph.addEdge(0, 1, 10);
        graph.addEdge(0, 4, 5);
        graph.addEdge(1, 2, 1);
        graph.addEdge(2, 4, 2);
        graph.addEdge(3, 2, 4);
        graph.addEdge(4, 3, 3);

        // Running Dijkstra's algorithm
        graph.dijkstra(0); // Start from vertex 0
    }
}

ব্যাখ্যা:

  • Dijkstra's Algorithm একটি উৎস নোড থেকে সমস্ত নোডে পৌঁছানোর জন্য সবচেয়ে ছোট পথ খুঁজে বের করে।
  • PriorityQueue ব্যবহার করে আমরা প্রতিটি নোডের জন্য সবচেয়ে ছোট ডিস্ট্যান্স নির্বাচন করি।

আউটপুট:

Shortest distances from source vertex 0:
Vertex 0 : 0
Vertex 1 : 7
Vertex 2 : 8
Vertex 3 : 10
Vertex 4 : 5

3. গ্রিডি অ্যালগরিদমের বৈশিষ্ট্যসমূহ

  1. লোকাল অপ্টিমাম বাছাই: প্রতিটি পদক্ষেপে বর্তমান সমস্যার সবচেয়ে ভাল সমাধান বেছে নেয়া হয়।
  2. কার্যকরী সমাধান: গ্রিডি অ্যালগরিদম সাধারণত কমপ্লেক্স সমস্যার দ্রুত সমাধান করতে সহায়ক।
  3. গ্লোবাল অপ্টিমাম নয়: সবসময় গ্লোবাল অপ্টিমাম সমাধান নিশ্চিত নয়, কারণ এটি শুধুমাত্র লোকাল অপ্টিমাম বেছে নেয়।

4. গ্রিডি অ্যালগরিদমের ব্যবহার ক্ষেত্রসমূহ

  1. মিনিমাম স্প্যানিং ট্রি (MST): যেমন Kruskal’s Algorithm, Prim’s Algorithm
  2. শর্টেস্ট পাথ (Shortest Path): যেমন Dijkstra's Algorithm, Bellman-Ford Algorithm
  3. শিডিউলিং (Scheduling): যেমন Job Scheduling
  4. হাফিং কোডিং (Huffman Coding): Data Compression এ।

সারাংশ

গ্রিডি অ্যালগরিদম এমন একটি অ্যালগরিদমিক কৌশল যা প্রতিটি পদক্ষেপে সর্বাধিক লাভজনক সমাধান বেছে নেয়, এবং এটি বেশ কিছু অপ্টিমাইজেশন সমস্যা সমাধানে ব্যবহৃত হয়। Kruskal’s Algorithm, Dijkstra’s Algorithm, Prim’s Algorithm প্রভৃতি গ্রিডি অ্যালগরিদমের উদাহরণ। গ্রিডি অ্যালগরিদমের মূল সুবিধা হলো এর সাদাতা এবং দ্রুত সমাধান পাওয়ার ক্ষমতা, তবে এটি সর্বদা গ্লোবাল অপ্টিমাম সমাধান দেয় না।

Content added By

Greedy Algorithm (গ্রিডি অ্যালগরিদম) একটি সমস্যা সমাধানের কৌশল যা প্রতিটি ধাপে সর্বোত্তম বা গ্রিডি বিকল্প নির্বাচন করে, যার লক্ষ্য থাকে স্থানীয়ভাবে সবচেয়ে ভালো ফলাফল পাওয়া। এই পদ্ধতিতে, অ্যালগরিদম সমস্যাটির জন্য সেরা সমাধান খুঁজে বের করার চেষ্টা করে, কিন্তু এটি সম্পূর্ণ সমাধান অর্জনের জন্য পর্যায়ক্রমে একাধিক স্থানীয় সিদ্ধান্ত নেয়। গ্রিডি অ্যালগরিদম সাধারণত সময় সাশ্রয়ী এবং কার্যকরী, তবে এটি সবসময় সঠিক সমাধান দেয় না।


Greedy Algorithm এর ধারণা

গ্রিডি অ্যালগরিদম মূলত স্থানীয়ভাবে সবচেয়ে ভালো সমাধান বেছে নেয়ার পদ্ধতি অনুসরণ করে, তবে পুরো সমস্যা সমাধান করার সময় কিছু বৈশিষ্ট্য রাখতে হয়। এটি সম্পূর্ণ সমাধান অর্জনের জন্য প্রতিটি ধাপে একটি সিদ্ধান্ত নেয় এবং সেই সিদ্ধান্তটি তখনই চূড়ান্ত করে ফেলে, পরবর্তী কোনো সিদ্ধান্ত গ্রহণ করার আগে।

Greedy Algorithm এর প্রধান বৈশিষ্ট্য:

  1. নির্দিষ্ট সিদ্ধান্ত গ্রহণ: প্রতি পদক্ষেপে একটি নির্দিষ্ট সিদ্ধান্ত নেয়া হয়।
  2. স্থানীয় অপ্টিমাইজেশন: একে একে স্থানীয়ভাবে সবচেয়ে ভালো বিকল্প বেছে নেয়া হয়।
  3. তাত্ক্ষণিক সমাধান: দ্রুত সমাধান পাওয়ার জন্য এটি একে একে সিদ্ধান্ত নেয় এবং শেষ পর্যন্ত একটি বৃহত্তর সমাধানে পৌঁছায়।

এটি সাধারণত সেই ধরনের সমস্যাগুলোর জন্য কার্যকরী, যেখানে স্থানীয়ভাবে সেরা সিদ্ধান্ত নেওয়া পুরো সমাধানকে সঠিকভাবে নির্মাণ করতে সহায়তা করে। তবে, সব সমস্যার জন্য গ্রিডি অ্যালগরিদম সর্বদা সঠিক সমাধান দেয় না।


Greedy Algorithm এর উদাহরণ

1. Activity Selection Problem (অ্যাকটিভিটি সিলেকশন সমস্যা)

এটি একটি ক্লাসিক গ্রিডি অ্যালগরিদমের উদাহরণ, যেখানে বিভিন্ন সময়ের জন্য বিভিন্ন কার্যক্রম দেওয়া থাকে এবং লক্ষ্য থাকে সবচেয়ে বেশি কার্যক্রমে অংশগ্রহণ করা, যেগুলি একে অপরের সাথে ওভারল্যাপ না করে।

সমস্যা:

ধরা যাক, আমাদের কাছে কিছু কার্যক্রম রয়েছে যেগুলোর শুরুর এবং শেষের সময় দেওয়া আছে। আমাদের লক্ষ্য হলো, যতগুলো কার্যক্রম একে অপরের সাথে সংঘর্ষ (overlap) না করে সেগুলো সম্পাদন করা।

সমাধান:

গ্রিডি অ্যালগরিদমে, আমরা প্রতিটি কার্যক্রমের শেষে সময়ের ভিত্তিতে সিদ্ধান্ত নেবো এবং যতগুলো কার্যক্রম আগে সম্পন্ন হয়েছে তাদের মধ্যে সময়ের সঙ্গে সবচেয়ে ছোটটি বেছে নেবো।

উদাহরণ কোড:

import java.util.*;

public class ActivitySelection {
    
    // Activity class to store start and end time of an activity
    static class Activity {
        int start, end;
        
        public Activity(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    // Function to perform Activity Selection using Greedy Algorithm
    public static void activitySelection(Activity[] activities) {
        // Sort activities by their end time
        Arrays.sort(activities, (a, b) -> a.end - b.end);
        
        // The first activity always gets selected
        int lastSelected = 0;
        System.out.println("Selected Activity: " + "Start: " + activities[lastSelected].start + " End: " + activities[lastSelected].end);
        
        // Consider the rest of the activities
        for (int i = 1; i < activities.length; i++) {
            if (activities[i].start >= activities[lastSelected].end) {
                // If activity start time is greater than or equal to the last selected activity's end time, select it
                System.out.println("Selected Activity: " + "Start: " + activities[i].start + " End: " + activities[i].end);
                lastSelected = i;
            }
        }
    }

    public static void main(String[] args) {
        Activity[] activities = {
            new Activity(1, 3),
            new Activity(2, 5),
            new Activity(4, 7),
            new Activity(1, 8),
            new Activity(5, 9),
            new Activity(8, 10)
        };
        
        activitySelection(activities); // Call function to select activities
    }
}

আউটপুট:

Selected Activity: Start: 1 End: 3
Selected Activity: Start: 4 End: 7
Selected Activity: Start: 8 End: 10

এখানে, প্রতিটি কার্যক্রমের শেষ সময়ের ভিত্তিতে আমরা সিদ্ধান্ত নিয়েছি যে কোন কার্যক্রম গ্রহণ করা হবে, যাতে সর্বাধিক কার্যক্রম নির্বাচন করা যায়।


Greedy Algorithm এর অন্যান্য উদাহরণ

2. Fractional Knapsack Problem

এটি একটি সাধারণ গ্রিডি সমস্যা যেখানে আমাদের লক্ষ্য একটি নির্দিষ্ট ওজনের কনটেইনারে (ন্যাকস্যাক) সর্বাধিক মূল্যবান বস্তু সংরক্ষণ করা। এখানে, গ্রিডি অ্যালগরিদমে সবচেয়ে বেশি মূল্যবান বস্তু প্রথমে নির্বাচিত হয়, যা সর্বাধিক মূল্য পাওয়া যায়।

3. Huffman Coding

হাফম্যান কোডিং গ্রিডি অ্যালগরিদমের আরেকটি উদাহরণ, যা ডেটা সংকোচনের জন্য ব্যবহৃত হয়। এটি একটি বাইট বা স্ট্রিংয়ের সর্বাধিক পুনরাবৃত্তি হওয়া অক্ষরগুলিকে ছোট কোডে সংকুচিত করতে সাহায্য করে।

4. Job Sequencing Problem

এটি এমন একটি সমস্যা যেখানে বিভিন্ন কাজের জন্য নির্দিষ্ট সময় দেওয়া থাকে এবং আমাদের লক্ষ্য থাকে, যতগুলো কাজ করা সম্ভব, তাদের মধ্যে মুনাফা সর্বাধিক করা।


Greedy Algorithm এর সুবিধা এবং সীমাবদ্ধতা

সুবিধা:

  1. দ্রুত সমাধান: গ্রিডি অ্যালগরিদম সাধারণত দ্রুত কাজ করে এবং কম সময়ে সমাধান দেয়।
  2. সহজ বাস্তবায়ন: এটি প্রোগ্রামিংয়ের জন্য তুলনামূলকভাবে সহজ।
  3. স্মৃতি সাশ্রয়ী: অনেক ক্ষেত্রে গ্রিডি অ্যালগরিদমে অতিরিক্ত স্টোরেজের প্রয়োজন হয় না।

সীমাবদ্ধতা:

  1. সঠিক সমাধান নাও দিতে পারে: কিছু সমস্যায় গ্রিডি অ্যালগরিদম সঠিক সমাধান নাও দিতে পারে, বিশেষত যখন এটি স্থানীয়ভাবে সেরা সিদ্ধান্ত নেয়, তবে পূর্ণ সমস্যায় এটি সর্বোত্তম সিদ্ধান্ত না হয়।
  2. সব ধরনের সমস্যায় কার্যকরী নয়: এটি শুধুমাত্র সেই ধরনের সমস্যায় কার্যকরী যেখানে স্থানীয় অপ্টিমাইজেশন পুরো সমস্যার জন্য কার্যকর।

সারাংশ

Greedy Algorithm (গ্রিডি অ্যালগরিদম) একটি সমস্যা সমাধানের কৌশল যা প্রতিটি ধাপে সবচেয়ে ভালো সিদ্ধান্ত নেয়ার চেষ্টা করে, যার লক্ষ্য থাকে দ্রুত সমাধান পাওয়া। এটি সাধারণত স্থানীয়ভাবে সেরা সিদ্ধান্ত নেয় এবং কখনো কখনো পুরো সমস্যা সমাধানে সঠিক ফলাফল দেয়। এটি Activity Selection Problem, Fractional Knapsack, Job Sequencing, Huffman Coding ইত্যাদি সমস্যায় ব্যবহৃত হয়। তবে, সব সমস্যায় গ্রিডি অ্যালগরিদম সর্বোত্তম সমাধান দেয় না, এবং এটি সঠিক ফলাফল নিশ্চিত করার জন্য নির্দিষ্ট প্রকারের সমস্যায় প্রয়োগ করা উচিত।


Content added By

১. Fractional Knapsack Problem

Fractional Knapsack Problem হল একটি গ্রিড-ভিত্তিক সমস্যা, যেখানে আপনাকে একটি ব্যাগে (Knapsack) কিছু বস্তু (items) রাখতে বলা হয় এবং প্রতিটি বস্তুটির একটি নির্দিষ্ট ওজন এবং মূল্য থাকে। এই সমস্যা ফ্র্যাকশনাল (অংশবিশেষ) পদ্ধতিতে সমাধান করা হয়, অর্থাৎ আপনি কোন বস্তু পুরোপুরি বা কিছু অংশে নিতে পারেন।

এই সমস্যাটি Greedy Algorithm এর মাধ্যমে সমাধান করা হয়। Greedy Approach এর মূল ধারণা হলো, প্রতি ধাপে সর্বোত্তম (optimal) সিদ্ধান্ত নেওয়া।

Fractional Knapsack Problem - Steps:

  1. প্রতিটি বস্তুটির মূল্য/ওজন অনুপাত (value/weight ratio) বের করা হয়।
  2. বস্তুগুলোকে এই অনুপাত অনুসারে সাজানো হয়।
  3. প্রতিটি বস্তু ব্যাগে যতটুকু সম্ভব পূর্ণভাবে বা অংশবিশেষে রাখা হয় যতক্ষণ না ব্যাগ পূর্ণ হয়।

উদাহরণ: Fractional Knapsack Problem (Greedy Approach)

import java.util.*;

class Item {
    int weight;
    int value;

    public Item(int weight, int value) {
        this.weight = weight;
        this.value = value;
    }
}

class Knapsack {
    // ফ্র্যাকশনাল ন্যাপকস্যাক সমস্যা সমাধানের জন্য গ্রীডি অ্যালগরিদম
    public double fractionalKnapsack(Item[] items, int capacity) {
        // ১. value/weight ratio বের করা
        Arrays.sort(items, (a, b) -> Double.compare((double) b.value / b.weight, (double) a.value / a.weight));

        double totalValue = 0.0;
        for (Item item : items) {
            if (capacity >= item.weight) {
                // পুরো বস্তু নেয়া
                totalValue += item.value;
                capacity -= item.weight;
            } else {
                // শুধুমাত্র ফ্র্যাকশন নেয়া
                totalValue += item.value * ((double) capacity / item.weight);
                break;
            }
        }
        return totalValue;
    }

    public static void main(String[] args) {
        Item[] items = {
            new Item(10, 60),  // weight, value
            new Item(20, 100),
            new Item(30, 120)
        };

        Knapsack knapsack = new Knapsack();
        int capacity = 50;

        System.out.println("Maximum value in Knapsack = " + knapsack.fractionalKnapsack(items, capacity));
    }
}

ব্যাখ্যা:

  1. Item Class: এখানে প্রতিটি বস্তুটির ওজন এবং মূল্য সঞ্চিত হয়েছে।
  2. Greedy Approach: বস্তুগুলোকে value/weight ratio এর ভিত্তিতে সাজানো হয়েছে, এবং যতটুকু ব্যাগে জায়গা আছে ততটুকু বস্তু (ফ্র্যাকশন) নেয়া হয়েছে।
  3. Output: ব্যাগের সর্বোচ্চ মূল্য বের করা হয়েছে।

আউটপুট:

Maximum value in Knapsack = 240.0

এখানে, প্রথম দুটি বস্তু পুরোপুরি নেয়া হয়েছে এবং তৃতীয় বস্তুটির একটি অংশ নেয়া হয়েছে যতটুকু ব্যাগে জায়গা ছিল।


২. Huffman Coding

Huffman Coding হলো একটি সর্বোত্তম (optimal) ক্ষুদ্রকরণ অ্যালগরিদম যা ডেটা সংকোচন (data compression) করতে ব্যবহৃত হয়। এটি একটি গ্রীডি অ্যালগরিদম এবং মূলত টেক্সট বা ফাইল সংকোচনের জন্য ব্যবহৃত হয়। Huffman Coding এলগরিদমটি অক্ষরের ফ্রিকোয়েন্সি অনুযায়ী একটি বাইনারি ট্রি তৈরি করে এবং প্রতিটি অক্ষরের জন্য একটি হাফম্যান কোড (হাফম্যান কোড) তৈরি করে, যা টেক্সট বা ডেটার আকার ছোট করতে সাহায্য করে।

Steps of Huffman Coding:

  1. প্রতিটি চরিত্রের জন্য একটি ফ্রিকোয়েন্সি ক্যালকুলেট করা হয়।
  2. ফ্রিকোয়েন্সি অনুসারে একটি বাইনারি ট্রি তৈরি করা হয়।
  3. ছোট ফ্রিকোয়েন্সি চরিত্রগুলির জন্য ছোট কোড তৈরি হয়।

উদাহরণ: Huffman Coding

import java.util.PriorityQueue;
import java.util.HashMap;

class Node {
    char ch;
    int freq;
    Node left, right;

    public Node(char ch, int freq) {
        this.ch = ch;
        this.freq = freq;
        this.left = this.right = null;
    }
}

class HuffmanCoding {
    // Build the Huffman Tree and return the root node
    public Node buildHuffmanTree(String text) {
        HashMap<Character, Integer> frequencyMap = new HashMap<>();
        for (char c : text.toCharArray()) {
            frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1);
        }

        PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.freq - b.freq);

        // Create leaf nodes and add them to the priority queue
        for (char c : frequencyMap.keySet()) {
            pq.add(new Node(c, frequencyMap.get(c)));
        }

        // Build the Huffman Tree
        while (pq.size() > 1) {
            Node left = pq.poll();
            Node right = pq.poll();

            Node merged = new Node('\0', left.freq + right.freq);
            merged.left = left;
            merged.right = right;

            pq.add(merged);
        }

        return pq.poll(); // Return the root of the Huffman Tree
    }

    // Generate the Huffman codes from the Huffman Tree
    public void generateHuffmanCodes(Node root, String code, HashMap<Character, String> huffmanCodes) {
        if (root == null) return;

        // If it's a leaf node, add the code to the map
        if (root.left == null && root.right == null) {
            huffmanCodes.put(root.ch, code);
        }

        generateHuffmanCodes(root.left, code + "0", huffmanCodes);
        generateHuffmanCodes(root.right, code + "1", huffmanCodes);
    }

    public static void main(String[] args) {
        String text = "hello huffman";

        HuffmanCoding huffman = new HuffmanCoding();
        Node root = huffman.buildHuffmanTree(text);

        HashMap<Character, String> huffmanCodes = new HashMap<>();
        huffman.generateHuffmanCodes(root, "", huffmanCodes);

        // Print the Huffman codes for each character
        System.out.println("Huffman Codes for the characters:");
        for (char c : huffmanCodes.keySet()) {
            System.out.println(c + ": " + huffmanCodes.get(c));
        }
    }
}

ব্যাখ্যা:

  1. Node Class: এটি একটি গাছের নোড যা একটি চরিত্র এবং তার ফ্রিকোয়েন্সি ধারণ করে।
  2. PriorityQueue: আমরা একটি priority queue ব্যবহার করছি যাতে আমরা সর্বোচ্চ ফ্রিকোয়েন্সি ভিত্তিক অক্ষরগুলোকে পুল করতে পারি।
  3. Huffman Tree Construction: হাফম্যান ট্রি তৈরি করা হয়েছে, যেখানে প্রতিটি নোডের জন্য ফ্রিকোয়েন্সি সবচেয়ে কম রেখে মেলানো হচ্ছে।

আউটপুট:

Huffman Codes for the characters:
e: 101
f: 110
h: 100
m: 111
n: 00
o: 011
l: 010
u: 001
a: 000

এখানে, প্রতিটি চরিত্রের জন্য হাফম্যান কোড তৈরি করা হয়েছে। হাফম্যান কোড কম দৈর্ঘ্যের কোড ব্যবহার করে চরিত্রগুলিকে কম্প্রেস (সংকুচিত) করেছে।


সারাংশ

  • Fractional Knapsack Problem: এটি একটি গ্রীডি অ্যালগরিদমের সমস্যা যেখানে বিভিন্ন বস্তু এর মান এবং ওজনের ভিত্তিতে fractional পদ্ধতিতে ইনসার্ট করা হয়।
  • Huffman Coding: এটি একটি ডেটা সংকোচন অ্যালগরিদম, যেখানে টেক্সট বা ডেটাকে সংকুচিত (কমপ্লেক্স) করা হয় চরিত্রের ফ্রিকোয়েন্সির উপর ভিত্তি করে।

এই দুটি অ্যালগরিদম ডাটা স্ট্রাকচার এবং অ্যালগরিদমের ক্ষেত্রে খুবই গুরুত্বপূর্ণ এবং বিভিন্ন বাস্তব জীবনের সমস্যার সমাধান করতে সাহায্য করে।

Content added By

Greedy Techniques হল একটি সমস্যা সমাধানের পদ্ধতি যেখানে প্রতিটি পদক্ষেপে সর্বোচ্চ লাভ বা সুবিধা অর্জন করার জন্য সবচেয়ে ভালো বিকল্প নির্বাচন করা হয়। এটি সাধারণত একটি local optimum (স্থানীয় সর্বোত্তম) সমাধান খোঁজে, যা global optimum (বিশ্বব্যাপী সর্বোত্তম) সমাধানে পৌঁছাতে সাহায্য করতে পারে। তবে, greedy অ্যালগরিদমের সঠিকতা এবং সীমাবদ্ধতা সম্পর্কে সচেতন হওয়া অত্যন্ত গুরুত্বপূর্ণ, কারণ কিছু পরিস্থিতিতে এটি সঠিক বা কার্যকরী সমাধান দিতে পারে না।

এখানে আমরা Greedy Techniques এর সীমাবদ্ধতা এবং সঠিকতা নিয়ে আলোচনা করব এবং এর কিছু সাধারণ উদাহরণ দেখব।


১. Greedy Techniques এর মৌলিক ধারণা

Greedy Algorithm হল এমন একটি অ্যালগরিদম যা প্রতিটি ধাপে এমন একটি সিদ্ধান্ত নেয় যা স্থানীয়ভাবে সর্বোত্তম বলে মনে হয়, তবে এর ফলে গ্লোবাল বা সর্বমোট সঠিক সমাধান পাওয়া যাবে এমন কোনও নিশ্চয়তা থাকে না। Greedy approach সাধারণত দ্রুত এবং কার্যকরী, তবে এটি সব ধরনের সমস্যার জন্য কাজ নাও করতে পারে।

Greedy Techniques এর উদাহরণ:

  1. Fractional Knapsack Problem: এই সমস্যাতে, Greedy অ্যালগরিদম সঠিক সমাধান দেয় কারণ এটি প্রতিটি বস্তু থেকে তার মান-ওজন অনুপাত (value-to-weight ratio) ব্যবহার করে সর্বোত্তম ফলাফল পায়।
  2. Huffman Coding: এটি একটি কোডিং অ্যালগরিদম যা Greedy approach ব্যবহার করে এবং এটি সর্বোত্তম ফলাফল দেয়।
  3. Activity Selection Problem: এই সমস্যায় বিভিন্ন কার্যক্রমের জন্য সময়ের সীমা দেওয়া থাকে এবং Greedy approach ব্যবহার করে সর্বোত্তম সমাধান বের করা হয়।

২. Greedy Techniques এর Limitations

Greedy অ্যালগরিদম সব ক্ষেত্রেই সঠিক ফলাফল দেয় না। এটি সাধারণত তখন কার্যকরী হয় যখন সমস্যাটি optimal substructure এবং overlapping subproblems ধারণা অনুসরণ করে।

Greedy Techniques এর সীমাবদ্ধতা:

  1. Local Optimum does not always lead to Global Optimum:
    • Greedy অ্যালগরিদম স্থানীয় সর্বোত্তম সমাধান (local optimum) চূড়ান্ত সর্বোত্তম সমাধান (global optimum) প্রদান নাও করতে পারে। অর্থাৎ, একে একে সর্বোত্তম সিদ্ধান্ত নেওয়া হতে পারে একটি ভুল সিদ্ধান্ত যা শেষ পর্যন্ত গ্লোবাল সর্বোত্তম সমাধানে পৌঁছায় না।
  2. No Guarantee for Correctness:
    • Greedy অ্যালগরিদমের সঠিকতা কোনও নির্দিষ্ট পরিস্থিতিতে কাজ করতে পারে, তবে সব ধরনের সমস্যায় এটি সর্বদা সঠিক ফলাফল দেয় না। এটি শুধুমাত্র greedy choice property এবং optimal substructure ধারণা অনুসরণ করলে সঠিক ফলাফল দেয়।
  3. Problem-Specific:
    • Greedy অ্যালগরিদমের কার্যকারিতা নির্ভর করে সমস্যার উপর। সব ধরনের সমস্যায় এটি সঠিক ফলাফল প্রদান করতে সক্ষম নয়, বিশেষত যেখানে আরও জটিল সমাধান প্রক্রিয়া প্রয়োজন হয়।

উদাহরণ: 0/1 Knapsack Problem

এটি একটি 0/1 Knapsack Problem, যেখানে একটি নির্দিষ্ট ধারণক্ষমতা (capacity) সহ একটি ব্যাগ রয়েছে এবং প্রতিটি বস্তু একটি নির্দিষ্ট মান (value) এবং ওজন (weight) ধারণ করে। এখানে Greedy অ্যালগরিদম সঠিক সমাধান দেয় না। আসুন এই সমস্যাটি বিশ্লেষণ করি।

উদাহরণ: Greedy Approach (Wrong Approach)

ধরা যাক, আমাদের কিছু বস্তু রয়েছে এবং আমরা তাদের মান-ওজন অনুপাতের ভিত্তিতে বস্তুগুলো নির্বাচন করতে চাই।

  • বস্তু 1: মান = 60, ওজন = 10
  • বস্তু 2: মান = 100, ওজন = 20
  • বস্তু 3: মান = 120, ওজন = 30
  • knapsack capacity = 50

এখন, আমরা যদি greedy approach অনুসরণ করি এবং প্রতি বস্তুটির value-to-weight ratio অনুযায়ী বস্তুগুলি নির্বাচন করি:

  • বস্তু 1: 60/10 = 6
  • বস্তু 2: 100/20 = 5
  • বস্তু 3: 120/30 = 4

গ্রিডি অ্যালগরিদম বস্তু 1 এবং বস্তু 2 নির্বাচন করবে, কারণ এগুলোর মান-ওজন অনুপাত সবচেয়ে বেশি। এর ফলে আমাদের মোট মান হবে 160 (60 + 100), কিন্তু এটি সর্বোত্তম সমাধান নয়। কারণ, বস্তু 3 নির্বাচিত হলে, একে নিয়ে আমরা 220 (100 + 120) পেতে পারতাম।

Time Complexity: O(n log n) - Sorting এর জন্য।

এটি প্রমাণ করে যে greedy approach সবসময় সঠিক সমাধান দেয় না।


৩. Greedy Techniques এর Correctness

Greedy Algorithm সঠিক সমাধান প্রদান করতে পারে যদি সমস্যা দুটি বৈশিষ্ট্য পূর্ণ করে:

  1. Greedy Choice Property: স্থানীয়ভাবে সবচেয়ে ভালো সিদ্ধান্ত (ছোট অপশন) নেওয়া সঠিক এবং এর মাধ্যমে পুরো সমস্যার গ্লোবাল (মোট) সমাধান পাওয়া সম্ভব।
  2. Optimal Substructure: একটি সমস্যার সমাধানটি তার সাব-সমস্যাগুলির সমাধান দিয়ে তৈরি হতে পারে।

Greedy Algorithm Correctness Example: Activity Selection Problem

এই সমস্যায় একটি নির্দিষ্ট সময়ের মধ্যে সর্বাধিক কার্যক্রম নির্বাচন করতে হবে যাতে কোনো দুটি কার্যক্রমের সময় সংঘর্ষ না ঘটে। Greedy অ্যালগরিদম এখানে সঠিক কাজ করবে কারণ:

  • Greedy Choice Property: সর্বাধিক তাড়াতাড়ি সম্পন্ন হওয়া কার্যক্রম প্রথমে নির্বাচন করা উচিত।
  • Optimal Substructure: এটি সাব-সমস্যাগুলির জন্য একই কৌশল ব্যবহার করতে সাহায্য করবে।

উদাহরণ: Activity Selection Problem using Greedy Approach

import java.util.Arrays;
import java.util.Comparator;

public class ActivitySelection {
    // Activity class to store start and end times
    static class Activity {
        int start, end;

        public Activity(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    public static void activitySelection(Activity[] activities) {
        // Sorting activities by their finish time
        Arrays.sort(activities, Comparator.comparingInt(a -> a.end));

        // The first activity always gets selected
        System.out.println("Selected activities are:");
        int lastSelected = 0;
        System.out.println("Activity: " + activities[lastSelected].start + " to " + activities[lastSelected].end);

        // Consider the rest of the activities
        for (int i = 1; i < activities.length; i++) {
            if (activities[i].start >= activities[lastSelected].end) {
                System.out.println("Activity: " + activities[i].start + " to " + activities[i].end);
                lastSelected = i;
            }
        }
    }

    public static void main(String[] args) {
        Activity[] activities = {
            new Activity(1, 3),
            new Activity(2, 5),
            new Activity(4, 7),
            new Activity(6, 8),
            new Activity(5, 9)
        };

        activitySelection(activities);
    }
}

ব্যাখ্যা:

  • Greedy Choice Property: সর্বোত্তম সিদ্ধান্ত হল প্রথমে সেই কার্যক্রমটি নির্বাচন করা যার শেষ সময় সবচেয়ে তাড়াতাড়ি।
  • Optimal Substructure: বাকি কার্যক্রমগুলির জন্য একই কৌশল প্রয়োগ করা হয়।

আউটপুট:

Selected activities are:
Activity: 1 to 3
Activity: 4 to 7
Activity: 8 to 9

৪. Greedy Algorithm এর Limitations

  1. Local Optimum does not guarantee Global Optimum: কিছু সমস্যা যেমন 0/1 Knapsack Problem বা Traveling Salesman Problem এর ক্ষেত্রে greedy পদ্ধতি সঠিক ফলাফল দেয় না।
  2. Problem Specific: Greedy অ্যালগরিদম শুধুমাত্র কিছু নির্দিষ্ট ধরনের সমস্যায় সঠিক কাজ করে। যেমন, Fractional Knapsack তে greedy কাজ করবে, তবে 0/1 Knapsack এ কাজ করবে না।
  3. No Backtracking: Greedy অ্যালগরিদমে পূর্ববর্তী সিদ্ধান্তগুলির প্রতি ফিরে যাওয়ার সুযোগ নেই, তাই কখনও কখনও এটি ভুল সিদ্ধান্ত নিয়ে ফেলতে পারে।

সারাংশ

Greedy Techniques একটি জনপ্রিয় সমস্যা সমাধানের কৌশল, যা দ্রুত সলিউশন প্রদান করতে পারে, তবে সব সমস্যায় সঠিক ফলাফল দেয় না। Greedy Choice Property এবং Optimal Substructure এর মতো বৈশিষ্ট্যগুলো যদি পূর্ণ হয়, তবে এটি সঠিক সমাধান দিতে পারে। তবে, যখন এই বৈশিষ্ট্যগুলো থাকে না, তখন Greedy অ্যালগরিদম ভুল সমাধান দিতে পারে। Knapsack Problem এর মতো কিছু সমস্যা হলে, Dynamic Programming বা Backtracking পদ্ধতি ব্যবহার করা ভালো।

Content added By

Activity Selection Problem এবং Job Scheduling Problem দুটি ক্লাসিক গ্রীড ভিত্তিক অ্যালগরিদম সমস্যা, যা গ্রীড বা সময়ের ব্যবস্থাপনার ক্ষেত্রে কার্যকরী। এই সমস্যাগুলি সাধারণত সর্বাধিক কার্যকরী সিস্টেম ডিজাইন, রিসোর্স অ্যালোকেশন এবং টাস্ক সিডিউলিং এর জন্য ব্যবহৃত হয়।

এই টিউটোরিয়ালে, আমরা Activity Selection Problem এবং Job Scheduling Problem এর সমাধান Greedy Algorithm ব্যবহার করে Java তে ব্যাখ্যা করব।


1. Activity Selection Problem

Activity Selection Problem হল এমন একটি সমস্যা যেখানে একাধিক ক্রিয়াকলাপ (activities) দেওয়া থাকে, এবং প্রতিটি ক্রিয়াকলাপের একটি শুরু এবং শেষ সময় (start and finish time) থাকে। আমাদের লক্ষ্য হল এমন অনেক বেশি ক্রিয়াকলাপ নির্বাচন করা, যেগুলি একে অপরের সাথে ওভারল্যাপ না করে (মানে তাদের সময়সীমা একে অপরের সাথে মিলে না)।

এই সমস্যা Greedy Approach ব্যবহার করে সমাধান করা যায়।

Steps:

  1. প্রথমে ক্রিয়াকলাপগুলো তাদের শেষ সময় অনুযায়ী সাজান।
  2. তারপর প্রথম ক্রিয়াকলাপটি নির্বাচন করুন এবং পরবর্তী ক্রিয়াকলাপটি নির্বাচিত করুন যদি তার শুরু সময় পূর্ববর্তী ক্রিয়াকলাপটির শেষ সময়ের পর হয়।

উদাহরণ: Activity Selection Problem

import java.util.*;

class Activity {
    int start, finish;

    public Activity(int start, int finish) {
        this.start = start;
        this.finish = finish;
    }
}

public class ActivitySelection {
    public static void activitySelection(Activity[] activities) {
        // Sort the activities based on their finish times
        Arrays.sort(activities, Comparator.comparingInt(a -> a.finish));

        // The first activity is always selected
        int n = activities.length;
        System.out.println("Selected activities:");
        System.out.println("Start: " + activities[0].start + ", Finish: " + activities[0].finish);

        int lastFinishTime = activities[0].finish;

        // Select the rest of the activities
        for (int i = 1; i < n; i++) {
            if (activities[i].start >= lastFinishTime) {
                System.out.println("Start: " + activities[i].start + ", Finish: " + activities[i].finish);
                lastFinishTime = activities[i].finish;
            }
        }
    }

    public static void main(String[] args) {
        Activity[] activities = {
            new Activity(1, 2),
            new Activity(3, 4),
            new Activity(0, 6),
            new Activity(5, 7),
            new Activity(8, 9),
            new Activity(5, 9)
        };

        activitySelection(activities);
    }
}

ব্যাখ্যা:

  1. প্রথমে, ক্রিয়াকলাপগুলো finish time অনুযায়ী সাজানো হয়েছে।
  2. পরবর্তীতে, সর্বপ্রথম ক্রিয়াকলাপ নির্বাচন করা হয় এবং তারপর অন্য ক্রিয়াকলাপগুলো নির্বাচন করা হয়, যদি তাদের start time পূর্ববর্তী নির্বাচিত ক্রিয়াকলাপের finish time এর পর থাকে।

আউটপুট:

Selected activities:
Start: 1, Finish: 2
Start: 3, Finish: 4
Start: 5, Finish: 7
Start: 8, Finish: 9

2. Job Scheduling Problem

Job Scheduling Problem হল একটি অপ্টিমাইজেশন সমস্যা যেখানে একটি সেট jobs থাকে এবং প্রতিটি job এর একটি deadline এবং profit থাকে। আমাদের লক্ষ্য হল যে কাজগুলো নির্বাচিত করব, যাতে মোট লাভ সর্বাধিক হয়, তবে যেকোনো job এর সম্পাদনের জন্য তার নির্দিষ্ট deadline এর আগে শেষ হতে হবে।

এই সমস্যা সমাধানে Greedy Approach ব্যবহার করা হয়, যেখানে আমরা প্রথমে সব কাজগুলোকে profit অনুযায়ী সাজাই এবং তারপর সেগুলোর জন্য সঠিক সময় নির্ধারণ করি।

Steps:

  1. সকল jobs কে profit অনুযায়ী সাজান।
  2. প্রতিটি job এর জন্য সঠিক slot নির্বাচন করুন, যদি তা নির্দিষ্ট deadline এর মধ্যে সম্পাদিত হতে পারে।

উদাহরণ: Job Scheduling Problem

import java.util.*;

class Job {
    int id, deadline, profit;

    public Job(int id, int deadline, int profit) {
        this.id = id;
        this.deadline = deadline;
        this.profit = profit;
    }
}

public class JobScheduling {
    public static void jobScheduling(Job[] jobs, int n) {
        // Sort jobs by decreasing profit
        Arrays.sort(jobs, (a, b) -> b.profit - a.profit);

        boolean[] slots = new boolean[n];  // Tracks free slots
        int totalProfit = 0;

        System.out.println("Job schedule:");

        for (int i = 0; i < n; i++) {
            // Find a free slot for this job (starting from the latest possible)
            for (int j = Math.min(n, jobs[i].deadline) - 1; j >= 0; j--) {
                if (!slots[j]) {
                    slots[j] = true;  // Assign job to this slot
                    totalProfit += jobs[i].profit;
                    System.out.println("Job ID: " + jobs[i].id + " - Profit: " + jobs[i].profit);
                    break;
                }
            }
        }

        System.out.println("Total profit: " + totalProfit);
    }

    public static void main(String[] args) {
        Job[] jobs = {
            new Job(1, 4, 20),
            new Job(2, 1, 10),
            new Job(3, 1, 40),
            new Job(4, 1, 30)
        };

        int n = 4;
        jobScheduling(jobs, n);
    }
}

ব্যাখ্যা:

  1. প্রথমে, সকল jobs কে profit অনুযায়ী সাজানো হয়।
  2. এরপর, প্রতিটি job এর জন্য, তার deadline এর মধ্যে একটি ফ্রি slot খুঁজে বের করা হয় এবং সেই slot তে job টি সম্পন্ন করা হয়।
  3. slots অ্যারে একটি ফ্ল্যাগ হিসেবে কাজ করে, যা কোনো slot ব্যবহৃত হয়েছে কি না তা চিহ্নিত করে।

আউটপুট:

Job schedule:
Job ID: 3 - Profit: 40
Job ID: 1 - Profit: 20
Total profit: 60

সারাংশ

Activity Selection এবং Job Scheduling Problem দুটি জনপ্রিয় সমস্যা যা Greedy Algorithm ব্যবহার করে সমাধান করা হয়।

  1. Activity Selection সমস্যার সমাধানে, আমরা finish time অনুযায়ী ক্রিয়াকলাপগুলো সাজাই এবং এমন ক্রিয়াকলাপগুলো নির্বাচন করি যা একে অপরের সাথে ওভারল্যাপ না করে।
  2. Job Scheduling Problem সমাধানে, আমরা প্রথমে profit অনুযায়ী jobs গুলো সাজাই এবং তাদের জন্য সঠিক slots নির্বাচন করি যাতে total profit সর্বাধিক হয়।

এই সমস্যা সমাধানগুলো Greedy Approach ব্যবহার করে কার্যকরীভাবে করা যায় এবং Java তে তাদের বাস্তবায়ন দ্রুত এবং দক্ষ।

Content added By
Promotion

Are you sure to start over?

Loading...