Interview code - sorting algorithm [recommended collection]

Recently, the children at home are preparing for the postgraduate re-examination of computer science. They may pass the common sorting algorithm, so they help to organize a wave, review the relevant knowledge by the way, and test their coding ability;

Regarding the sorting algorithm, there are many posts and codes about the sorting algorithm on the Internet. Some posts even have animations of the sorting process, but there is still a problem-at the implementation level of the code, the description is not clear, or even not. A line of code comments is not easy to understand and remember, so this article sorts out the relevant code and attaches as detailed comments as possible to facilitate understanding and memory;

The elegance of code lies not only in simplicity, but also in readability and maintainability in actual production;

The following are introduced according to the degree of difficulty of the algorithm;

1. Bubble sort

Bubble sort (Bubble Sort) can also be called "exchange sort";

  • Bubble sorting needs to perform N rounds of sorting; the length of the sequence to be sorted in each round is N, N-1...1;
  • After each sorting, the largest element is moved to the end of the sequence through [ exchange ]; then the length of the unsorted sequence in the next round will be reduced by 1;
  • For single-round sorting, elements are traversed from the beginning to the end, the current element and its adjacent elements are compared , and the larger element is moved to the back through the [swap] operation;
  • Selection sorting is very similar to bubble sorting, with one difference, that is, each round of selection sorting only needs to find the position of the maximum value of the "remaining element sequence", and exchange the maximum value to the end of the sequence, that is, selection sorting only "exchanges 1" in each round Second-rate";
  • The traversal of bubble sorting may have multiple exchange operations. Although bubble sorting has many exchanges, if no element exchange occurs in a certain round of sorting, it means that the remaining sequences are already in order and there is no need to continue sorting ;

Time complexity : O(n^2)

package sort.popup;

import java.util.Arrays;
import java.util.List;

/**
 * 冒泡排序(交换排序):每次冒泡,通过相邻元素的依次比较和[交换],就把当前"还未排序"的序列最大值放到尾部,下次未排序的序列长度就减少1个;
 * 区别于选择排序,选择排序每次仅需要找"剩余元素序列"的最值,交换到序列尾部,即只交换1次;
 * 而冒泡排序的一次遍历可能发生多次交换操作,冒泡排序虽然交换次数多了,但是如果某一次交换操作中未发生任何元素交换,则说明已经有序,无需继续排序;
 * 比较次数:n,n-1,...1 ,复杂度 O(n^2)
 */
public class PopupSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 从小到大顺序
    // 第i次冒泡操作:从0往后,依次和相邻元素比较,若当前元素更则大则交换,然后继续往后比较,直到size-i的位置;
    // 通过一次冒泡,当前这次操作就把最大的元素放到了序列尾部;
    // 注意:如果某一次交换操作中未发生任何元素交换,则说明已经有序;
    public static void sort(int[] src) {
        for (int i = 0; i < src.length; i++) {
            boolean sorted = true;
            for (int j = 0; j + 1 < src.length - i; j++) {
                if (src[j] > src[j + 1]) {
                    swap(src, j, j + 1);
                    sorted = false;
                }
            }
            if (sorted) {
                break;
            }
        }
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }

}

GIF :

 2. Selection sort

  • Selection sort is very similar to bubble sort;
  • Selection sorting needs to perform N rounds of sorting; the length of the sequence to be sorted in each round is N, N-1...1;
  • After each sorting, the largest element is moved to the end of the sequence through [exchange]; then the length of the unsorted sequence in the next round will be reduced by 1;
  • For single-round sorting, traverse the elements from beginning to end, record the position of the most valued element in the current sequence to be sorted , perform a [swap] between the position of the most valued element and the last element of the current sequence , and move it to the end of the sequence;
  • Different from bubble sorting, although the number of exchange operations in each round is fixed at 1, the number of times the elements are traversed by selection sorting is fixed; while bubble sorting is already in order when no element exchange occurs in an operation, No need to continue sorting;

Time complexity : O(n^2)

package sort.choose;

import java.util.Arrays;
import java.util.List;

/**
 * 选择排序:每次找"剩余元素序列"的最值,放到序列尾部,即每次遍历只发生一次交换操作(这点区别于冒泡排序);选择排序也是最直观最好理解的排序;
 * 区别于冒泡排序,选择排序遍历元素的次数是固定的;而冒泡排序在某次操作未发生元素交换时,就说明已经有序,无需继续排序;
 * 比较次数:n,n-1,...1 ,复杂度 O(n^2)
 */
