Day94: divide and conquer strategy of sorting algorithm

Keywords: Java Algorithm data structure

  1. Day93: loop invariants of sorting algorithm < insert, select, bubble >

Divide and conquer strategy

Divide And Conquer: resolve a big problem into countless same small problems for solution, that is, the use of recursion.

Sorted divide and conquer strategy:

  1. Continuously split set A into the same small set, and finally return to the solution one by one [the external chain picture transfer fails, and the source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-5xwkr1uh-1635693287652) (review the classic sorting algorithm. assets/image-20211031204640261.png)]

    Important: the termination condition must be emphasized, otherwise you will see that the famous programming website (Stack Overflow) is cold.

  2. Pseudo code:

Merge sort

Refer to the blog: Merge sort

  1. Concept:

    1. Divide the unordered array into left and right groups with the middle corner mark

    2. Repeat step 1 for each group and split it continuously, that is, recursive call

    3. Until (the difference between the left and right indexes is < = 1), it can be merged

    4. Suppose that two B and C arrays are finally returned, which are combined as follows

      Finally, return: [5, 7, 8, 9, 10, 13, 19, 20] to finish sorting

  2. Code implementation:

    public class MergeSort implements IMutableSorter {
        @Override
        public void sort(int[] A) {
            mergeSort(A, 0, A.length);
        }
        //l: Left index, r: right index
        private void mergeSort(int[] A, int l, int r) {
            //The termination condition must be declared for recursive calls
            //When the left and right indexes move to only one element, they start merging up
            if(r - l <=1 ){
                return;
            }
            //Middle corner mark (whether + 1 depends on personal habits), and then divide it left and right according to the middle corner mark
            int mid = (l+r+1)/2;
            //Sort out the left array of the current (sub) array first
            mergeSort(A,l,mid);
            //Rearrange the right array of the current (sub) array
            mergeSort(A,mid,r);
            //Merge array
            merge(A, l, mid, r);
        }
    
        private void merge(int[] A, int l, int mid, int r) {
            //Using sentinel Integer.MAX_VALUE prevents null pointer error
            int[] left = Arrays.copyOfRange(A, l, mid + 1);
            int[] right = Arrays.copyOfRange(A, mid, r + 1);
            left[left.length - 1] = right[right.length - 1] = Integer.MAX_VALUE;
    
            //Set the initial subscript for the left and right arrays
            int i = 0,j = 0;
            //Start traversal, placing elements from left to right
            for(int k = l;k<r;k++){
                if(left[i]< right[j]){
                    A[k] = left[i++];
                }else{
                    A[k] = right[j++];
                }
            }
        }
    }
    
  3. Key points of code:

    The difference between Arrays.copyOfRange() and Arrays.copyOf():

    1. copyOfRange: the array length will be recalculated

      public static int[] copyOfRange(int[] original, int from, int to) {
          int newLength = to - from;
          if (newLength < 0)
              throw new IllegalArgumentException(from + " > " + to);
          int[] copy = new int[newLength];
          System.arraycopy(original, from, copy, 0,
                           Math.min(original.length - from, newLength));
          return copy;
      }
      
    2. copyOf(): the array length is directly received

      public static int[] copyOf(int[] original, int newLength) {
          int[] copy = new int[newLength];
          System.arraycopy(original, 0, copy, 0,
                           Math.min(original.length, newLength));
          return copy;
      }
      
  4. Recursive manual debugging is really troublesome. It's like a doll. You can't finish drawing. You can observe the last step by assigning debug to the idea condition

    Then push back slowly to understand.

  5. verification:

  6. Summary:

    1. Time complexity: recursion is used. According to the corresponding calculation, the time complexity is O(n · logN)

    2. Stable sorting algorithm: stable

      reference resources: Various sorting implementations and stability analysis

      1. Merge sort is to recursively divide the sequence into short sequences. The recursive exit is that the short sequence has only one element (considered as direct order) or two sequences (one comparison and exchange), and then merge each ordered segment sequence into an ordered long sequence, and continue to merge until all the original sequences are in order.
      2. It can be found that when there are one or two elements, one element will not be exchanged, and if the two elements are of equal size, no one will deliberately exchange, which will not destroy the stability.
      3. So, in the process of merging short ordered sequences, is stability destroyed? No, during the merging process, we can ensure that if the two current elements are equal, we save the elements of the previous sequence in front of the result sequence, so as to ensure stability.
      4. Therefore, merge sort is also a stable sort algorithm.

