Selection sort

core idea

Select the smallest element and swap places with the first element; Select the smallest of the remaining elements and swap places with the foremost of the current remaining elements.

Analysis of the

The number of comparisons for the selection sort is independent of the initial sort of the sequence, and is N(n-1)/2.

The number of moves is n minus 1 at most.

Thus, the time complexity is O(N^2), whether the inputs are ordered or not, and the order of the inputs only determines the number of swaps, but the number of comparisons remains the same.

Selection sort is unstable, like 5, 6, 5, 3.

code

public class SelectionSort {
    public void selectionSort(int[] nums){
        if(nums==null)
            return;
        for(int i=0; i<nums.length; i++) {int index = i;
            for (int j = i; j < nums.length; j++) {
                if(nums[j] < nums[index]) { index = j; } } swap(nums, i, index); }}}Copy the code

Bubble sort:

core idea

Swapping adjacent elements in reverse order from left to right brings the largest element to the far right. Repeat this process over and over until there is no exchange in one loop, which means it has been ordered and exits.

Analysis of the

  • When the original sequence is ordered, the number of comparisons is n-1 and the number of moves is 0, so the time complexity is O(n) in the best case.
  • When sorting in reverse order, the number of comparisons is N(n-1)/2 and the number of moves is 3N(n-1)/2, so the worst-case time complexity is O(N^2).
  • The average time complexity is O(N^2).

When two elements are exchanged, the order of the same elements does not change, so it is stable.

code

public class BubbleSort {
    public void bubbleSort(int[] nums){
        for(int i=nums.length-1; i>0; i--){boolean sorted=false;
            for(int j=0; j<i; j++){if(nums[j]>nums[j+1]){
                    Sort.swap(nums,j,j+1);
                    sorted=true; }}if(! sorted)break; }}Copy the code

Insertion sort

core idea

Each time the current element is inserted into the sorted left array, the left array remains in order after insertion.

Analysis of the

Since insertion sort can only swap adjacent elements each time, reducing the number of inversions by 1, the number of swaps is equal to the number of inversions.

Therefore, the complexity of insertion sort depends on the initial order of the array.