public class ChooseSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 从小到大顺序
    // 第i次操作:从0往后遍历"剩余元素序列",直到size-i的位置,找到最大的元素,放在当前序列的尾部;
    public static void sort(int[] src) {
        for (int i = 0; i < src.length; i++) {
            int maxIndex = 0;
            int lastIndex = src.length - i - 1;
            for (int j = 1; j <= lastIndex; j++) {
                if (src[j] > src[maxIndex]) {
                    maxIndex = j;
                }
            }
            swap(src, maxIndex, lastIndex);
        }
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }
}

GIF :

3. Insertion sort

  • Insertion sorting is also relatively easy to understand. Anyone who has played poker should be able to understand it in seconds; just like fighting a landlord to take cards, the cards in the hand are already in order. Every time a new row is taken, insert the appropriate card in the hand according to the size relationship Location;
  • Starting from the first element, gradually construct an ordered sequence, traverse the unsorted elements, and insert this ordered sequence one by one; the length of the ordered sequence is from 1, 2... to N, and the length of the sequence to be sorted is from N-1, N -2... to 0;
  • Insert elements into this ordered sequence one by one: record a current position pos, traverse the ordered element sequence from back to front, if it is larger than the element, update pos to the element position, and then "squeeze" the element back by 1 bit (It can be realized by assignment or exchange);
  • The maximum number of comparisons in each round: 1, 2, ..., n-1;
  • The principle of insertion sorting leads to its natural advantage : when inserting sorting operates on almost sorted data, it hardly needs to be reinserted, which is highly efficient ;

Time complexity : O(n^2)

package sort.insert;

import java.util.Arrays;
import java.util.List;

/**
 * 插入排序:从第1个元素开始,逐渐构建有序序列,未排序的元素插入这个有序序列;
 * 理解:就像打斗地主取牌一样,手牌已经有序,每次取到新排,根据大小关系插入手牌中合适的位置;
 * 比较次数:1,2,...,n-1,n,复杂度 O(n^2)
 * 特点:插入排序在对几乎已经排好序的数据操作时,几乎不需要怎么重新插入,效率高
 */
public class InsertSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 从小到大顺序
    // 从1开始,第i次操作时,前i个元素已经有序,将第i+1个元素从后往前/从前往后依次与已经有序的序列比较,找到插入"正确的位置";直到i+1是最后一个数组元素,完成最后一次操作;
    // 插入"正确的位置":若以满足大小关系则跳出,否则与当前元素交换,临时存放到交换元素的原位置上,继续往前比较
    public static void sort(int[] src) {
        for (int i = 1; i < src.length; i++) {
            int current = src[i];
            int pos = i;
            // 找到插入的pos
            for (int j = i - 1; j >= 0; j--) {
                if (current > src[j]) {
                    break;
                } else {
                    // 后移j元素 更新最终要放入的位置pos 当然这里也可以用swap来做移位,就不需要更新pos了
                    src[j + 1] = src[j];
                    // 考虑到方法在跳出后要把元素放入pos位置 因此需要在跳出前 每次比较后都及时更新当前pos
                    pos = j;
                }
            }
            // 插入到pos位置
            src[pos] = current;
        }
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }

}

GIF :

4. Heap sort

For heap sorting, you can refer to this article of mine: Coding stepping on the pit - MySQL order by&limit order is inconsistent / heap sorting / sorting stability ;

  • Heap sorting utilizes the characteristics of the heap data structure: complete binary tree & the largest/smallest element at the top of the heap, first build a heap for the sorted sequence, and then take out the elements from the top of the heap to complete the sorting;
  • Heap sorting itself does not have an element value comparison process, but completes the comparison in the process of building a heap/heap;
  • The process of heap sorting: traverse the elements to be sorted, put them into the heap one by one, and then take out the elements from the top of the heap one by one. Every time you take them out, you need to re-[heap] to maintain the data structure of the heap;
  • Since the elements at the bottom of the heap are exchanged to the top of the heap after each element is taken out from the top of the heap, the sorting is unstable, that is, the initial relative position of elements with equal values ​​may be changed;

Tree depth log2(N), complexity O(N*log2(N)) ;

package sort.heap;

import java.util.Arrays;
import java.util.List;

