This may be the most detailed [Eight Sorting Algorithms] you have ever seen.

Prerequisite knowledge [sorting stability]

Assume that there are multiple records with the same keyword in the record sequence to be sorted. If sorted, the relative order of these records remains unchanged, that is, in the original sequence, , r[i]=r[j]and r[i] is before r[j] , and in the sorted sequence, r[i] is still before r[j], then this sorting algorithm is called stable; otherwise, it is called unstable.

Before explaining :[以下所有排序均以升序排序为例]

1. Direct insertion sort

Direct insertion sort (Insertion Sort) is a simple and intuitive sorting algorithm that works similar to our 整理一组扑克牌的method. The characteristics of this algorithm are that 排第 i 个的时候说明前 i-1 个已经有序了the algorithm inserts the elements to be sorted into the correct position in the sorted sequence one by one, thereby gradually building an ordered sequence.

Implementation ideas:

  1. Starting from the second element (the first element is itself ordered by default), it is compared to the sorted sequence to find the insertion position.
  2. If the element to be inserted is smaller than the previous element, move the previous element one position back (vacate the position). If the element to be inserted is larger than the previous element, the insertion position has been found.
  3. If the element to be inserted is smaller than the entire sorted sequence, it is inserted at the first position.
  4. Repeat steps 1, 2, and 3 until all elements are inserted at the correct location.
public static void insertSort(int[] array) {
    
    
    // 从 1 下标开始,向前插入
    for (int i = 1; i < array.length; i++) {
    
    
        // 记录待插入下标 i 的值
        int tmp = array[i];
        // 将 i 下标对应值分别和 i 下标之前的元素比较
        int j = i - 1;
        for (; j >= 0; j--) {
    
    
            if (tmp < array[j]) {
    
    
                // 升序排序:如果 value(i) 小于之前元素,该元素向后挪动
                array[j + 1] = array[j];
            } else {
    
    
                // 如果 value(i) > 前面的某一个元素值,即找到插入位置,跳出循环
                break;
            }
        }
        // 代码走到这里表示:
        // 1.break跳出循环,即在[0,i]下标之间找到小于value(i)的元素
        // 2.value(i) 在 [0,i]下标之间是最小的
        array[j + 1] = tmp;
    }
}

Analysis of insertion sort characteristics:

  1. Time complexity
    worst case: For the above insertion sort, the worst case time complexity is an arithmetic sequence O ( 1 + 2 + . . . + N ) = O ( N 2 ) O(1+2+. ..+N)=O(N^2)O(1+2+...+N)=O ( N2 )
    Best case: The best case is that the column to be sorted is itself ordered. The insertion sort algorithm only traverses and compares once, and the time complexity isO ( N ) O (N)O ( N )
    so for insertion sort:元素集合越接近有序,直接插入排序算法的时间效率越高
  2. Space complexity
    Since no additional space is used, the space complexity is O ( 1 ) O(1)O(1)
  3. Stability
    The above insertion sort algorithm is stable. But if you tmp < array[j]change to tmp <= array[j], then the sorting is no longer stable. Because it is concluded that if a sorting is stable, it can be realized as unstable; if a sorting itself is unstable, it cannot be realized as a stable sorting.

2. Hill sorting

Hill sorting method is also called shrinking increment method. The basic idea of ​​Hill sorting method is: select an integer increment gap gapg a p , divide all records in the sequence to be sorted into multiple groups, and all distances are incrementalgap gapThe records of g a p are grouped into the same group, and the records in each group are sorted. Then, reduce the increment and repeat the above grouping and sorting work. When the incrementalgap gapWhen g a p is reduced to 1, all records are sorted in the same group.

Hill sorting, when gap > 1 gap > 1gap>1 is all pre-sorted, the purpose is to make the array closer to order. For insertion sort, the elements tend to be ordered, and the time complexity of insertion sort tends to O(N). So Hill sort is an optimization of direct insertion sort.

Implementation ideas:

  1. First, determine the initial increment gap. You can choose half the length of the array as the initial value.
  2. Grouping (code control) and sorting operations (insertion sort) are performed on each increment.
  3. Each time the increment is reduced by half, that is gap = gap / 2, until the gap shrinks to 1, the last grouping and sorting operation is performed.
public static void shellSort(int[] array) {
    
    
    // 确定增量 gap
    int gap = array.length/2;
    while (gap > 1) {
    
    
        shell(array, gap);
        // 每次增量减少
        gap = gap / 2;
    }
    // 增量为1时再排一次
    shell(array, gap);

}

