Seven sorting algorithms and counting sort


The following sorting takes small to large sorting as an example

1. Direct insertion sort

Time complexity:
best case: fully ordered 1 2 3 4 5 O(N)
worst case: completely reversed 5 4 3 2 1 O(N^2) (equivalent to arithmetic sequence summation) space complexity:
O(1) stability
: stable
When the given data is more ordered, direct insertion sorting is faster
When there is a set of basically ordered data, it is better to use direct insertion sorting

public static void insertSort(int[] array) {
    
    
        for(int i = 1; i < array.length; i++) {
    
    
            int j = i - 1;
            int tmp = array[i];
            for(; j >= 0; j--) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

2. Hill sort

Hill sorting is an optimization of direct insertion sorting. Jumping grouping may move smaller elements forward as
much as possible. How many increments (how much is the gap) are divided into groups?
Time complexity: N^1.3 ~ N^1.5
Space complexity: O(1)
Stability: unstable

public static void shellSort(int[] array) {
    
    
        int gap = array.length;//增量为多少(gap为多少),则被分为多少组
        while(gap > 1) {
    
    //里面已经包括了排序gap为1的情况
            gap /= 2;
            shell(array, gap);
        }
    }
    private static void shell(int[] array, int gap) {
    
    
        for(int i = gap; i < array.length; i++) {
    
    //i+=gap也行,因为gap最后会变为1
            int j = i - gap;
            int tmp = array[i];
            for(; j >= 0; j -= gap) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + gap] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + gap] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

3. Direct selection sort

Time complexity: Regardless of whether the situation is good or bad, the following two writing methods are O(N^2) (equivalent to the sum of arithmetic sequence) Space
complexity: O(1)
Stability: Unstable

public static void selectSort1(int[] array){
    
    
        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;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                }
            }
            swap(array, i, minIndex);
        }
    }
    private static void swap(int[]array, int i, int j) {
    
    
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    //写法二
    public static void selectSort2(int[] array){
    
    
        int left = 0;
        int right = array.length - 1;
        while(left < right) {
    
    //与写法一相比将写法一最外层的for循环改为了while循环
                int minIndex = left;
                int maxIndex = left;//maxIndex一定是left,因为下面的j下标是从left+1开始从前往后遍历
                for(int i = left + 1; i <= right; i++) {
    
    
                    if(array[i] < array[minIndex]) {
    
    
                        minIndex = i;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                    }
                    if(array[i] > array[maxIndex]) {
    
    
                        maxIndex = i;//在所有的j下标元素中找比array[maxIndex]还要大的元素的下标
                    }
                }
                swap(array, left, minIndex);
                //当最大值原来刚好在最小值的位置(left位置)时,则上一步已经将最小值与left交换,此时最大值在原来最小值的位置,
                if(maxIndex == left) {
    
    //所以要做这一步操作
                    maxIndex = minIndex;
                }
                swap(array, right, maxIndex);
                left--;
                right++;
        }
    }

4. Heap sort

Time complexity: O(N) (creating a large root heap) + O(N logN) (downward adjustment of each node of the heap) or O(N logN)
Space complexity: O(1)
Stability: Unstable
When the amount of data is very large, heap sorting must be faster than Hill sorting, because the time complexity of Hill sorting is N^1.3 ~ N^1.4, and heap sorting is logarithmic and Hill sorting is exponential

public static void heapSort(int[] array){
    
    
        creatBigHeap(array);
        int end = array.length - 1;
        while(end > 0) {
    
    
            swap(array, 0, end);//因为是大堆,堆顶的元素最大,将堆顶元素与队尾元素交换,使队尾元素变成队内最大元素
            shiftDown(array, 0, end);//在这里end=array.length-1,而在shiftDown中end代表数组的总长度,
            end--;                          //相当于去掉队尾元素再进行向下调整
        }
    }
    private static void creatBigHeap(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 end) {
    
    
        int child = 2 * parent + 1;
        while(child < end) {
    
    //因为end传的是array.length,所以条件用<而不用<=
            if(child + 1 < end && array[child] < array[child + 1]) {
    
    
                child++;
            }
            if(array[child] > array[parent]) {
    
    
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            }else {
    
    
                break;
            }
        }
    }

Five, bubble sort

Time complexity: O(N^2) After optimization, the best case (only one comparison) is O(N)
Space complexity: O(1)
Stability: stable

public static void bubbleSort(int[] array){
    
    
        for (int i = 0; i < array.length - 1; i++) {
    
    
            boolean flg = false;
            for (int j = 0; j < array.length - 1 - i; j++) {
    
    
                if(array[j] > array[j + 1]) {
    
    
                    swap(array, j, j + 1);
                    flg = true;
                }
            }
            if(!flg) {
    
    
                return;
            }
        }
    }

Six, quick sort

Quick sort (equivalent to creating a binary tree with the benchmark as the root)
Time complexity:
Best case: O(N*logN) full binary tree/complete binary tree
Worst case: O(N^2) (equivalent to the sum of arithmetic sequence) Single-branch tree
Space complexity:
Best case: O(logN) full binary tree/height of complete binary tree
Worst case: O(N) Single-branch tree creates N nodes
Stability: Unstable
Three methods for benchmarking: Hoare method, Digging method, forward and backward pointer method

6.1 Recursive Quick Sort

The quicksort implemented recursively here has two optimizations. The first optimization is to use the method of taking the middle of three numbers to find the original benchmark, so that the created tree is more like a full binary tree, reducing the height of the tree and reducing the space complexity. The second optimization is to use insertion sorting for each interval when the recursion reaches a certain level, because the intervals are getting smaller and the contents of the intervals are getting more and more orderly. At this time, insertion sorting can be used for this part of the interval to reduce the number of
insert image description here
recursions

public static void quickSort(int[] array){
    
    
        quickSortF(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void quickSortF(int[] array, int left, int right) {
    
    
        if(left >= right) return;//这是递归的结束条件,有=,因为left=right时不用再创建节点了,这个节点已经有序
        //递归到后面的较小区间时用插入法
        if(right - left + 1 <= 7) {
    
    //随着快速排序的进行,整个数据正在趋于有序,当区间越来越小且区间的内容越来越有序时,
            insertSort1(array, left, right);//这部分区间可以用插入排序,这样做可以减少递归的次数
            return;
        }
        //三数取中法,降低了二叉树的高度,降低了空间复杂度,使空间复杂度变为O(logn)
        int midIndex = midOfTree(array, left, right);
        swap(array, midIndex, left);//交换完之后保证left下标是三个数中中间大的数字
        int pivot = partition2(array, left, right);//找到一次基准相当于排好了当前基准这个元素在数组中的顺序,找到基准后,基准左边都是比基准小的,基准右边都是比基准大的
        //先递归左边,递归完左边再递归右边
        quickSortF(array, left, pivot - 1);
        quickSortF(array, pivot + 1, right);
    }
    //插入法
    private static void insertSort1(int[] array, int left, int right) {
    
    
        for(int i = left + 1; i <= right; i++) {
    
    
            int j = i - 1;
            int tmp = array[i];
            for(; j >= left; j--) {
    
    //跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {
    
    //如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
    
    
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }
    //在三数中找到中间大小数的下标
    private static int midOfTree(int[] array, int left, int right) {
    
    
        int mid = (left + right) / 2;
        if(array[left] < array[right]) {
    
    
            if(array[mid] < array[left]) {
    
    
                return left;
            }else if(array[mid] > array[right]) {
    
    
                return right;
            }else {
    
    
                return mid;
            }
        }else {
    
    
            if(array[mid] > array[left]) {
    
    
                return left;
            }else if(array[mid] < array[right]) {
    
    
                return right;
            }else {
    
    
                return mid;
            }
        }
    }
    //三种找基准的方法,在排序的过程中序列可能会不一样,但最终排好序的结果一样,建议优先使用挖坑法,right参数传的都是array.length-1
    //Hoare法找基准
    private static int partition1(int[] array, int left, int right) {
    
    
         int key = array[left];
         int i = left;//将基准的下标保存到i中,因为后面要用left与right相交位置的元素与基准进行交换,这里的交换函数只传下标
        while(left < right) {
    
    
            while (left < right && array[right] >= key) {
    
    //left<right这个条件很重要,因为right不断--,此时再判断array[right]>=key时array[right]有可能会越界
                right--;//先走right,因为一开始基准为left,先走right,left和right相遇的地方才能是比基准小的,才能把比基准小的与基准交换放在基准前
            }
            while (left < right && array[left] <= key) {
    
    //array[left]<=key或上面array[right]>=key的=一定要有,否则可能会进入死循环(如当left和right的值相同时)
                left++;
            }
            swap(array, left, right);
        }
        swap(array, left, i);
        return left;
    }
    //挖坑法找基准
    private static int partition2(int[] array, int left, int right) {
    
    
        int key = array[left];
        while(left < right) {
    
    
            while (left < right && array[right] >= key) {
    
    
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= key) {
    
    
                left++;
            }
            array[right] = array[left];
        }
        array[left] = key;
        return left;
    }
    //前后指针法找基准
    private static int partition3(int[] array, int left, int right) {
    
    
       int prev = left;
       int cur = left + 1;
       while(cur <= right) {
    
    
           if(array[cur] < array[left] && array[++prev] != array[cur]) {
    
    //cur在前面找比基准小的,prev保持在cur找到比基准小的之后要跟prev交换的那个位置,prev是前置++
               swap(array, prev, cur);//代码走到这说明cur和prev拉开了距离且cur<left,prev>=left
           }
           cur++;//array[cur]>=array[left]时cur直接往前走,与prev拉开距离
       }
       swap(array, prev, left);
       return prev;
    }

6.2 Non-recursive Quick Sort

public static void quickSortNor(int[] array) {
    
    
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int piovt = partition2(array, left, right);
        if(piovt - 1 > left) {
    
    
            stack.push(left);//往栈上先放left后放right,则出栈时先出right后出left
            stack.push(piovt - 1);
        }
        if(piovt + 1 < right) {
    
    
            stack.push(piovt + 1);
            stack.push(right);
        }
        while(!stack.isEmpty()) {
    
    //因为这里出栈时先出right后出left,根据出出来的right和left找新的基准,所以相当于先递归二叉树右边,再递归二叉树左边
            right = stack.pop();
            left = stack.pop();
            piovt = partition2(array, left, right);
            if(piovt - 1 > left) {
    
    
                stack.push(left);
                stack.push(piovt - 1);
            }
            if(piovt + 1 < right) {
    
    
                stack.push(piovt + 1);
                stack.push(right);
            }
        }
    }

Seven, merge sort

Time complexity: O(N*logN) Merge sort needs to traverse each layer N times during the merging process, a total of logN layers
Space complexity: O(N) Merge sort needs to apply for an additional array of exactly the same size as the original array when merging at the end. The disadvantage of merge sort is that the space complexity is too large. Stability :
stable
.

insert image description here
insert image description here

7.1 Recursive implementation of merge sort

public static void mergeSort(int[] array) {
    
    
        mergeSort(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void mergeSort(int[] array, int left, int right) {
    
    
        if(left >= right) return;
        int mid = (left + right) / 2;
        //分裂左边
        mergeSort(array, left, mid);
        //分裂右边
        mergeSort(array, mid + 1, right);
        //合并,在合并的过程中进行了排序
        merge(array, left, right, mid);
    }
    private static void merge(int[] array, int left, int right, int mid) {
    
    
        int s1 = left;
        int s2 = mid + 1;
        int[] tmpArr = new int[right - left + 1];//申请一个新的数组,大小为right-left+1
        int k = 0;
        while(s1 <= mid && s2 <= right) {
    
    //满足这个循环条件说明s1~mid和s2~right这两个区间都同时有数据
            if(array[s2] < array[s1]) {
    
    //若条件改为array[s2]<=array[s1]则排序变为不稳定
                tmpArr[k++] = array[s2++];
            }else {
    
    
                tmpArr[k++] = array[s1++];
            }
        }
        while(s1 <= mid) {
    
    
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= right) {
    
    
            tmpArr[k++] = array[s2++];
        }
        for (int i = 0; i < tmpArr.length; i++) {
    
    
            array[i + left] = tmpArr[i];//将tmpArr数组拷回原来数组时,赋给array[i+left],因为原来数组的left有可能不是0
        }
    }

7.2 Non-recursive implementation of merge sort

Before merging, the number of groups keeps decreasing, and each group of elements keeps increasing, one by one as a group (gap is 1), and then two and two as a group (gap is 2), and so on. Before merging, mid and right may cross the boundary, and adjustments should be made at this
insert image description here
time
insert image description here

public static void mergeSortNor(int[] array) {
    
    
        int gap = 1;
        while(gap < array.length) {
    
    
            for (int i = 0; i < array.length; i += 2*gap) {
    
    
                int left = i;
                int mid = left + gap - 1;
                int right = mid + gap;
                if(mid >= array.length) {
    
    //mid有可能会越界,越界时要做出调整
                    mid = array.length - 1;
                }
                if(right >= array.length) {
    
    //right有可能会越界,越界时要做出调整
                    right = array.length - 1;
                }
                merge(array, left, right, mid);
            }
            gap *= 2;
        }
    }

8. Counting and sorting

Time complexity: O(2N+data range) or O(MAX(N, data range))
Space complexity: O(data range)
Stability: stable, but the counting sort implemented by the following code is unstable
Counting sort is suitable for sorting concentrated data in a certain interval

   public static void countSort(int[] array) {
    
    
        int minVal = array[0];
        int maxVal = array[0];
        //求数组中的最大值和最小值
        for (int i = 0; i < array.length; i++) {
    
    
            if(array[i] < minVal) {
    
    
                minVal = array[i];
            }
            if(array[i] > maxVal) {
    
    
                maxVal = array[i];
            }
        }
        int[] count = new int[maxVal - minVal + 1];//依据最大值和最小值来确定计数数组的大小
        //遍历原来的数组进行计数
        for (int i = 0; i < array.length; i++) {
    
    
            count[array[i] - minVal]++;//计数数组的下标相当于要排序数组的元素内容
        }
        //遍历count,把当前元素写回array
        int index = 0;
        for (int i = 0; i < count.length; i++) {
    
    
            while(count[i] != 0) {
    
    
                array[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }

Guess you like

Origin blog.csdn.net/zhanlongsiqu/article/details/131876104