/**
 * 堆排序:利用堆数据结构的特点:完全二叉树+对顶元素最大/最小,先构建堆再依次从堆顶取出元素即完成排序
 * 树深度log2(N),复杂度 O(n*log2(n))
 */
public class HeapSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("output:" + Arrays.toString(array));
    }

    public static void sort(int[] array) {
        // 初始化堆,插入全部元素
        BigHeap bigHeap = new BigHeap(array.length);
        for (int i = 0; i < array.length; i++) {
            bigHeap.insert(array[i]);
        }
        // 堆顶元素最大,依次从堆顶取出元素插入数组序列完成排序
        for (int i = array.length - 1; i >= 0; i--) {
            array[i] = bigHeap.remove();
        }
    }

    // 从上到下执行堆化 无需交换元素则退出 父节点下标i,左节点若存在则下标为i*2,右节点若存在则下标为i*2+1
    public static void heapAsc(int[] array, int i, int count) {
        while (true) {
            int maxIndex = i;
            if (i * 2 <= count && array[i * 2] > array[maxIndex]) {
                maxIndex = i * 2;
            }
            if (i * 2 + 1 <= count && array[i * 2 + 1] > array[maxIndex]) {
                maxIndex = i * 2 + 1;
            }
            if (maxIndex == i) {
                break;
            }
            swap(array, maxIndex, i);
            i = maxIndex;
        }
    }

    // 从下到上执行堆化 无需交换元素则退出 子节点下标i,父节点若存在则下标为i/2
    public static void heapDesc(int[] array, int i) {
        while (true) {
            int maxIndex = i;
            if (i / 2 >= 1 && array[i] > array[i / 2]) {
                swap(array, i, i / 2);
                i = i / 2;
            } else {
                break;
            }
        }
    }
}

// 大顶堆 最大的元素在上面
class BigHeap {
    // 元素序列
    int[] array;
    // 当前堆元素数量 由于数组下标0不放元素 因此最大为数组size-1
    int count;

    // 初始化堆大小 最多放elemNum个元素
    public BigHeap(int elemNum) {
        array = new int[elemNum + 1];
        count = 0;
    }

    // 堆顶移除元素 将堆尾部元素换到堆顶 再从上而下的堆化(最小子树有序)
    public int remove() {
        if (count <= 0) {
            throw new RuntimeException("already empty");
        }
        int max = array[1];
        HeapSort.swap(array, 1, count);
        count--;
        HeapSort.heapAsc(array, 1, count);
        return max;
    }

    // 插入新元素到堆尾 从下到上的执行堆化 只需要根父节点比较大小即可
    public void insert(int item) {
        if (count == array.length - 1) {
            throw new RuntimeException("already empty");
        }
        count++;
        array[count] = item;
        HeapSort.heapDesc(array, count);
    }

}

GIF :

 5. Hill sort

  • Shell Sort is a kind of insertion sort, also known as Diminishing Increment Sort;
  • Hill sort is an improvement based on insertion sort:
  1. Make full use of the feature of insertion sorting "when operating on almost sorted data, there is almost no need to re-insert, and the efficiency is high";
  2. Insertion sorting is slightly less efficient than sorting, because each exchange can only move 1 bit, while shell sorting can move the position of the incremental length each time;
  • Hill sorting process: set an incremental step, divide the original sequence into step small segments (the subscripts of the starting elements of each small segment are 0~step-1), and perform insertion sort on each small segment, so that the smaller The value of is moved to the front of the sequence as much as possible;
  • Then gradually reduce the step, generally step=step/2, the number of elements in each small segment is increasing, until N=1, the small segment degenerates into the entire sequence, at this time the sequence is basically relatively orderly, and finally do it again Insertion sort of full sequence;
  • Regarding sorting stability, since Hill sorting requires multiple insertion sorts, we know that one insertion sort is stable and will not change the relative order of the same elements; but in the process of insertion sorting of different segments, the same elements may be distributed in Different small segments move in the insertion sort of their respective segments, some may move to the front in the small segment, and some may not move, resulting in the destruction of the initial relative order of the same elements, and finally its stability will be disrupted, so shell sorting is unstable ;
package sort.shell;

import java.util.Arrays;
import java.util.List;