// 这段代码,利用 下标 i、j 和 gap 之间的关系,实现每一组的交替插入排序:
private static void shell(int[] array, int gap) {
    
    
    // 初始时 i = gap ,使下标 i 指向第 1 组的第 2 个元素
    // 便于对每一组执行插入排序
    for (int i = gap; i < array.length; i++) {
    
    
        // 记录待插入下标 i 的值
        int tmp = array[i];
        // 下标 j 指向每组的已排序序列
        int j = i - gap;
        // 寻找每组 value(i) 的插入位置
        for (; j >= 0; j -= gap) {
    
    
            if (array[j] > tmp) {
    
    
                array[j + gap] = array[j];
            } else {
    
    
                break;
            }
        }
        // 代码走到这里表示:找到插入位置
        array[j + gap] = tmp;
    }
}

Analysis of Hill sorting characteristics:

  1. Time complexity
    The time complexity of Hill sorting involves unsolved mathematical problems, so we are temporarily unable to obtain the accurate time complexity of Hill sorting. Here is a range: O ( N 1.3 ) ∼ O ( N 1.5 ) O(N^{1.3}) \sim O(N^{1.5})O ( N1.3)O ( N1.5)
  2. Space complexity
    Since the sorting process uses no additional space, the space complexity is O ( 1 ) O(1)O(1)
  3. Stability
    In Hill sorting, elements are grouped in increments and inserted sorted, which may cause the relative order of equal elements to change after sorting, so Hill sorting is an unstable sorting algorithm.

3. Direct selection and sorting

Direct selection sorting idea: Each time, the smallest (or largest) element is selected from the data elements to be sorted, and stored at the starting position of the column to be sorted, until all the data elements to be sorted are sorted.

Implementation ideas:

  1. Record the starting subscript of the column to be sorted i, traverse the column to be sorted, and find the minimum subscript among the elements to be sorted each timeminIndex
  2. Swap the subscript values, place the minimum value at the beginning of the column to be sorted, and i++reduce the range of the column to be sorted
  3. Repeat steps 1 and 2 until the columns to be sorted are sorted, and the sequence is in order.
// 交换函数:swap()
public static void swap(int[] array, int i, int j) {
    
    
    int tmp = array[i];
    array[i] = array[j];
    array[j] = tmp;
}

public static void selectSort(int[] array) {
    
    
	// 待排序列起始下标 i
    for (int i = 0; i < array.length; i++) {
    
    
    	// 初始化最小值下标
        int minIndex = i;
        // 寻找待排元素中最小值
        for (int j = i + 1; j < array.length; j++) {
    
    
            if (array[j] < array[minIndex]) {
    
    
                //更新最小值下标
                minIndex = j;
            }
        }
        // 此时minIndex放的是最小元素的下标
        swap(array, minIndex, i);
    }
}

Variation of direct selection sorting
The idea of ​​​​the above direct selection sorting is to find the minimum value each time and place it at the starting position of the column to be sorted, thereby achieving sorting. Here is another implementation idea:

  1. leftAt the same time, record the starting subscript and ending subscript of the column to be sorted right, that is, limiting the range of the column to be sorted.[left,right]
  2. Each time the column to be sorted is traversed, the maximum subscript maxIndexand minimum subscript are recorded at the same time.minIndex
  3. Exchange the subscript values, put the maximum value at the end of the column to be sorted, and the minimum position at the beginning of the column to be sorted
  4. Each exchange reduces the range of the columns to be sorted left ++, right --until the range of the columns to be sorted is 0, that isleft == right
public static void selectSort2(int[] array) {
    
    
    // 待排序列起始下标
    int left = 0;
    // 待排序列终止下标
    int right = array.length - 1;
    
    // 遍历待排序列
    while (left < right) {
    
    
        // 初识值,假定最小最大值下标都在最左边
        int maxIndex = left;
        int minIndex = left;
        //每次找到当前范围内的最大值下标:maxIndex;最小值下标:minIndex
        for (int i = left + 1; i <= right; i++) {
    
    
            if (array[i] < array[minIndex]) {
    
    
                minIndex = i;
            }
            if (array[i] > array[maxIndex]) {
    
    
                maxIndex = i;
            }
        }
        // 确定最小值位置
        swap(array, left, minIndex);
        // 如果最大值对应待排序列的起始下标,需要特殊处理,
        // 因为上面的交换会将最大值换到下标 minIndex 处
        if (maxIndex == left) {
    
    
            maxIndex = minIndex;
        }
        // 确定最大值位置
        swap(array, right, maxIndex);
        
        // 缩小待排序列范围
        left++;
        right--;
    }
}

