Wednesday, March 15, 2023

Divide and Conquer

Divide and conquer is a problem-solving technique in computer science where a problem is divided into smaller sub-problems, solved independently, and then combined to solve the original problem. This approach is often used to solve complex problems efficiently, as breaking a problem into smaller pieces can make it more manageable.

The Divide and Conquer algorithm typically consists of three steps:

  1. Divide: The problem is divided into smaller sub-problems that can be solved independently. This is usually done recursively, so that each sub-problem can be divided into smaller sub-problems until the sub-problems are simple enough to be solved directly.

  2. Conquer: Each sub-problem is solved independently.

  3. Combine: The solutions of the sub-problems are combined to solve the original problem.

Here is an example of the Divide and Conquer algorithm that sorts an array of integers using the Merge Sort algorithm:

public class MergeSort {

    public static void mergeSort(int[] arr, int left, int right) {

        if (left < right) {

            // Find the middle point

            int mid = (left + right) / 2;


            // Recursively sort first and second halves

            mergeSort(arr, left, mid);

            mergeSort(arr, mid + 1, right);


            // Merge the sorted halves

            merge(arr, left, mid, right);

        }

    }


    public static void merge(int[] arr, int left, int mid, int right) {

        // Find sizes of two subarrays to be merged

        int n1 = mid - left + 1;

        int n2 = right - mid;


        // Create temporary arrays

        int[] L = new int[n1];

        int[] R = new int[n2];


        // Copy data to temporary arrays

        for (int i = 0; i < n1; ++i)

            L[i] = arr[left + i];

        for (int j = 0; j < n2; ++j)

            R[j] = arr[mid + 1 + j];


        // Merge the temporary arrays

        int i = 0, j = 0;

        int k = left;

        while (i < n1 && j < n2) {

            if (L[i] <= R[j]) {

                arr[k] = L[i];

                i++;

            } else {

                arr[k] = R[j];

                j++;

            }

            k++;

        }


        // Copy remaining elements of L[] and R[] if any

        while (i < n1) {

            arr[k] = L[i];

            i++;

            k++;

        }


        while (j < n2) {

            arr[k] = R[j];

            j++;

            k++;

        }

    }


    public static void main(String[] args) {

        int[] arr = { 38, 27, 43, 3, 9, 82, 10 };

        int n = arr.length;


        System.out.println("Original Array:");

        for (int i = 0; i < n; ++i) {

            System.out.print(arr[i] + " ");

        }


        mergeSort(arr, 0, n - 1);


        System.out.println("\nSorted Array:");

        for (int i = 0; i < n; ++i) {

            System.out.print(arr[i] + " ");

        }

    }

}


The Merge Sort algorithm is a sorting algorithm that uses the Divide and Conquer approach to sort an array. The basic idea behind the Divide and Conquer approach is to break a problem down into smaller subproblems that are easier to solve, solve the subproblems recursively, and then combine the solutions to the subproblems to obtain the solution to the original problem.

In the case of Merge Sort, the problem is to sort an array of integers in ascending order. To do this, the array is first divided into two subarrays of roughly equal size. This is done recursively until the subarrays contain only one element, at which point they are already sorted.

Once the subarrays are sorted, they are merged back together to create a sorted array. This is done by comparing the first elements of each subarray and selecting the smaller of the two to put into the merged array. The process is repeated until all elements have been merged into the sorted array.

In the Java program I provided, the mergeSort function takes three arguments: the array to be sorted (arr), the left index of the subarray to be sorted (left), and the right index of the subarray to be sorted (right).

The mergeSort function checks if the left index is less than the right index. If it is, the function proceeds to divide the subarray into two halves by finding the middle index.

The function recursively calls itself on the two halves of the subarray. Once the subarrays are sorted, the merge function is called to merge the two sorted subarrays into a single sorted array.

The merge function takes four arguments: the array to be merged (arr), the left index of the first subarray (left), the right index of the first subarray and left index of the second subarray (mid), and the right index of the second subarray (right).

The function creates two temporary arrays (L and R) to store the two subarrays to be merged. The function then iterates through both arrays and compares the elements at each index. The smaller element is added to the merged array, and the index of the corresponding subarray is incremented. Once one of the subarrays has been completely merged, the remaining elements from the other subarray are added to the merged array.

Once the merge function has completed, the mergeSort function will have sorted the subarray between the left and right indices. This process is repeated recursively until the entire array is sorted.

Tracking back with backtracking algorithm

Backtracking is a technique used in programming to explore all possible solutions to a problem by traversing a tree-like structure of possible outcomes. Here is an example of a Java program that uses backtracking to solve a simple problem.

The Problem:

Suppose we have a set of integers, and we want to find all possible subsets of that set whose sum is equal to a given target number.

The Solution:

We can use backtracking to explore all possible subsets of the set and keep track of the ones that add up to the target sum.

The Java Program:

public class SubsetSumBacktracking {


    public static void main(String[] args) {

        int[] set = {3, 9, 8, 4, 5, 7};

        int targetSum = 15;

        findSubsets(set, targetSum);

    }


    public static void findSubsets(int[] set, int targetSum) {

        boolean[] used = new boolean[set.length];

        int sumSoFar = 0;

        findSubsetsHelper(set, used, sumSoFar, targetSum, 0);

    }


    public static void findSubsetsHelper(int[] set, boolean[] used, int sumSoFar, int targetSum, int start) {

        if (sumSoFar == targetSum) {

            // We have found a subset that adds up to the target sum

            printSubset(set, used);

        } else if (sumSoFar < targetSum) {

            // Try all possible combinations of unused elements

            for (int i = start; i < set.length; i++) {

                if (!used[i]) {

                    used[i] = true;

                    sumSoFar += set[i];

                    findSubsetsHelper(set, used, sumSoFar, targetSum, i + 1);

                    // Backtrack by undoing the last element added to the sum

                    used[i] = false;

                    sumSoFar -= set[i];

                }

            }

        }

    }


    public static void printSubset(int[] set, boolean[] used) {

        System.out.print("{ ");

        for (int i = 0; i < set.length; i++) {

            if (used[i]) {

                System.out.print(set[i] + " ");

            }

        }

        System.out.println("}");

    }


}

                      

Explanation:

  1. The main method initializes the set of integers and the target sum and calls the "findSubsets" method to start the backtracking process.
  2. The "findSubsets" method initializes a boolean array called "used" to keep track of which elements of the set have been used so far, and a variable called "sumSoFar" to keep track of the sum of the elements used in the current subset. It then calls the "findSubsetsHelper" method to start the recursive backtracking process.
  3. The "findSubsetsHelper" method is where the backtracking happens. If the "sumSoFar" equals the "targetSum", it means we have found a subset that adds up to the target sum, so we call the "printSubset" method to print out the subset.
  4. If the "sumSoFar" is less than the "targetSum", it means we still have room to add more elements to the subset. We loop through the remaining unused elements of the set and try adding each one to the sum by setting its corresponding value in the "used" array to "true". We then recursively call "findSubsetsHelper" with the updated "used" array, "sumSoFar", and "start" index (which is incremented by 1 to avoid using the same element twice).
  5. If the recursive call returns without finding a subset

Divide and Conquer

Divide and conquer is a problem-solving technique in computer science where a problem is divided into smaller sub-problems, solved independ...