/**
 * 希尔排序:设置一个增量N,将原序列分为N个小段,对每个小段执行插入排序,这样就把较小的值尽量的移到序列前面;然后逐渐减小N,一般N=N/2,直到N=1,此时序列基本上已经相对有序了,最后再做一次全序列的插入排序;
 * 希尔排序是基于插入排序的改进,充分利用插入排序 "在对几乎已经排好序的数据操作时,几乎不需要怎么重新插入,效率高" 的特点
 *
 * @link https://blog.csdn.net/yuan2019035055/article/details/120246584?ops_request_misc=&request_id=&biz_id=102&utm_term=%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-120246584.nonecase&spm=1018.2226.3001.4449
 */
public class ShellSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 带步长的插入排序: 先写步长为1的插入排序 再把步长1该为step
    public static void insertSort(int[] src, int start, int step) {
        // 插入时 第一个元素作为有序列 从第一个元素的下一个元素开始 往前插入
        for (int i = (start + step); i < src.length; i = i + step) {
            int val = src[i];
            int pos = i;
            for (int j = i - step; j >= 0; j = j - step) {
                if (src[j] < val) {
                    break;
                } else {
                    src[j + step] = src[j];
                    pos = j;
                }
            }
            src[pos] = val;
        }
    }

    // 从小到大顺序
    // 每个小段的元素数量从2开始,则分成了N/2个小组:(0,N/2)、(1,N/2+1)...(N/2-1,N),步长step初始值为N/2
    // 每一轮中,依次对每个小段内执行插入排序,一轮排完后,整体序列相对有序;
    // 下一轮,减小step(一般step=step/2),每组内元素越来越多,直到step=1,则只剩下一组,对整个相对有序的序列执行插入排序
    // 随着轮次迭代,整个序列的相对有序程度越来越大,插入排序成本越来越小;
    public static void sort(int[] src) {
        for (int step = src.length / 2; step >= 1; step = step / 2) {
            for (int start = 0; start < step; start++) {
                insertSort(src, start, step);
            }
            System.out.println("step:" + step + " 当前轮次排序结果:" + Arrays.toString(src));
        }
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }
}

Schematic diagram :

When the increment is 4,

6. Merge sort

  • Merge sort (Merge sort) is an effective sorting algorithm based on merge operations; this algorithm is a very typical application of divide and conquer (Divide and Conquer);
  • The idea of ​​​​merge sorting: Divide the current sequence into two segments (left, mid) and (mid+1, left), and perform sorting on them respectively; after they are sorted separately, perform the operation of "merging two ordered sequences " ;
  • The easiest way to understand is recursive writing; of course, all recursion can be rewritten with loops, the essence is grouping and sorting, and the number of elements in each group ranges from 1 to (length)/2 ;
  • The space complexity of merging is the space occupied by the temporary array and the data pushed onto the stack during recursion: N+logN; so the space complexity is: O(n);
  • The time complexity of merge sort is O(NlogN);
package sort.merge;

import java.util.Arrays;
import java.util.List;

/**
 * 归并排序:将当前序列分为(left,mid)和(mid+1,left)2段,分别对其执行排序;分别有序后,再执行"合并2个有序序列";
 * 最容易理解的实现方式就是递归写法;当然,所有的递归都可以用循环改写,本质是分组排序,步长从1到(length+1)/2;
 * 归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn;所以空间复杂度为: O(n)
 * 归并排序的时间复杂度是O(nlogn)
 * 参考:https://blog.csdn.net/qq_52777887/article/details/123858393
 */