Direct selection sorting feature analysis:

  1. Time complexity
    Whether it is direct selection sorting or a variant of direct selection sorting, as a very "violent" sorting, regardless of whether the column to be sorted is in order or not, the column to be sorted will be traversed every time, and the time complexity is the same. Addition of difference sequences, that is, O ( N 2 ) O(N^2)O ( N2)

  2. Space complexity
    Since no additional space is used, the space complexity is O ( 1 ) O(1)O(1)

  3. Stability
    In selection sort, the smallest (or largest) element in the unsorted sequence is selected each time and then exchanged with the first position of the unsorted sequence. This operation may destroy the relative order between equal elements, causing their relative positions to change after sorting. Therefore direct selection sort is unstable.

4. Heap sort

Heapsort refers to a sorting algorithm designed using a heap, a data structure. Its principle is to use the properties of the heap, that is, the characteristic that the top element of the heap is the maximum value (minimum value) in the heap, to determine the position of an ordered sequence element each time, and gradually build an ordered sequence.

Note: To sort in ascending order, you need to build a large root heap; to sort in descending order, you need to build a small root heap.


Implementation principle:

  1. The build source is sorted into a large top heap.
  2. Swap the top element of the heap with the last element of the to-be-sorted column.
  3. Redetermine the range of the columns to be sorted, and use the downward adjustment algorithm to build the new columns into a large root heap.
  4. Repeat steps 2 and 3 above until the sequence is in order.
public static void heapSort(int[] array) {
    
    
    // 将源序列构建为大根堆
    creatHeap(array);
    // 进行堆排序
    // 从后向前调整待排序列
    int end = array.length - 1;
    while (end > 0) {
    
    
        swap(array, 0, end);
        shiftDown(array, 0, end);
        end--;
    }
}

//这里是建立大根堆->升序排序
private static void creatHeap(int[] array) {
    
    
    for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
    
    
        shiftDown(array, parent, array.length);
    }
}
// 向下调整算法
private static void shiftDown(int[] array, int parent, int len) {
    
    
    int child = 2 * parent + 1;
    //一定有左孩子
    while (child < len) {
    
    
        //有左孩子和右孩子
        if (child + 1 < len && array[child] < array[child + 1]) {
    
    
            child++;
        }
        // 此时child拿到最大值孩子下标
        if (array[child] > array[parent]) {
    
    
            swap(array, child, parent);

            parent = child;
            child = 2 * parent + 1;
        } else {
    
    
            break;
        }
    }
}

Analysis of heap sort characteristics:

  1. Time complexity:
    In the process of building a large root heap (creatHeap), each non-leaf node needs to be adjusted downward (shiftDown). The time complexity of this part is O ( n ) O (n )O ( n ) . During heap sorting, the top element of the heap needs to be exchanged with the last element currently to be sorted, and the top element of the heap needs to be adjusted downward. Repeat this process until all elements are sorted. The time complexity of this part isO (nlogn) O(nlogn)O ( nlogn ) . _ _ _ _ And regardless of the initial state of the input sequence, heap sort requires a complete heap building and element swapping operation. Therefore, the time complexity of the entire heap sort isO ( n + nlogn ) O(n + nlogn)O ( n+nlogn),即 O ( n l o g n ) O(nlogn) O(nlogn)

  2. Space complexity:
    Heap sort is an in-place sorting algorithm that does not require additional auxiliary space. The space complexity is O ( 1 ) O (1)O(1)

  3. Stability
    In heap sort, the operation of exchanging nodes may change the relative order between elements with the same value, so heap sort is an unstable sorting algorithm.

5. Bubble sorting

Bubble sort is a simple sorting algorithm that repeatedly compares two adjacent elements and exchanges their positions in ascending (descending) order until the entire sequence is sorted. The process of bubble sorting is similar to the rising process of bubbles. Larger elements will gradually "float" to the end of the sequence like bubbles, hence the name bubble sorting.

Implementation ideas:

  1. Starting from the first element of the sequence, compare two adjacent elements in sequence.
  2. If the current element is greater than the next element, the positions of the two elements are swapped until a round of comparison is completed.
  3. Repeat step 2 until the entire sequence is in order. Because each round of comparison will move the largest element of this round to the end, the number of elements in each comparison is reduced by 1 (optimization).