  • The array is already ordered, N minus 1 comparisons and 0 swaps, O(N) time.
  • The array is completely reversed, N(n-1)/2 comparisons and N(n-1)/2 swaps, O(N^2)
  • On average, time is O(N^2).

Insertion sort is stable

code

public class InsertionSort {
    public void insertionSort(int[] nums){
        for(int i=1; i<nums.length; i++){for(intj=i; j>0; j--){if(nums[j]<nums[j-1])
                    swap(nums,j,j-1);
                else
                    break;// It has been placed in the correct position}}}}Copy the code

Hill sorting

For large arrays, insertion sort is slow because it can only swap adjacent elements, reducing the number of inversions by one at a time.

core idea

Hill sort To address the limitations of insertion sort, the number of inversions is reduced by more than one at a time by swapping non-adjacent elements. Hill sort uses insertion sort to sort the sequence spaced H, decreasing H until H=1, eventually making the entire array orderly.

Time complexity

The time complexity of Hill sort is difficult to determine, and the choice of H will also change its time complexity.

The time complexity of Hill sort is less than O(N^2), and the advanced sorting algorithm is only about twice as fast as Hill sort.

The stability of

Hill sort is not stable.

code

public class ShellSort {
    public void shellSort(int[] nums){
        int N=nums.length;
        int h=1;

        while(h<N/3){
            h=3*h+1;
        }

        while(h>=1) {for(inti=h; i<N; i++){for(intj=i; j>0; j--){if(nums[j]<nums[j-1]){
                        swap(nums,j,j-1);
                    }else{
                        break;// It has been placed in the correct position
                    }
                }
            }
        }
    }
}
Copy the code

Merge sort

core idea

Divide the array into two parts, sort them separately, and merge them.

Merge method

public void merge(int[] nums, int left, int mid, int right) {
        int p1 = left, p2 = mid + 1;
        int[] tmp = new int[right-left+1];
        int cur=0;
        
        // Two Pointers to the left and right subarrays, the smaller one into the auxiliary array
        while(p1<=mid&&p2<=right){
            if(nums[p1]<nums[p2]){
                tmp[cur++]=nums[p1++];
            }else{ tmp[cur++]=nums[p2++]; }}// Add the remaining array to the auxiliary array
        while(p1<=mid){
            tmp[cur++]=nums[p1++];
        }
        while(p2<=right){
            tmp[cur++]=nums[p2++];
        }

        / / copy
        for(int i=0; i<tmp.length; i++){ nums[left+i]=tmp[i]; }}Copy the code

Code implementation

Recursive method: top down

A large array is divided into two smaller arrays by recursive calls from the top down.

public void up2DownMergeSort(int[] nums, int left, int right) {
        if(left==right)
            return;
        int mid=left+(right-left)/2;
        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);
        merge(nums,left,mid,right);
    }
Copy the code

Non-recursive: bottom up

public void down2UpMergeSort(int[] nums) {
        int N = nums.length;
       
        for (int sz = 1; sz < N; sz += sz) {
            for(int lo = 0; lo < N - sz; lo += sz + sz) { merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1)); }}}Copy the code

Analysis of the

A problem of size N is decomposed into two subproblems of size N/2 respectively, and the combined time complexity is O(N). T (N) = 2 T (N / 2) + O (N).

The time complexity is O(NlogN) and the time complexity is the same in the worst, best and average cases.

Merge sort requires order N space.

Merge sort is stable.

Quick sort

core idea

Quicksort Uses a shard element pivot to divide the array into two subarrays, with the left subarray less than or equal to the shard element and the right subarray greater than or equal to the shard element, sorting the subarrays separately, and finally sorting the whole array.

partition

Take a[L] as the shard element and scan right from the left end of the array until it finds the first element greater than or equal to it. Then scan left from the right end of the array to find the first element less than it. Swap the two elements. This process ensures that no element to the left of the left pointer I is larger than the shard element, and no element to the right of the right pointer J is smaller than the shard element. When two Pointers meet, the shard elements A [L] and a[j] swap places.

private int partition(int[] nums, int left, int right) {
        int p1=left,p2=right;
        int pivot=nums[left];
        while(p1<p2){
            while(nums[p1++]<pivot&&p1<=right);
            while(nums[p2--]>pivot&&p2>=left);
            swap(nums,p1,p2);
        }
        swap(nums,left,p2);
        return p2;
    }
Copy the code

Code implementation

public void sort(T[] nums, int l, int h) {
        if (h <= l)
            return;
        int j = partition(nums, l, h);
        sort(nums, l, j - 1);
        sort(nums, j + 1, h);
    }
Copy the code

Analysis of the

In the best case, the array is split exactly in half each time, with a minimum of recursive calls and O(NlogN) complexity.

In the worst case, it’s an ordered array, one element at a time, O(N^2). To prevent this, the array needs to be randomly shuffled when performing quicksort.

It’s not stable.

To improve the