Quick sort

  1. Fast sorting and merge sorting are classic divide and conquer ideas, but there are still many differences:

    The difference and relation between quick sort and merge sort

    Comparison of execution time between merge sort and quick sort

    1. Merging and sorting need to open up a new space to merge data; Fast scheduling is directly operated on the original array
    2. Merge sort The grouping strategy of is to assume that the elements to be sorted are stored in the array, then it takes the first half of the elements of the array as a group and the latter half as another group.
    3. and Quick sort It is grouped according to the value of elements, that is, elements greater than a certain value are placed in one group and elements less than are placed in another group. This value is called the benchmark.
    4. In general, if the grouping strategy is simpler, the later merging strategy will be more complex, because quick sorting has been grouped according to the element size. During merging, only two groups need to be merged, while merging sorting needs to merge two ordered arrays according to the size.
  2. realization:

    1. 1.8 implementation of streaming Computing:

      public class QuickSort implements IImutableSorter {
          @Override
          public List<Integer> sort(List<Integer> A) {
              return this.quickSort(A);
          }
      
          private List<Integer> quickSort(List<Integer> A) {
      
              if (A.size() <= 1) {
                  return A;
              }
              // Construction target:
             	// |---left---| x | ---right ---||
              Integer x = A.get(0);
              //Find all new collections less than x in the collection
              List<Integer> left = A.stream().filter(a -> a < x).collect(toList());
              //Find all new sets equal to x in the set
              List<Integer> mid = A.stream().filter(a -> a == x).collect(toList());
              //Find all new collections greater than x in the collection
              List<Integer> right = A.stream().filter(a -> a > x).collect(toList());
              //Recursive sorting
              left = quickSort(left);
              right = quickSort(right);
              //Merge return
              left.addAll(mid);
              left.addAll(right);
              return left;
          }
      }
      
    2. Introduction to flow calculation method: JAVA streaming computing

      1. All interfaces inherited from Collection can be directly converted to streams:

        Stream<Integer> stream = A.stream();
        
      2. filter()

        Method definition:

        Stream<T> filter(Predicate<? super T> predicate)
        

        Method introduction:

        1. It is used to filter data and filter qualified data;
        2. Receive a Predicate function interface for condition filtering; Return a new stream composed of qualified data;

        Code example:

        List<Integer> l1 = Arrays.asList(1,2,3,4,5);  
        //Filter out elements greater than 2  
        Stream<Integer> integerStream = l1.stream().filter(s -> s > 2);
        
      3. collect(): convert the data in the stream into a new data structure;

        1. Code example:

          //Convert stream to List data structure
          List<Integer> collect1 = Stream.of(1, 2, 3).collect(Collectors.toList());
          
          //Convert flow to Map structure
          Student student1 = new Student("xiaoni", "male", 15);
          Student student2 = new Student("xiaohua", "female", 20);
          List<Student> l1 = new ArrayList<>();
          l1.add(student1);
          l1.add(student2);
          HashMap<String, Student> collect = l1.stream()
              .collect(() -> new HashMap<String, Student>(), 
                       (h, v) -> {h.put(v.getName(),v);},
                       HashMap::putAll);
          
    3. Native algorithm implementation:

      public class QuickSort1 implements IMutableSorter {
          @Override
          public void sort(int[] A) {
              this.quickSort(A, 0, A.length);
          }
      
          private void quickSort(int[] A, int l, int r) {
              //Recursive termination condition
              if (r - l <= 1) {
                  return;
              }
              // Select the leftmost element to construct the set of subproblems
              // Those less than x are placed on the left, and those greater than x are placed on the right
              // int x = A[l];
              // i represents the position of x
              int i = partition(A, l, r);
              // Omit the position i where x is calculated
              // And put all elements less than x on the left and elements greater than x on the right
              quickSort(A, l, i);
              quickSort(A, i + 1, r);
          }
      
          private int partition(int[] A, int l, int r) {
              int x = A[l];
              
              int i = l + 1;
              int j = r;
      
              while (i != j) {
                  if (A[i] < x) {
                      i++;
                  } else {
                      Helper.swap(A, i, --j);
                  }
              }
              Helper.swap(A, i - 1, l);
              return i - 1;
          }
      }
      
  3. Summary:

    1. Unstable sorting algorithm:
      1. Quick sort has two directions. When a [i] < = a [center_index], the I subscript on the left always goes to the right, where center_ Index is the array index of the central element, which is generally taken as the 0th element of the array.
      2. When a[j] > a[center_index], the j subscript on the right goes all the way to the left. If I and J can't move, I < = J, exchange a[i] and a[j], and repeat the above process until I > J. Exchange a[j] and a[center_index] to complete a quick sort. When the central elements are exchanged with a[j], it is likely to disrupt the stability of the previous elements
    2. For example, the sequence is 5 3 3 4 3 8 9 10 11. Now the exchange of central elements 5 and 3 (the fifth element, the subscript starts from 1) will disrupt the stability of element 3. Therefore, quick sorting is an unstable sorting algorithm. The instability occurs when the central element and a[j] exchange.
  4. Comparison and summary of divide and conquer ranking:

    1. Merge sort:

    2. Quick sort:

      Summary:

Posted by mdgottlieb on Sun, 31 Oct 2021 07:36:48 -0700