public class MergeSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    // (1)递归实现:先将大序列拆分为2个子序列,对2个子序列继续执行当前排序方法(递归直到子序列长度为2),最后合并2个有序序列
    public static void sort(int[] src, int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        sort(src, left, mid);
        sort(src, mid + 1, right);
        merge(src, left, mid, right);
    }

    // 核心方法:合并2个有序序列
    private static void merge(int[] src, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int t = 0;
        // 左右子序列的当前数组下标
        int l = left;
        int r = mid + 1;

        // 1. 当2个有序数组都没有取完时
        while (l <= mid && r <= right) {
            if (src[l] < src[r]) {
                temp[t] = src[l];
                t++;
                l++;
            } else {
                temp[t] = src[r];
                t++;
                r++;
            }
        }
        // 2. 左序列还没取完 右序列取完了 直接往temp后面加
        while (l <= mid) {
            temp[t] = src[l];
            t++;
            l++;
        }
        // 3. 右序列还没取完 左序列取完了 直接往temp后面加
        while (r <= right) {
            temp[t] = src[r];
            t++;
            r++;
        }

        // 临时数组的结果设置到原序列上
        for (int i = 0; i < temp.length; i++) {
            src[left + i] = temp[i];
        }
    }

    // (2)循环实现:步长从1到N/2,即子序列元素数从2到length
    // 步长为1时,组别划分:[(0),(1)]、[(2),(3)]、...[(N-2),(N-1)],子序列最大长度为1
    // 步长为2时,组别划分:[(0,1),(2)]、[(3,4),(5)]、...[(N-2,N-2),(N-1)],子序列最大长度为2
    // 步长为N时,组别划分:[(0,1,...,N/2),(N/2+1,...N-2,N-1)],子序列最大长度为N/2,合并完即完成全部排序
    public static void sortV2(int[] src) {
        for (int step = 1; step <= src.length; step = step * 2) {
            int left = 0;
            int right = left + step;
            int mid = (left + right) / 2;
            while (left < right && right < src.length) {
                merge(src, left, mid, right);
                // 下一组的起始位置 right + 1
                left = right + 1;
                // 下一组的终点 新left+step
                right = left + step;
                mid = (left + right) / 2;
            }
        }
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array, 0, array.length - 1);
        System.out.println("sorted:" + Arrays.toString(array));

        int[] arrayV2 = initData();
        sortV2(arrayV2);
        System.out.println("sortedV2:" + Arrays.toString(arrayV2));
    }
}

GIF :

7. Quick Sort

  • Quick sort is a typical application of divide and conquer thinking;
  • The idea of ​​​​quick sorting: For the entire sorting sequence, take a value V (such as the leftmost element in the sequence), and then use the "exchange" operation to shift the element to the position pos of the sequence to meet the left side of pos The elements are all less than V, and the elements on the right side of pos are all greater than or equal to V; in this way, after one sorting, the position of the current element in the entire sequence is "determined" ;
  • Subsequently, for the left sequence and the right sequence of the element respectively, continue the above operation until the length of the subsequence is "less than or equal to 1";
  • In the process of sorting, the length of the subsequence will become smaller and smaller, so the number of exchanges will also become smaller and smaller;
  • The time complexity of quick sort is O(nlogn);

Optimization ideas for quick sorting :

(1) When the number of elements is small and the advantage of quick sort is small, other sorts such as insertion sort can be used;

(2) The value of V should be the average value of the current sequence as far as possible, so that the sub-sequences are evenly separated, and the number of moves is relatively small;

The core method of quick sorting is to find a way to divide the position of the sequence . Here are two better-understood methods:

(1) The method of constructing the left sequence (less than the reference value V), the reference value is changed to the start position, and the left sequence is gradually constructed from the start+1 position. It is necessary to traverse the entire sequence and "exchange" the values ​​​​less than V to start+ one by one. 1. start+2, ... position, and finally exchange the position where V is at start with the last element of the left sequence to satisfy left < V <= right, and return the position of V;

(2) The hoare method, respectively set the two pointers p and q to traverse the sequence elements from start+1 and end respectively; for p, if it is less than the reference value v, then p stops or moves backward; similarly for q , if it is not satisfied that it is greater than or equal to the reference value v, then q stops or moves forward; when both p and q stop, exchange the position elements of p and q; repeat this process until p and q meet (the positions are equal), and then do An exchange of the position start where V is located and the last element of the left sequence (this element position may be p or p-1), and finally return p or q (p=q);

package sort.fast;

import java.util.Arrays;
import java.util.List;

/**
 * 快速排序:是一种分治思想的典型应用;对于整个排序序列,取一个值V(如序列中的最左元素),然后通过"交换"操作移位,将该元素放到序列的位置pos,
 * 以满足——pos左侧元素都小于V,pos右侧元素都大于等于V;
 * 这样,经过一次排序,当前元素在整个序列的位置就"确定好了";
 * 随后,分别对于左侧序列和右侧序列,继续上述操作,直到子序列长度"小于等于1";
 * 在排序的过程中,整个序列会逐渐相对有序,因此交换次数也会越来越小;
 * 快速排序的时间复杂度是O(nlogn)
 * 对快速排序的优化思路:
 * (1)当元素数量较小,快速排序优势小,可以使用其他的排序如插入排序;
 * (2)V的取值尽量为当前序列的平均值,这样子序列就被均匀分开,移动次数也相对较小,简化计算则可以使用左/中/右三数取中位数的方式;
 * refer:https://blog.csdn.net/LiangXiay/article/details/121421920
 */