public static void bubbleSort(int[] array) {
    
    
    // 外层循环控制比较的轮数
    for (int i = 0; i < array.length - 1; i++) {
    
    
        // 判断是否有序的标志
        boolean flag = true;
        // 内层循环进行相邻元素的比较和交换,每一轮次比较个数少1(优化1)
        for (int j = 0; j < array.length - 1 - i; j++) {
    
    
            // 这里是升序
            if (array[j] > array[j + 1]) {
    
    
                swap(array, j, j + 1);
                // 只要进入比较就说明还不一定有序
                flag = false;
            }
        }
        //如果在一趟比较中一次都不比较说明已经有序,不需要继续遍历(优化2)
        if (flag) {
    
    
            break;
        }
    }
}

Analysis of bubble sorting characteristics:

  1. Time complexity
    After adding two optimizations, in the worst case, the time complexity of the above bubble sort is O ( n − 1 + n − 2 + . . . 1 ) = O ( n 2 ) O(n-1+ n-2+...1)=O(n^2)O ( n1+n2+...1)=O ( n2 ). In the best case, the sequence itself is ordered. In this case, the sequence is traversed only once, and the time complexity isO ( 1 ) O (1)O ( 1 ) .
    Without optimization, the time complexity of both the best case and the worst case isO (n 2) O(n^2)O ( n2).

  2. Space complexity
    Bubble sorting is an in-place sorting algorithm that does not require additional auxiliary space. The space complexity is O ( 1 ) O (1)O(1).

  3. Stability
    Bubble sort is a stable sorting algorithm that maintains the relative order of equal elements.

6. Quick sort

Quick sort is an exchange sorting method with a binary tree structure proposed by Hoare in 1962. Its basic idea is to take any
element Sequence, all elements in the left subsequence are less than the reference value, all
elements in the right subsequence are greater than the reference value, and then the process is repeated in the left and right subsequences until all elements are arranged in the corresponding positions.

Note: Quick sort, each division can determine the ordered position of an element in the sequence.

Implementation ideas:

  1. First, divide the column to be sorted according to the benchmark value, so that the left subsequence is smaller than the benchmark value and the right subsequence is greater than the benchmark value.
  2. Then recursively sort the left subsequence and right subsequence until the subsequence is empty or there is only 1 element left in the subsequence (default ordered).
public static void quickSort(int[] array) {
    
    
       quick(array,0,array.length-1);
}
// 为了接口的统一抽象出一个方法
private static void quick(int[] array,int start,int end) {
    
    
   	// 递归终止条件:
   	// start > end 表示子序列为空;
   	// start == end 表示子序列仅剩1个元素
	if (start >= end) {
    
    
		return;
	}
	// 按照基准值对array数组的 [left, right)区间中的元素进行划分
	// 返回基准值下标,便于递归左右子序列
	int pivotIndex = partition(array,start, end);
	// 划分成功后以div为边界形成了左右两部分 [pivot, div) 和 [pivot+1, right)
	
	// 递归排[left, pivotIndex)
       quick(array,start,pivotIndex-1);
       // 递归排[pivotIndex+1, right)
       quick(array,pivotIndex+1,end);
}

One of the core functions of quick sort is division partition(). According to the way of dividing intervals according to the benchmark value, there are three main implementation methods:

1. Hoare version
Hoare division implementation idea:

  1. First determine the base value . Generally , the leftmost element or the rightmost element pivotof the sequence interval is selected as the base value. [left,right]Select here value(left)as the base value.
  2. Let's left 指针go right until we find a value greater than the pivot value pivot; right 指针let's go left until we find a value less than the pivot value pivot. After finding them respectively, exchange the left and right subscript values ​​so that the value smaller than the benchmark value is at the left subscript, and the value larger than the benchmark value is at the right subscript.
  3. At a certain moment left == right, when both the left and right subscripts point to subscripts smaller than the reference value, the pivot value of the reference value and the corresponding values ​​of the left/right subscript are exchanged to complete the sequence division.

private static int partition(int[] array, int left, int right) {
    
    
	// 记录初始时pivot下标
    int i = left;
    // 记录基准值
    int pivot = array[left];
    while (left < right) {
    
    
        // 注意取等,否则可能会出现左右横跳的死循环![left和right相等时]
        // 注意取左边为基准值就右边 right 先走
        // 1.寻找右边小于piovt的值
        while (left < right && array[right] >= pivot) {
    
    
            right--;
        }
        // 2.寻找左边大于pivot的值
        while (left < right && array[left] <= pivot) {
    
    
            left++;
        }
       	// 交换
        swap(array, left, right);
    }
    // left == right 时,让下标值交换
    swap(array, left, i);
    // 此时 left 和 right 均指向pivot基准值,返回即可
    return left;
}