  1. Switch to Insert sort: recursive subarray size is small, use insert sort.
  2. Take the middle of three numbers: in the best case, take the median every time as the segmentation element, the cost of calculating the median is relatively high, so take three elements and take the median as the segmentation element.

Three way fast row

For the array with a large number of repeating elements, the array is divided into less than, equal to and greater than three parts. For the random number group with a large number of repeating elements, sorting can be completed in linear time.

public void threeWayQuickSort(int[] nums,int left,int right){
        if(right<=left)
            return;

        int lt=left,cur=left+1,gt=right;
        int pivot=nums[left];
        while(cur<=gt){
            if(nums[cur]<pivot){
                swap(nums,lt++,cur++);
            }else if(nums[cur]>pivot){
                swap(nums,cur,gt--);
            }else{
                cur++;
            }
        }
        threeWayQuickSort(nums,left,lt-1);
        threeWayQuickSort(nums,gt+1,right);
    }
Copy the code

Partition based fast lookup

Partition () is used to find the KTH element of the array in linear time complexity.

Assuming the array can be dichotomized each time, the total number of comparisons is (N+N/2+N/4+..). All the way to the KTH element, which is obviously less than 2N.

public int select(int[] nums, int k) {
    int l = 0, h = nums.length - 1;
    while (h > l) {
        int j = partition(nums, l, h);

        if (j == k) {
            return nums[k];

        } else if (j > k) {
            h = j - 1;

        } else {
            l = j + 1; }}return nums[k];
}
Copy the code

Heap sort

The heap

A heap can be represented as an array because a heap is a complete binary tree, and a complete binary tree can easily be stored in an array. The parent of the node at position K is at k/2, while its two children are at 2k and 2K +1, respectively. In this case, the position from the index of 1 is used to give a clearer description of the position of the nodes.

Up and down

When a node is larger than its parent, swapping the two nodes until the node is put in place is called floating up.

private void shiftUp(int k) {
        while (k > 1 && heap[k / 2] < heap[k]) {
            swap(k / 2, k);
            k = k / 2; }}Copy the code

When a node is smaller than its children, comparisons and swaps are made continuously downward, and when a base point has two children, swaps are made with the largest node. This operation is called sinking.

private void shiftDown(int k){
        while(2*k<=size){
            int j=2*k;
            if(j<size&&heap[j]<heap[j+1])
                j++;
            if(heap[k]<heap[j])
                break; swap(k,j); k=j; }}Copy the code

Heap sort

Swapping the largest element with the last element of the array in the current heap, without deleting it, results in a decrementing sequence from end to end.

Building a heap The most straightforward way to build a heap is to float up a group of numbers from left to right. A more efficient method is to sink from right to left. The leaf nodes do not need to be sunk and can be ignored, so only half of the elements need to be traversed.

Swap the top of the heap and the worst element to sink and maintain the nature of the heap.

public class HeapSort {
    public void sort(int[] nums){
        int N=nums.length-1;
        for(int k=N/2; k>=1; k--){ shiftDown(nums,k,N); }while(N>1){
            swap(nums,1,N--);
            shiftDown(nums,1,N);
        }
        System.out.println(Arrays.toString(nums));
    }

    private void shiftDown(int[] heap,int k,int N){
        while(2*k<=N){
            int j=2*k;
            if(j<N&&heap[j]<heap[j+1])
                j++;
            if(heap[k]>=heap[j])
                break; swap(heap,k,j); k=j; }}private void swap(int[] nums,int i,int j){
        intt=nums[i]; nums[i]=nums[j]; nums[j]=t; }}Copy the code

Analysis of the

The time to build the heap is order N.

The height of a heap is logN, so the complexity of inserting and removing the largest element in the heap is logN.

In heap sort, N nodes are sunk and the complexity is O(N logn).

Heapsort is rarely used by modern operating systems because it cannot be cached using the principle of locality, where array elements are rarely compared and swapped with neighboring elements.

To compare

Sorting algorithm Best time complexity Average time complexity Worst time complexity Spatial complexity The stability of Applicable scenario
Bubble sort O(N) O(N^2) O(N^2) O(1) stable
Selection sort O(N) O(N^2) O(N^2) O(1) unstable The running time is independent of the input, and is applicable when the number of data moves is minimum and the amount of data is small.
Insertion sort O(N) O(N^2) O(N^2) O(1) stable The data is small and most of it has been sorted
Hill sorting O(N) O (N ^ 1.3) O(N^2) O(1) unstable
Quick sort O(NlogN) O(NlogN) O(N^2) O(logN)-O(N) unstable Fastest universal sorting algorithm, best choice for most cases
Merge sort O(NlogN) O(NlogN) O(NlogN) O(N) stable You need stability. Space is not very important
Heap sort O(NlogN) O(NlogN) O(NlogN) O(1) O(1) unstable
  • When the size is small, such as less than or equal to 50, insert or select sort is used.
  • When the elements are basically in order, choose insert, bubble, or random quicksort.
  • When the scale is large, O(NlogN) sorting algorithm is used.
  • When the keywords to be sorted are randomly distributed, the average quicksort time is the shortest.
  • When stability is needed, merge sort is used.

Noncomparative sort

The previous algorithms are all comparison-based sorting algorithms. Here are two non-comparison-based sorting algorithms.

Count sorting

Given the data range x1 through x2, sort the elements in the range. You can use an array of length X2 -x1+1 to store the number of occurrences of each number. And then we get the sorted result.

Bucket sort

Bucket sorting assumes that a set of numbers to be sorted are evenly and independently distributed in a range, and this range is divided into several buckets. Then, the keyword k to be sorted is mapped to the ith bucket based on some mapping function. Then the data in each bucket is combined in an orderly manner, and the elements in each bucket can be sorted, and then an ordered sequence is output.