public class FastSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    public static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 从小到大顺序
    // (1)找到pos(2)对左右序列分别做上述操作
    public static void quickSort(int[] src, int start, int end) {
        // 退出条件
        if (start >= end) {
            return;
        }
        // 找到V
        int pos = partitionV2(src, start, end);
        quickSort(src, start, pos - 1);
        quickSort(src, pos + 1, end);
    }

    // 核心方法:
    // 1.找合适的V,为尽量打散序列顺序,且为了好挪元素,尽可能取左中右三个值中平均的那个值作为基准值V,然后跟start位置元素做交换
    // 关于找V的位置并调整序列元素顺序以满足——pos左侧元素都小于V,pos右侧元素都大于V,有几种方法,这里使用最好理解的 "仅遍历一次序列的方法"
    // 算法描述:pos初始化为0,假设从1开始遍历序列,找到小于V的元素位置时,则换到(pos+1)位置,然后往前移动pos(即pos++);
    // 当遍历结束时,1到pos位置的元素都小于V,再将位置0与pos位置元素互换,则整个序列满足pos位置之前的元素都小于V(src[pos]),之后的元素都不小于V;
    // 正是这一次的交换操作,导致"快速排序是不稳定的",类似堆排序中的堆尾元素换到堆顶;
    private static int partition(int[] src, int start, int end) {
        // 1. 把相对平均的值换到序列起始位置 作为基准值
        int mid = (start + end) / 2;
        if (src[start] < src[end]) {
            if (src[mid] > src[end]) {
                swap(src, start, end);
            } else {
                swap(src, start, mid);
            }
        } else {
            if (src[mid] > src[end]) {
                swap(src, start, mid);
            } else {
                swap(src, start, end);
            }
        }
        int baseValue = src[start];
        int pos = start;

        // 2. 从start+1位置遍历序列
        for (int i = start + 1; i <= end; i++) {
            // 小于基准值 则挪动pos 把i位置元素换到前面的pos位置去
            if (src[i] < baseValue) {
                pos++;
                swap(src, i, pos);
            }
        }

        // 3. 此时 start+1到pos位置的元素都小于V 最后将start位置的基准值V元素与pos位置元素交换 以满足基准值V在序列中的位置把序列按大小关系划分为2个子序列:左 < V <= 右
        swap(src, start, pos);
        return pos;
    }

    // 另一种比较好理解的找V的位置方法的实现——hoare法
    private static int partitionV2(int[] src, int start, int end) {
        int baseVal = src[start];
        int left = start + 1;
        int right = end;
        while (left < right) {
            // 从左到右遍历序列 满足大小关系则往右移动left
            while (left < right && src[left] < baseVal) {
                left++;
            }
            // 从右到左遍历序列 满足大小关系则往左移动right
            while (left < right && src[right] >= baseVal) {
                right--;
            }
            // 此时left位置元素大于等于baseVal right位置元素小于baseVal 则交换这两个位置的元素后重新满足了大小关系; 然后继续循环 直到2个指针相遇
            if (left < right) {
                swap(src, left, right);
            }
        }
        // 此时left与right相遇(left=right) 但注意此时left位置元素与val大小关系未知!
        // 1.因为可能执行left++后,left位置元素大于等于于val,但刚好左右指针相遇,不再移动right;即此时left位置元素大于等于val
        // 2.但是还有可能执行left++后,left位置元素小于val,然后再执行right的移动时,right++后,左右指针相遇,最终跳出;即此时left位置元素小于val
        // 所以这里需要根据相遇位置元素val的大小关系来判断如何做交换:若src[left]<=val则交换,否则left-1必然小于val,直接与left-1位置交换;
        if (src[left] <= baseVal) {
            swap(src, start, left);
        } else {
            swap(src, start, left - 1);
        }
        return right;
    }

    public static void main(String[] args) {
        int[] array = initData();
        quickSort(array, 0, array.length - 1);
        System.out.println("quickSorted:" + Arrays.toString(array));
    }
}

GIF :

8. Counting sort

  • The core idea of ​​counting sort is to exchange space for time;
  • In fact, counting sorting does not have a comparison process of sequence elements at all, but stores sorting sequence elements according to the counting array. The array subscript is the value of the sorting element, and the value of the array subscript is "the number of elements whose value is equal to the array subscript". Finally, only need Traverse the count array in order to output the sorted sequence;

