Recently when I read < >, I learned a lot of common sorting algorithms, so I wrote a reading note to record the ideas and implementation of these sorting algorithms.

Bubble sort


Bubble sort is a very simple elementary sorting algorithm that compares two adjacent elements at a time and swaps them if they are in the wrong order. Because the smallest elements float to the top through constant exchange, it is called bubble sort.

Bubble sort requires O(n^2) comparisons for n elements, so it is inefficient to sort large arrays.

Operation process

  1. Compare two adjacent elements and swap if the second element is less than the first (descending order is the opposite).
  1. Do the same for each pair of adjacent elements, starting with the first pair and ending with the last pair. When done, the last element will be the largest.
  1. Repeat for all elements except the last one.
  1. Keep repeating these steps for each smaller and smaller number of elements until the entire array is ordered (that is, there are no pairs of elements to compare).

Code implementation

Public static void sort(Comparable[] a) {for (int I = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1 - i; j++) { if (less(a[j + 1], a[j])) { exch(a, j, j + 1); }}}}Copy the code

The complete code

/** * Bubble Sort * * @author SylvanasSun * */ public class Bubble { // This class should not be instantiated. private Bubble() { } /** * Rearranges the array in ascending order, using the natural order. * * @param a * a the array to be sorted */ public static void sort(Comparable[] a) { for (int i = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1 - i; j++) { if (less(a[j + 1], a[j])) { exch(a, j, j + 1); } } } } /** * Rearranges the array in ascending order, using a comparator. * * @param a * a the arry to be sorted * @param comparator * comparator the comparator specifying the order */ public static void sort(Object[] a, Comparator comparator) { for (int i = 0; i < a.length - 1; i++) { for (int j = 0; j < a.length - 1 - i; j++) { if (less(comparator, a[j + 1], a[j])) { exch(a, j, j + 1); } } } } // a < b ? private static boolean less(Comparable a, Comparable b) { return a.compareTo(b) < 0; } // a < b ? private static boolean less(Comparator comparator, Object a, Object b) { return comparator.compare(a, b) < 0; } // exchange a[i] and a[j] private static void exch(Object[] a, int i, int j) { Object temp = a[i]; a[i] = a[j]; a[j] = temp; } // print array elements to console public static void print(Comparable[] a) { for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } } // test public static void main(String[] args) { String[] s = new Scanner(System.in).nextLine().split("\\s+"); Bubble.sort(s); Bubble.print(s); }}Copy the code

Selection sort


Selection sort is also a very simple and intuitive primary sorting algorithm, its idea is to constantly select the smallest of the remaining elements.

It has the following two characteristics.

  1. Running time has nothing to do with the input model In selection, in order to find the smallest element and scanning array again don’t provide what information for the next round of scanning, even if the input is an orderly array or the primary key all equal random arrangement disordered and an element of an array of array used in sort time for the same length.
  1. Data movement is minimal if the element is in the correct position, it will not be moved. Each time a pair of elements is swapped, at least one of them will be moved to its final position, so sorting a list of N elements does at most n-1 swaps.

Operation process

  1. First, find the smallest element in the array
  1. Second, swap it with the first element of the array (or with itself if the first element is the smallest)
  1. Again, find the smallest element among the remaining elements and swap it with the second element in the array. And so on until the array is in order.

Code implementation

public static void sort(Comparable[] a) { for (int i = 0; i < a.length; i++) { int min = i; // the smallest element index for (int j = i + 1; j < a.length; j++) { if (less(a[j], a[min])) min = j; exch(a, i, min); }}}Copy the code

Insertion sort


Like selection sort, all elements to the left of the current index are ordered, but their final position is not determined. It builds an ordered sequence and, for unordered elements, scans back to front in the ordered sequence to find the appropriate position and insert.

The time required for insertion sort depends on the initial order of elements in the input model. Insertion sort is much more efficient when the input model is a partially ordered array.

So insertion sort is very efficient for partially ordered arrays, but also for small arrays.

Operation process

  1. Starting with the first element, the element can be considered ordered
  1. Take the next element and scan back to front in the ordered sequence
  1. If the element (sorted) is larger than the new element, move it to the next position (right move)
  1. Repeat step 3 until you find a place where the sorted element is less than or equal to the new element
  1. After inserting the new element into the location
  1. Repeat steps 2 to 5

Code implementation


                                                        
public static void sort(Comparable[] a) {
		for (int i = 0; i < a.length; i++) {
			// a[i] insert to a[i-1]、a[i-2]、a[i-3]...
			for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {
				exch(a, j, j - 1);
			}
		}
	}

                                                    Copy the code

To optimize the

There’s a lot more you can do to optimize insertion sort, so here are two examples.

  • The binary search method is used to reduce the number of comparison operations.
    
                                                                    
    public static void sort(Comparable[] a) {
    	int length = a.length;
    	for (int i = 1; i < length; i++) {
    		// binary search to determine index j at which to insert a[i]
    		Comparable v = a[i];
    		int lo = 0, hi = i;
    		while (lo < hi) {
    			int mid = lo + (hi - lo) / 2;
    			if (less(v, a[mid]))
    				hi = mid;
    			else
    				lo = mid + 1;
    		}
    		// insertion sort with "half exchanges"
    		// (insert a[i] at index j and shift a[j], ..., a[i-1] to right)
    		for (int j = i; j > lo; --j)
    			a[j] = a[j - 1];
    		a[lo] = v;
    	}
    }
    
                                                                Copy the code
  • Move larger elements to the right in the inner loop instead of always swapping two elements (the number of array accesses can be halved)
    public static void sort(Comparable[] a) { int length = a.length; // put smallest element in position to serve as sentinel int exchanges = 0; for (int i = length - 1; i > 0; i--) { if (less(a[i], a[i - 1])) { exch(a, i, i - 1); exchanges++; } } if (exchanges == 0) return; // insertion sort with half-exchanges for (int i = 2; i < length; i++) { Comparable v = a[i]; int j = i; while (less(v, a[j - 1])) { a[j] = a[j - 1]; j--; } a[j] = v; }}Copy the code

Hill sorting


Hill sort, also known as the descending incremental sort algorithm, is a more efficient and improved version of insertion sort.

Since insertion sort is not efficient for large arrays of random numbers because it swaps only adjacent elements, elements can only be moved from one end of the array to the other bit by bit.

Hill sort simply improves insertion sort to speed things up, swapping non-contiguous elements to sort parts of an array, and eventually using insertion sort to sort locally ordered arrays.

The idea of Hill sort is to make any element with h interval in the array be ordered. It can be said that an ordered array of H is an array composed of H independent ordered arrays woven together.

Code implementation

public static void sort(Comparable[] a) { int h = 1; While (h < a. ength / 3) {/ / h sequence 1,4,13,40,121,364,1093,... h = h * 3 + 1; } while (h >= 1) { for (int i = h; i < a.length; i++) { // a[i] insert to a[i-h],a[i-2*h],a[i-3*h]... for (int j = i; j >= h && less(a[j], a[j - h]); j -= h) { exch(a, j, j - h); } } h = h / 3; }}Copy the code

Merge sort


Merge sort is a typical application of divide-and-conquer algorithm. Merging is the merging of two ordered arrays into a larger ordered array.

One major drawback is that it requires extra space (auxiliary arrays) and the extra space required is proportional to N.

Merging process

  1. Apply for a space that is the sum of two ordered sequences and is used to store the combined sequence
  1. Declares two Pointers, starting at the start of two ordered sequences
  1. Compares the elements to which the two Pointers point, selects the smaller element into the merge space, and moves the pointer to the next position
  1. Repeat step 3 until a pointer reaches the end of the sequence
  1. Place all remaining elements of the other sequence directly at the end of the merge sequence

Top down merge sort

Top-down is solving a problem recursively from the top.

For example, to sort the array A [lo..hi], split it into a[lo..mid] and a[mid+1..hi], sort them separately through recursive calls, and merge the ordered subarrays into the final sort result.


                                                        
// stably merge a[lo .. mid] with a[mid+1 ..hi] using aux[lo .. hi]
	private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
		// copy a[] to aux[]
		for (int k = lo; k <= hi; k++) {
			aux[k] = a[k];
		}
		// merge back to a[]
		int i = lo, j = mid + 1;
		for (int k = lo; k <= hi; k++) {
			if (i > mid) {
				a[k] = aux[j++];
			} else if (j > hi) {
				a[k] = aux[i++];
			} else if (less(aux[j], aux[i])) {
				a[k] = aux[j++];
			} else {
				a[k] = aux[i++];
			}
		}
	}
	
// mergesort a[lo..hi] using auxiliary array aux[lo..hi]
	private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
		if (hi <= lo)
			return;
		int mid = lo + (hi - lo) / 2;
		sort(a, aux, lo, mid);
		sort(a, aux, mid + 1, hi);
		merge(a, aux, lo, mid, hi);
	}

                                                    Copy the code

Bottom up merge sort

Bottom-up is solving the problem step by step.

The idea is to merge those mini-arrays first, then merge the resulting subarrays in pairs until you merge the entire array together.

You can do pwise merge first (think of each element as an array of size 1), then fwise merge (merge two arrays of size 2 into an array of four elements), then fwise merge eight… . In each round of merge, the second subarray of the last merge may be smaller than the first, if not both arrays should be the same size in all merges.

Public static void sort(Comparable[] a) {int N = a.type; Comparable[] aux = new Comparable[N]; for (int len = 1; len < N; len *= 2) { for (int lo = 0; lo < N - len; lo += len + len) { int mid = lo + len - 1; int hi = Math.min(lo + len + len - 1, N - 1); merge(a, aux, lo, mid, hi); }}}Copy the code

To optimize the

  • If the array is small, frequent recursive calls are inefficient, so insertion sort (or selection sort, etc.) can be used to handle small subarrays.

    private static void merge(Comparable[] src, Comparable[] dst, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { if (i > mid) { dst[k] = src[j++]; } else if (j > hi) { dst[k] = src[i++]; } else if (less(src[j], src[i])) { dst[k] = src[j++]; } else { dst[k] = src[i++]; } } } private static void sort(Comparable[] src, Comparable[] dst, int lo, int hi) { // if (hi <= lo) return; if (hi <= lo + CUTOFF) { insertionSort(dst, lo, hi); return; } int mid = lo + (hi - lo) / 2; sort(dst, src, lo, mid); sort(dst, src, mid + 1, hi); // using System.arraycopy() is a bit faster than the above loop if (! less(src[mid + 1], src[mid])) { System.arraycopy(src, lo, dst, lo, hi - lo + 1); return; } merge(src, dst, lo, mid, hi); } // using insertion sort handle small array private static void insertionSort(Comparable[] a, int lo, int hi) { for (int i = lo; i <= hi; i++) { for (int j = i; j > lo && less(a[j], a[j - 1]); j--) { exch(a, j, j - 1); } } } public static void sort(Comparable[] a) { Comparable[] aux = a.clone(); sort(aux, a, 0, a.length - 1); }Copy the code

Quick sort


Quicksort is also called partition swap sort, which is also a divide-and-conquer sorting algorithm.

One potential downside of quicksort is that the program can be extremely inefficient when shard is unbalanced, so you need to sort the array randomly before quicksort to avoid this.

It splits an array into two subarrays, sorting the two parts independently. It differs from merge sort in that:

  • Merge sort divides an array into two subarrays sorted separately, and finally merges the ordered subarrays so that the entire array is sorted.
  • Quicksort sorts an array in such a way that when both subarrays are ordered, the entire array is ordered.
  • In merge sort, recursive calls occur before processing the entire array; In quicksort, the recursive call occurs after processing the entire array.
  • In merge sort, an array is split equally in half, whereas in quicksort, the location of the split depends on the contents of the array.

Operation process

  1. So let’s pick one of these numbersThe benchmark, can be a[lo], which is identified as the scheduled element.
  1. Start at the left end of the array (the left pointer) and scan to the right until you find one greater than or equal toThe benchmarkThe element.
  1. Starting at the right end of the array (the right pointer), scan left until it finds one less than or equal toThe benchmarkThe element.
  1. These two elements are unscheduled, swapping their positions ensures that neither element to the left of the left pointer I is greater thanThe benchmark, the elements to the right of the right pointer j are not less thanThe benchmark).
  1. . When two Pointers meet, theThe benchmarkSwap with the rightmost element of the left subarray (a[j]) and return j.

Code implementation

// partition the subarray a[lo..hi] so that a[lo..j-1] <= a[j] <= a[j+1..hi] // and return the index j. private static int partition(Comparable[] a, int lo, int hi) { int i = lo; // left point int j = hi + 1; // right point Comparable v = a[lo]; // partition element while (true) { // scan left point while (less(a[++i], v)) { if (i == hi) break; } // scan right point while (less(v, a[--j])) { if (j == lo) break; } // check if point cross if (i >= j) break; exch(a, i, j); } // put partition element v to a[j] exch(a, lo, j); // now a[lo..j-1] <= a[j] <= a[j+1..hi] return j; } private static void sort(Comparable[] a, int lo, int hi) { if (hi <= lo) return; int j = partition(a, lo, hi); sort(a, lo, j - 1); sort(a, j + 1, hi); } public static void sort(Comparable[] a) { shuffle(a); sort(a, 0, a.length - 1); } // random sort an array private static void shuffle(Object[] a) { if (a == null) throw new IllegalArgumentException("array is null."); Random random = new Random(); int N = a.length; for (int i = 0; i < N; i++) { int j = i + random.nextInt(N - i); Object temp = a[i]; a[i] = a[j]; a[j] = temp; }}Copy the code

Three – way syncopated quicksort

When there are a large number of repeating elements, the recursion of quicksort causes subarrays of all repeating elements to appear frequently, which has the potential to improve the current performance of quicksort from the linear logarithmic level to the linear level.

A simple idea is to split an array into three parts, corresponding to elements less than, equal to, and greater than the shard element.

In the implementation, maintain a left pointer lt such that elements in A [lo.. l-1] are less than the baseline,a right pointer GT such that elements in a[gt+1..hi] are greater than the baseline,a pointer I such that elements in a[LT..i-1] are equal to the baseline, and elements in a[I.. GT] are undetermined.

  1. a[i]Less thanThe benchmarkThat will bea[lt]anda[i]Exchange, lt++ & i++.
  1. a[i]Is greater thanThe benchmarkThat will bea[gt]anda[i]Exchange, gt -.
  1. a[i]Is equal to theThe benchmark,i++.

All of this keeps the array elements unchanged and shrinks the value of GT-I (so the loop ends). All elements are swapped unless they are equal to the shard element.


                                                        
// quicksort the subarray a[lo .. hi] using 3-way partitioning
	private static void sort(Comparable[] a, int lo, int hi) {
		if (hi <= lo)
			return;
		int lt = lo, i = lo + 1, gt = hi;
		Comparable v = a[lo]; // partition element
		// a[lo..lt-1] < a[lt..gt] < a[gt+1..hi]
		while (i <= gt) {
			int cmp = a[i].compareTo(v);
			if (cmp < 0) {
				exch(a, i++, lt++);
			} else if (cmp > 0) {
				exch(a, i, gt--);
			} else {
				i++;
			}
		}
		sort(a, lo, lt - 1);
		sort(a, gt + 1, hi);
	}

                                                    Copy the code

Heap sort


Heap sort is a sort algorithm based on priority queue of heap.

Priority queue

A priority queue is a data structure that supports deletion of maximum (minimum) elements and insertion of elements. Its interior is ordered, and any priority queue can be turned into a sorting method.

The heap

A heap is a data structure that can often be viewed as an array object of a tree. Those with the root as the largest number are called the largest heap, and those with the root as the smallest are called the smallest heap.

A heap is a nearly complete binary tree structure that also satisfies the heap property: each element is guaranteed to be greater than (less than) equal to the elements of its child nodes.

In a heap, depending on the index position of the root node, the algorithm for calculating the position of the parent node and the child node is different.

  • When the array starts at 0, the parent of the node at position K is(k - 1)/2, its two child nodes are2k+1.2k+2.
  • When the array starts at position 1 (that is, no index 0 is used), the parent of the node at position k isk/2, its two child nodes are2k.2k+1.

To keep the heap in order, two operations need to be supported to break the state of the heap and then iterate over the heap and restore the state as required, a process called ordering the heap.

  • Bottom-up heap ordering (floating) : if the order for a node becomes greater than its parent node and has been broken, you will need to repair by exchanging it and its parent node, the node constantly moving up until met a larger parent node. (if the minimum heap, compare logic instead).

    Private void swim(int k) {while (k > 1 &&less (k/2, k)) {exch(a,k, k/2); private void swim(int k) {while (k > 1 &&less (k/2, k)) {exch(a,k, k/2); k = k/2; }}Copy the code
  • Top to bottom heap ordering (sinking) : If the order for a node to become better than its two child nodes, or one of them smaller and has been broken, need it and through the exchange of the two child nodes supplied by it to repair the heap, the nodes move down until it’s child nodes are smaller than it or arrived at the bottom of the heap. (if the minimum heap, comparative logic thoughts)

    Private void sink(int k) {while (2*k <= n) {int j = 2*k; private void sink(int k) {while (2*k <= n) {int j = 2*k; if (j < n && less(j, j+1)) j++; if (! less(a[k],a[j])) break; exch(a,k, j); k = j; }}Copy the code

Operation process

Heap sorting can be divided into two phases.

  1. The construction phase of the heap, in which the original arrays are reorganized into a heap. Using the sink() function from right to left, the subheap is constructed. Each position in the array is already the root node of a subheap. We only need to scan half of the array because we can skip the subheap of size 1. Finally, the sink() function is called at position 1 to end the scan.
  1. In the sinking sort phase, all elements are fetched from the heap in descending order and the sorting result is obtained. Remove the largest element from the heap and place it in the hollowed out position of the array after the heap has shrunk.

Code implementation

public static void sort(Comparable[] a) { int N = a.length; // construction max heap for (int k = N / 2; k >= 1; k--) { sink(a, k, N); } // sink sort while (N > 1) { // the biggest element (root) swap smallest element then heap shrink exch(a, 1, N--); // new root element sink sink(a, 1, N); } } private static void sink(Comparable[] pq, int k, int n) { while (2 * k <= n) { int j = 2 * k; if (j < n && less(pq, j, j + 1)) j++; if (! less(pq, k, j)) break; exch(pq, k, j); k = j; } } private static boolean less(Comparable[] pq, int i, int j) { return pq[i - 1].compareTo(pq[j - 1]) < 0; } private static void exch(Object[] pq, int i, int j) { Object swap = pq[i - 1]; pq[i - 1] = pq[j - 1]; pq[j - 1] = swap; }Copy the code

conclusion


The name of the Is stable Whether to sort in place Time complexity Spatial complexity note
Bubble sort is is O(N^2) O(1) (Disordered zone, ordered zone). Swap out the largest element from the unordered region and put it in front of the ordered region.
Selection sort no is O(N^2) O(1) (Ordered zone, disordered zone). Find the smallest element in the unordered area to follow the ordered area. Pairs of arrays: compare more, change less.
Insertion sort is is Intervening between N and N^2 O(1) (Ordered zone, disordered zone). Inserts the first element of the unordered region into the appropriate position of the ordered region. Pairs of arrays: compare less, swap more.
Hill sorting no is O(N log^2 N) O(1) Each round of insertion is sorted at a predetermined interval, with the interval shrinking in turn, and the last one must be 1.
Quick sort no is O(N log N) O(logN) (decimal, base element, large number). Select a random element in the interval as a reference, place the elements smaller than the reference before the reference, and place the elements larger than the reference after the reference, and then sort the decimal area and the large number area respectively.
Three-way quicksort no is Between N and NlogN O(logN) High efficiency for input data with a large number of repeating elements.
Merge sort is no O(N log N) O(N) Divide the data into two segments and move the smallest element from the two segments to the end of the new segment.
Heap sort no is O(N log N) O(1) (Maximum heap, ordered region). Unload the roots from the top of the heap before placing them in the ordered zone, and then restore the heap.

In most practical cases, quicksort is the best choice. If stability is important and space is not an issue, merge sort is probably best. But use of quicksort should be seriously considered in any sort application where running time is critical.

In the JDK, arrays.sort () chooses to use different sorting algorithms based on different parameter types. Three-shard quicksort is used for raw data types, and merge sort is used for reference types.

end


  • See GitHub & Gist for the full implementation of this article
  • References for this article are from <> & WikiPedia