(1) Why does it take the leftmost value of the sequence as the base value, and the right needs to go first, and the left goes later?

Here we assume that every time right moves first, right moves second. At this time, when left == right, the pointed subscript corresponds to an element larger than the pivot value. If the subscripts are exchanged at this time, it will result in an element larger than the pivot value. The element appears to the left of the pivot. This is a hidden bug that we should avoid:


(2) Why does array[right] >= pivot search for a value less than the base value and array[left] <= pivot search for a value greater than the base line must be equal?

2. Digging method
The idea of ​​implementing the division of digging method:

  1. First save the baseline value pivot, and the pivot corresponding subscript is the first pit position. It is assumed here that in the interval [left,right]is value(left)the base value, then the corresponding subscript of the pit position is left.
  2. rightStart moving forward and find a position larger than pivot. After finding it, put the position value into the pit, and a new pit will be formed at this position.
  3. leftStart moving backward and find a position smaller than pivot. After finding it, put the position value into the pit, and a new pit will be formed at this position.
  4. Repeat steps 2 and 3 until left == right, put the saved pivot value into the current pit position, and the division is completed.

private static int partition2(int[] array, int left, int right) {
    
    
	// 保存基准值
    int pivot = array[left];
    while (left < right) {
    
    
    	//注意条件取等(原因同 hoare)
    	// 1.寻找右边小于piovt的值
        while (left < right && array[right] >= pivot) {
    
    
            right--;
        }
        // 入坑
        array[left] = array[right];
        // 2.寻找左边大于piovt的值
        while (left < right && array[left] <= pivot) {
    
    
            left++;
        }
        // 入坑
        array[right] = array[left];
    }
    // pivot 值入坑
    array[left] = pivot;
    // 返回pivot值对应下标
    return left;
}

3. Front and rear pointers (understand)
implementation ideas of front and rear pointer division:

  1. Set two pointers cur, prev. Initially, prev points to the beginning of the sequence, and cur points to the position after prev.
  2. Determine whether the data pointed by the cur pointer is greater than the base value pivot. If it is greater, cur moves backward until it finds a position that is less than the base value pivot.
  3. Through step 2, at this time, the cur pointer points to data smaller than the pivot value pivot. prev moves backward 1 bit, and determines whether the pointer cur is equal to prev at this time. If it does cur != prev, it means that there is a value greater than pivot between prev and cur, and prev points to a value greater than pivot at this time, and then let value(prev) and value (cur) can be exchanged; if cur == prev, it means that there is no value greater than pivot between prev and cur, that is, prev and cur are adjacent, no exchange is performed, and cur continues to move backward.
  4. Until cur goes out of bounds, exchange the pivot value with value(prev). Complete the division.

The before and after pointer method is very clever. It realizes sequence division by controlling the positions of the cur pointer and the prev pointer:

private static int partition3(int[] array,int left,int right) {
    
    
	// 初始化指针位置
    int prev= left;
    int cur = left+1;
    while(cur<= right) {
    
    
        // 条件限制,保证不相邻时 prev 下标记录的是左边小于基准值的最后一个
        if (array[cur]<array[left] && array[++prev]!=array[cur]) {
    
    
            swap(array,prev,cur);
        }
        cur++;
    }
    // 上面走完了需要把基准值放到 prev 下标下
    swap(array,prev,left);
    // 返回基准值下标
    return prev;
}

Non-recursive implementation of quick sort

  1. The non-recursive nature of quick sort is actually used to simulate recursive operations. So first you need to create a stack.
  2. leftPush the starting position and ending position of the sequence to be sorted rightonto the stack.
  3. Loop out of the stack to determine whether the stack is empty. If not, pop right and left from the stack, and call the partition function to divide the subsequence in the current range to obtain the correct position of the reference element pivotIndex.
  4. If there are still more than two elements in the left range of pivotIndex, the left starting position left and pivotIndex - 1 are pushed onto the stack, and the next cycle will divide them.
  5. If the range on the right of pivotIndex still has more than two elements, pivotIndex + 1 and the end position on the right are pushed onto the stack, and the next cycle will divide them.
  6. After the loop ends, all sequences are divided and sorting is completed.