Counting sort steps:

(1) Build a count array

Traversing the sorting sequence, the current element value is recorded as X, and then find the X position of the counting array, for arr[x]++; thus, the counting array, as the name suggests, records the number of occurrences of each sorting element in the sorting sequence; the sorting element The value is the subscript of the counting array (the subscript of the counting array naturally satisfies the order of natural numbers!);

(2) traverse the count array

After the counting array is initialized, you only need to traverse the array, and take out the array subscript values ​​whose array elements are greater than 1 in turn, and then you can output the "ordered" element sequence;

package sort.count;

import java.util.Arrays;
import java.util.List;

/**
 * 计数排序的核心思想是空间换时间,实际上根本没有序列元素的比较过程,而是按照计数数组存放排序序列元素,数组下标为排序元素值,数组下标的值为"值等于数组下标的元素的个数",最后只需要按序遍历计数数组输出排序后的序列即可;
 * 计数排序步骤:遍历排序序列,当前元素值记为X,然后找到计数数组的X位置,对于arr[x]++;
 * 如此,计数数组顾名思义,记录了每个排序元素的在排序序列中的出现次数;排序元素值为计数数组的下标(计数数组下标天然的满足自然数有序!);
 * 初始化好计数数组后,只需要遍历该数组,则可以输出"排好序"的元素序列;
 * refer:https://blog.csdn.net/justidle/article/details/104203972
 */
public class CountSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(8, 3, 9, 7, 7, 1, 9, 9, 5);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    // 从小到大顺序
    // (1)先初始化计数数组,数组长度为(最大值-最小值+1),对于正整数序列,则仅找最大值即可(就不去找最小值了,可能会多申请一些数组空间),最小值按0处理(如果排序序列存在负数,则需要一个相对值将其改写为正数数序列)
    // (2)遍历计数数组,按数组下标顺序输出存储的序列元素,对计数为0的跳过,对计数>1的每输出一次则计数减少1,直到遍历完整个计数数组
    public static void sort(int[] src) {
        int gap = getMax(src) - 0;
        int[] arr = new int[gap + 1];
        // 初始化数计数数组
        for (int i = 0; i < src.length; i++) {
            int item = src[i];
            arr[item]++;
        }
        // 遍历计数数组 输出有序的元素序列
        int index = 0;
        for (int i = 0; i < arr.length; i++) {
            while (arr[i] > 0) {
                src[index++] = i;
                arr[i]--;
            }
        }
    }

    private static int getMax(int[] src) {
        int max = src[0];
        for (int i = 1; i < src.length; i++) {
            if (src[i] > max) {
                max = src[i];
            }
        }
        return max;
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }

}

GIF :

9. Radix sort

  • Cardinality sorting is a special sorting method. It is not sorting based on comparison, but "distribution" and "collection" based on the size of each digit of the sorted element value. It is essentially an implementation of bucket sorting (grouping sorting);
  • The idea of ​​radix sorting is to divide the sorting sequence elements into multiple groups according to the element characteristics (divided into multiple digits under the base k), and perform "collection" similar to counting and sorting for each group (the length of the collection array is k, The array subscript is the eigenvalue of the current element at this digit), and finally traverse the collection array and output the ordered sequence under the digit;
  • When sorting the next digit, the output of the previous digit is used as input; when all digits are sorted in sequence, the original sorting sequence is sorted;

For example: with 10 as the base number, each of the tens of millions of digits is used as a feature, and divided into multiple groups (the number of groups is the highest digit, such as decimal 1024, it is divided into four groups of tens of thousands), for each groups, respectively "assign" the sorting elements to the collection array with a length of 10, and finally traverse the collection array to output the ordered sequence under the current digit; cardinality sorting can be divided into: highest digit first (MSD) and lowest digit
first (LSD);

package sort.radix;

import java.util.Arrays;
import java.util.List;