public static void quickSort2(int[] array) {
    
    
    // 创建栈
    Deque<Integer> stack = new LinkedList<>();
	// 将待排序列起始和终止位置压栈
    int left = 0;
    int right = array.length - 1;
    // 初始时如果left>=right,直接返回
    if (left < right) {
    
    
        stack.push(left);
        stack.push(right);
    } else {
    
    
        return;
    }
    // 循环出栈
    while (!stack.isEmpty()) {
    
    
        right = stack.pop();
        left = stack.pop();
        // 对子序列进行划分
        int pivotIndex = partition(array,left,right);
        // 判断左子序列是否存在两个以上元素
        if (pivotIndex > left + 1) {
    
    
            stack.push(left);
            stack.push(pivotIndex - 1);
        }
        // 判断右子序列是否存在两个以上元素
        if (pivotIndex < right - 1) {
    
    
            stack.push(pivotIndex + 1);
            stack.push(right);
        }
    }
}

Quick sort feature analysis:

  1. Time complexity
    Best case: In the best case, that is, the sequence can be divided evenly every time, and the sequence is divided into two subsequences. The final recursion height is the height of a complete binary tree, which is log 2 (N) log_2( N)log2( N ) , each layer needs to be compared and exchanged n times, so the time complexity isO ( N . log 2 ( N ) ) O(N.log_2(N))O(N.log2( N )) .

    Worst case scenario: When the sequence itself is in ascending or reverse order, each time there is only a right subsequence or a left subsequence, the final recursion height is the height of an n-level single-branch binary tree. , the first layer needs to compare and exchange n times, and the second layer needs to compare and exchange n-1 times..., so the time complexity isO ( N 2 ) O(N^2)O ( N2)

  2. Space complexity
    In the best case (same as above), the height of the recursion is log 2 ( N ) log_2(N)log2( N ) , that is, the space complexity isO ( log 2 ( N ) ) O(log_2(N))O(log2( N )) .
    In the worst case (same as above), the height of the recursion isNNN , that is, the space complexity isO ( N ) O(N)O ( N ) .

  3. Stability
    : During the division process, positions of equal elements may be exchanged, causing the relative order of originally equal elements to change. Therefore quicksort is unstable.

(1) Quick sort optimization 1: Determine the benchmark value by taking the middle of three numbers

(2) Quick sort optimization 2: Use direct insertion sort instead of recursion in the later stage
Through the above analysis, we know that on average, the recursion of quick sort is similar to a complete binary tree, and for a complete binary tree, the number of nodes in the last few layers basically occupies the entire binary tree node. 3/4 3/43/4 , that is to say, the deeper the recursion depth of quick sort, the more times of recursion. Furthermore, for quick sort, when the recursion reaches the later levels, the subsequence sequence has basically become ordered.

Therefore, in order to reduce the number of recursions in the later stage of quick sort, we also use the characteristics of insertion sort: insertion sort is suitable for small-scale or nearly ordered sequences, and has better performance when running on small-scale sequences. Therefore, when the length of the divided subsequence is less than or equal to a threshold, you can choose to directly use insertion sort to sort the subsequences.

After the above 2 2After 2 optimizations, the final quick sort can be implemented as:

	public static void quickSort(int[] array) {
    
    
        quick(array,0,array.length-1);
    }
    // 为了接口的统一抽象出一个方法
    private static void quick(int[] array,int start,int end) {
    
    
    	// 递归终止条件:
    	// start > end 表示子序列为空;
    	// start == end 表示子序列仅剩1个元素
		if (start >= end) {
    
    
			return;
		}
		
		// 优化二:递归到小的子区间时,可以考虑使用直接插入排序
        if (end-start+1<=16) {
    
    
            quickInsert(array,start,end);
            return;
        }

		// 优化一:三数取中法选 pivot ,防止单枝树
        int index = midThree(array,start,end);
        // 每次将 pivot 值换到 start 位置
        swap(array,start,index);
		
		// 按照基准值对array数组的 [left, right)区间中的元素进行划分
		// 返回基准值下标,便于递归左右子序列
		int pivotIndex = partition(array,start, end);
		// 划分成功后以div为边界形成了左右两部分 [pivot, div) 和 [pivot+1, right)
		
		// 递归排[left, pivotIndex)
        quick(array,start,pivotIndex-1);
        // 递归排[pivotIndex+1, right)
        quick(array,pivotIndex+1,end);
    }
    /*优化*/
    // 1.三数取中法选 pivot,防止单枝树
    private static int midThree(int[] array,int left,int right) {
    
    
        int mid = (left+right)/2;

        if (array[left]<array[right]) {
    
    
            if (array[mid]>array[right]) {
    
    
                return right;
            } else if (array[mid]<array[left]) {
    
    
                return left;
            } else {
    
    
                return mid;
            }

        } else {
    
    
            //array[left]>=array[right]的情况
            if (array[mid]>array[left]) {
    
    
                return left;
            } else if (array[mid]<array[right]) {
    
    
                return right;
            } else {
    
    
                return mid;
            }
        }
    }
    // 2. 递归到小的子区间时,可以考虑使用插入排序[利用越有序越快的特点]
    public static void quickInsert(int[] array,int left,int right) {
    
    
        for (int i = left+1; i <=right; i++) {
    
    
            int tmp = array[i];
            //将i下标对应值分别和i下标后元素比较
            int j = i - 1;
            for (; j >= left; j--) {
    
    
                //升序排序:如果小于其后元素,向后挪动
                if (array[j] > tmp) {
    
    
                    array[j + 1] = array[j];
                } else {
    
    
                    break;
                }
            }
            array[j + 1] = tmp;
        }
    }