/**
 * 基数排序:基数排序是一种特殊的排序方式,不是基于比较进行排序,而是基于关键字的各个位置大小进行"分配"和"收集"两种操作对关键字序列进行排序的一种排序方式;
 * 基数排序本质还是属于桶排序的一种,思路就是将排序序列元素按照元素特征,拆成k个组,分别对每个组计数排序,然后再输出当前组的有序序列;
 * 下一组的排序序列则以上一组输出的有序序列为输入,当按序完成所有组的排序时,原始排序序列就完成了排序;
 * 例如:以10为基数,分别对个十百千万位作为特征,分成多个组,分别对每个组做计数排序;
 * 基数排序可以分为:最高位优先(MSD)和最低位优先(LSD);
 * refer:https://blog.csdn.net/qq_51950769/article/details/122734206
 */
public class RadixSort {

    public static int[] initData() {
        List<Integer> input = Arrays.asList(18, 3, 39, 7, 27, 211, 69, 59, 985);
        int[] array = new int[input.size()];
        for (int i = 0; i < input.size(); i++) {
            array[i] = input.get(i);
        }
        System.out.println("input:" + Arrays.toString(array));
        return array;
    }

    // 10为基数
    public static void sort(int[] src) {
        // 基数为10
        int base = 10;
        // 获取序列元素中最长的位数
        int numberLength = getMaxNumberLength(base, src);

        // 递归方式 按照位数先入桶,再输出该位下的序列,再入桶,以此类推
        buildBucketsAndOutPutArray(src, numberLength, base);
    }

    // 核心方法
    private static void buildBucketsAndOutPutArray(int[] src, int numberLength, int base) {
        // 从个位开始 各位记为0 十位记为1 以此类推
        for (int j = 0; j <= numberLength; j++) {
            // 临时数组 当然也可以使用数组链表,数组为bucket的位数桶位,List存桶位内的元素序列
            int[][] buckets = new int[base][src.length];
            // 存放当前数位的数值bucket下 元素个数 初始化都是0
            int[] itemNumInBucket = new int[base];
            // 1. 先构建buckets
            for (int i = 0; i < src.length; i++) {
                // 计算当前位数下 位数上的数值 公式:先对上一位取余数,则当前位变成了最高位,然后再对当前位取商得到当前数位的值
                // 如123的十位的值为2: 123 % 10^(2)=23, 23 / 10^(2-1) = 2
                int radix = src[i] % ((int) Math.pow(base, j + 1)) / ((int) Math.pow(base, j));
                // 存入数组
                buckets[radix][itemNumInBucket[radix]] = src[i];
                // 数量+1
                itemNumInBucket[radix]++;
            }
            // 2. 再遍历buckets 输出存放的元素到src
            int srcIndex = 0;
            for (int i = 0; i < base; i++) {
                int itemIndex = 0;
                while (itemIndex < itemNumInBucket[i]) {
                    // 对于一个bucket存放的序列 因为小的值放前面 所以输出时也要注意顺序 从前往后(类似队列先入先出)正序输出存放的元素到src
                    src[srcIndex] = buckets[i][itemIndex];
                    itemIndex++;
                    srcIndex++;
                }
            }
        }
    }

    // 先找到最大值 再算位数
    private static int getMaxNumberLength(int base, int[] src) {
        int max = 0;
        for (int item : src) {
            max = Math.max(max, item);
        }
        // 个位记为0
        int numberLength = 0;
        while (max / base > 0) {
            max = max / base;
            numberLength++;
        }
        return numberLength;
    }

    public static void main(String[] args) {
        int[] array = initData();
        sort(array);
        System.out.println("sorted:" + Arrays.toString(array));
    }
}

GIF :

summary

Glossary

n: data size

k: the number of "buckets"

In-place: occupies constant memory, does not occupy additional memory

Out-place: takes up extra memory

Stability: the order of 2 equal key values ​​after sorting is the same as their order before sorting

Regarding time complexity :

  • Square order (O(n2)) sorting Various simple sorting: direct insertion, direct selection and bubble sorting;
  • Linear logarithmic order (O(nlog2n)) sorting quick sort, heap sort and merge sort;
  • O(n1+§)) sorting, where § is a constant between 0 and 1; Hill sorting;
  • Linear order (O(n)) sorting radix sorting, in addition to bucket and box sorting;

Regarding stability :

  • Stable sorting algorithms: bubble sort, insertion sort, merge sort, and radix sort.
  • Not a stable sorting algorithm: selection sorting, quick sorting, Hill sorting, heap sorting;

reference:

Commonly used top ten sorting algorithms

A detailed explanation of the ten classic sorting algorithms

Guess you like

Origin blog.csdn.net/minghao0508/article/details/130017485