7. Merge sort

Merge sort (MERGE-SORT) is an effective sorting algorithm based on merge operations. This algorithm is a
very typical application of the divide and conquer method (Divide and Conquer). Merge the ordered subsequences to obtain a completely ordered sequence. The process mainly includes decomposition, merge and sort.

Merge sort recursive implementation

  1. Recursively decompose the sequence to be sorted into a left subsequence [left,mid]and a right subsequence each time [mid+1,right].
  2. When each subsequence is decomposed to only one element, the backtracking process of merge sorting begins. Each merge combines two ordered sequences into one ordered sequence.
public static void mergeSort(int[] array) {
    
    
    // 为了接口的统一性,这里将归并排序抽象出一个方法
    mergeSortFunc(array,0,array.length-1);
}
// 归并排序
private static void mergeSortFunc(int[] array, int start,int end){
    
    
    // 当元素个数小于等于1时,分解停止
    if (start >= end) {
    
    
        return;
    }
    // 递归分解
    int mid = (start+end)/2;
    // 左子序列[left,mid]
    mergeSortFunc(array,start,mid);
    // 右子序列[mid+1,right]
    mergeSortFunc(array,mid+1,end);
    // 合并排序
    merge(array,start,end,mid);
}

// 递归完成后的合并排序过程
private static void merge(int[] array,int left,int right,int mid) {
    
    
    // 定义两个变量分别指向两个子序列的头
    int s1 = left;
    int s2 = mid+1;

    // 定义一个临时数组用来存储“和并排序”的数据
    int[] tmp = new int[right-left+1];
    // 临时数组下标
    int k = 0;
    // 进行“合并排序”的条件是两个子数组均不越界
    while (s1<=mid && s2<=right) {
    
    
        if (array[s1]<array[s2]) {
    
    
            tmp[k++] = array[s1++];
        } else {
    
    
            tmp[k++] = array[s2++];
        }
    }
    // 将还没走完的数组全部排入临时数组中
    while (s1<=mid) {
    
    
        tmp[k++] = array[s1++];
    }
    while (s2<=right) {
    
    
        tmp[k++] = array[s2++];
    }

    // 将排好的数据放入原来的数组中->注意: i+left找到原数组对应下标
    for (int i = 0; i < tmp.length; i++) {
    
    
        array[i+left] = tmp[i];
    }
}

Merge sort non-recursive implementation

  1. Record the ordered number gaps of each group, omit the recursive decomposition process, and start merging and sorting directly from 1 element in each group (when each group only has 1 element, it is ordered by default).
  2. By determining the left, mid, subscripts of each two groups of ordered sequences rightand calling mergethe method, the two groups are merged and sorted.
  3. After each sorting pass, the ordered numbers in each group are multiplied 2.
  4. Repeat steps 2 and 3 until the sequence is in order.
public static void mergeSort2(int[] array) {
    
    
    // gap 表示当前每组多少个有序元素
    int gap = 1;
    
    // 合并过程
    // 因为每组最多为 array.length 个,所以 gap < array.length
    while (gap < array.length) {
    
    
        // 两组两组的合并序列
        // i += gap * 2 表示去合并另外两组有序序列 
        for (int left = 0; left < array.length; left += gap * 2) {
    
    
            int mid = left+gap-1;
            // 有可能会越界,处理越界情况
            if(mid >= array.length) {
    
    
                mid = array.length-1;
            }
            int right = mid+gap;
            // 有可能会越界,处理越界情况
            if(right>= array.length) {
    
    
                right = array.length-1;
            }
            // 进行合并排序
            merge(array,left,right,mid);
        }
        // 当前为每2个一组有序,下次变成4个一组有序
        gap *= 2;
    }
}

What needs to be noted here is that midthe and rightsubscripts may go out of bounds, and out-of-bounds situations need to be handled. For example, the following situation would be out of bounds:

Quick sort feature analysis:

  1. Time complexity
    When decomposing, the sequence needs to be divided into two parts. For a sequence of length n, log 2 ( n ) log_2(n) needs to be performed.log2( n ) times divided. In each merge sort operation, the elements of each decomposition need to be compared and merged, and the time complexity isO ( n ) O (n)O ( n ) . The total time complexity isO ( nlog 2 ( n ) ) O(nlog_2(n))O ( n l o g2(n)).

  2. Space complexity
    Merge sort requires additional space to store the temporary merge results, so the space complexity is O(n).

  3. Stability
    Merge sort maintains the relative order of equal elements and is a stable sort.

Application scenarios of merge sort
Merge sort can handle the sorting problem of massive data through external sorting. External sorting is a technique for sorting large-scale data that cannot be loaded into memory at once.

  1. Divide massive amounts of data into small chunks that can be loaded into memory.
  2. For each small block, since the memory can already be placed, any sorting method can be used. Generate the results into ordered sub-files.
  3. Merge each sub-file into a larger ordered file.
  4. If it still cannot be loaded into memory at once, repeat steps 2 and 3 until all sub-files are merged into a complete ordered file.

8. Counting sort (non-comparison sort)

Counting sorting is a non-comparative sorting algorithm. Its basic idea is to count the occurrence times of each element in the sequence to be sorted, and then place the elements in the correct position based on these statistical information to achieve the purpose of sorting.

The main implementation ideas of counting sorting are as follows:

  1. Find the maximum and minimum values arr​​in the sequence to be sorted , and determine the length of the count array . Count array is used to store the number of occurrences of each element.maxmincountlen = max - min +1
  2. Traverse the sequence to be sorted , count the number of occurrences of each element, and accumulate arrthem at the corresponding position in the count array .arr[i] - min
  3. Reconstruct the sorted sequence based on the statistical information in the count array. The specific method is to traverse the count array and put the corresponding elements into the to-be-sorted column according to the accumulated number of elements.
public static void countSort(int[] array) {
    
    
    //1. 遍历数组 找到最大值 和 最小值
    int min =array[0];
    int max =array[0];
    for (int i = 1; i < array.length; i++) {
    
    
        if (array[i]<min) {
    
    
            min = array[i];
        }
        if (array[i]>max) {
    
    
            max = array[i];
        }
    }
    //2. 根据范围 确定计数数组的长度
    int len = max - min + 1;
    int[] count = new int[len];
    //3.遍历数组,在计数数组当中 统计每个元素出现的次数
    for (int i = 0; i < array.length; i++) {
    
    
    	// 下标值 i - min 表示相对位置
        int index = array[i]-min;
        count[index]++;
    }

    int k=0;
    //4.遍历计数数组,根据元素的累加次数,将对应元素放入待排序列中
    for (int i = 0; i < count.length; i++) {
    
    
        while (count[i]!=0) {
    
    
        	// 下标值 i + min 表示真实的数据
            array[k++] = i+min;
            count[i]--;
        }
    }
}

Analysis of counting sorting characteristics:

  1. Time complexity
    The time complexity of counting sort is O ( n + k ) O(n+k)O ( n+k ) , where n represents the length of the sequence to be sorted, and k represents the length of the counting array, which is the range of the sequence to be sorted.
  2. Space Complexity
    The space complexity of counting sort is O(k), where k represents the length of the counting array, that is, the range of the column to be sorted.
  3. Stability
    Under the current implementation above, there is no stability at all.
  4. Applicable scenarios
    : For counting sorting, when the value range is large, or the data is not concentrated, it takes a lot of space to create a counting array. So for counting sort it works 已知一定范围内相对集中的整数排序.

Summary of sorting complexity and stability

Sorting method Best case time complexity average case time complexity Worst case time complexity space complexity stability
Bubble Sort O(n) O(n^2) O(n^2) O(1) Stablize
insertion sort O(n) O(n^2) O(n^2) O(1) Stablize
selection sort O(n^2) O(n^2) O(n^2) O(1) unstable
Hill sort O(n) O(n^1.3) O(n^2) O(1) unstable
Heap sort O(n * log(n)) O(n * log(n)) O(n * log(n)) O(1) unstable
Quick sort O(n * log(n)) O(n * log(n)) O(n^2) O(log(n)) ~ O(n) unstable
merge sort O(n * log(n)) O(n * log(n)) O(n * log(n)) O(n) Stablize

Guess you like

Origin blog.csdn.net/LEE180501/article/details/131938645