【JAVA】常见的七大排序

news2024/9/24 11:50:21

前言:本篇主要介绍常见的七大排序,实现语言为Java,其主要分为:直接插入排序,希尔排序,直接选择排序,堆排序,冒泡排序,快速排序,归并排序。

在介绍七大排序之前我们先来认识一下排序的概念。

一、排序的概念及引用

1.1 排序的概念

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键词的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序仍保持不变。即在原序列中,arr[i] = arr[j],且 arr[i]在arr[j]之前,而在排序后,arr[i]仍在arr[j]之前,则称此排序为稳定的排序,某则称为不稳定排序。

需要注意的是:一个本身稳定的排序可以实现不稳定,但是反之则不行。

1.2 常见排序算法:

 

二、常见排序算法的实现

2.1 插入排序

2.1.1 直接插入排序

直接插入排序是一种简单的插入排序算法,其基本思想是:

把待排序的记录按其关键码的大小逐个插入到一个已经排好序的有序队列中,直到所有的记录插入完为止;实际中打扑克牌时,就用到此思想。

以下为动图展示:

时间复杂度

当待排列数有序时,时间复杂度最小,因为这时,内循环直接执行了break。

当待排列数逆序时,时间复杂度最大,每次内循环中都要进行交换,当i=1时,内层执行一次,i=2时,内层执行两次........当i=n-1时,内层执行n-1次,即:(n-1)n/2,大O阶渐进法为:n^2.

空间复杂度

所创建变量不随着某个值增大,即为O(1).

稳定性

先说结论,其本身是稳定排序,但是如果我们将if (num[j] > tmp)加上等号变为:if (num[j] >= tmp),可以实现不稳定排序。

 代码实现:

    /**
     * 直接插入排序
     * 时间复杂度:逆序的时候(最坏)-> O(n^2)
     *          有序的时候(最好)-> O(n)
     *          结论:在数据基本有序的时候,使用直接插入排序
     * 空间复杂度:O(1)
     * 稳定性:稳定
     * @param num
     */
    public static void insertSort(int[] num) {
        for (int i = 1; i < num.length; i++) {
            int tmp = num[i];
            int j = i-1;
            for (; j >= 0; j--) {
                if (num[j] > tmp) {
                    num[j+1] = num[j];
                }else {
                    break;
                }
            }
            num[j+1] = tmp;
        }
    }

直接插入排序的总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

2.1.2  希尔排序(缩小增量排序)

希尔排序法又称缩小增量法,是直接插入排序的改进,希尔排序的基本思想是:它通过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。

分析:

gap = 5 时,之所以摒弃传统的,相邻两个数为一组的情况,是因为图中这种分法可以更好的将大的数放到后面,小的数放到前面。

gap = 1 时,就是将其作为一个整体,进行直接插入排序,

前面的两趟排序都是预排序,让待排列数趋向于有序。

代码实现:

 /**
     * 希尔排序
     * 时间复杂度:O(n^1.3~n^1.5)
     * 空间复杂度:O(1)
     * 稳定性:不稳定
     * @param num
     */
    public static void shell(int[] num, int gap) {
        for (int i = gap; i < num.length; i++) {
            int tmp = num[i];
            int j = i - gap;
            for (; j >= 0; j -=gap) {
                if (num[j] > tmp) {
                    num[j+gap] = num[j];
                }else {
                    break;
                }
            }
            num[j+gap] = tmp;
        }
    }
    public static void shellSort(int[] num) {
        int gap = num.length;
        while (gap > 1) {
            shell(num,gap);
            gap /= 2;
        }
        shell(num,1);
    }

可能有人会问,这样的缩小增量的方法,真的有助于提高效率嘛?分那么多次组,看上去很麻烦。

先说结论,既然是优化,那必然是有助于提高效率的,从时间复杂度的角度考量的话:

对于10000个数据,如果直接进行 直接插入排序,那么时间复杂度度就是n^2,也就是 

10000*10000  = 1亿。如果将其分组,比如分为两组:100*100,那么每组的时间复杂度就是:

100*100,那么100组就是 100*100*100 = 100万,时间复杂度大大缩小了。

希尔排序的总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap>1 时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序了,再此基础上排序就会大大提高效率,达到最优情况。
  3. 希尔排序的时间复杂度不好计算,因为gap的取值可以有很多种方法,另一方面,许多书籍中对希尔排序的时间复杂度也都不固定。
  4. 稳定性:不稳定。

2.2 选择排序

2.2.1 直接选择排序

每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

 代码实现:

    /**
         * 直接选择排序
         * 时间复杂度:O(n^2) -> 对数据不敏感,不管你是有序还是无序,时间复杂度不变
         * 空间复杂度:O(1)
         * 稳定性: 不稳定的排序
         * @param num
         */
    public static void selectSort(int[] num) {
        for (int i = 0; i < num.length; i++) {
            int minIndex = i;
            for (int j = i+1; j < num.length; j++) {
                if (num[j] < num[minIndex]) {
                    minIndex = j;
                }
            }
            swap(num,i,minIndex);
        }
    }

    private static void swap(int[] array, int i, int minIndex) {
        int tmp = array[i];
        array[i] = array[minIndex];
        array[minIndex] = tmp;
    }

时间复杂度:

当有n个数据的时候,内层循环需要比较n-1次,当有n-1个数据的时候,内层循环需要比较n-2次.......因此,时间复杂度 1+2+3+...+n-1 = (n-1)*n  /  2。

直接选择排序总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

以下为对选择排序的优化:

定义两个指针,left和right,在搜索的过程中,让left找最小的元素,right找最大的元素。

public static void selectSort2(int[] num) {
        int left = 0;
        int right = num.length-1;
        while(left < right) {
            int minIndex = left;
            int maxIndex = left;
            for (int i = left+1; i <= right; i++) {
                if(num[i] > maxIndex) {
                    maxIndex = i;
                }
                if (num[i] < minIndex) {
                    minIndex = i;
                }
            }
            swap(num,left,minIndex);
            if (left == maxIndex) {
                maxIndex = minIndex;
            }
            swap(num,right,maxIndex);
            left++;
            right--;
        }
    }

需要注意的是:在将left所指向的元素和minIndex指向的元素交换后,需要注意是否存在一种情况:left和maxIndex指向了同一个值。

为了避免这种状况的发生(执行了第一个swap后,将maxIndex指向的值修改了),于是这里加入了一个if语句。

2.2.2 堆排序

堆排序是指利用堆这种数据结构所设计的一种排序,它是选择排序的一种。需要注意的是排升序是要建立大堆,降序是要建小堆。

堆排序的流程:

建立一个大根堆,每次最后一个元素跟堆顶元素进行交换,之后进行向下调整,直到交换完成。

代码实现:


    /**
     * 时间复杂度:O(n*logn)
     * 空间复杂度:O(1)
     * 稳定性:不稳定的
     * @param num
     */
    public static void heapSort(int[] num) {
        createBigHeap(num);
        int end = num.length - 1;
        while (end >= 0) {
            swap(num,0,end);
            shiftDown(num,0,end);
            end--;
        }
    }
    private static void createBigHeap(int[] num) {
        for (int parent = (num.length-1-1)/2; parent >=0; parent--) {
            shiftDown(num,parent,num.length);
        }
    }
    private static void swap(int[] array, int i, int minIndex) {
        int tmp = array[i];
        array[i] = array[minIndex];
        array[minIndex] = tmp;
    }

    /**
     * 实现向下调整
     * @param num
     * @param parent 每颗子树的根节点的下标
     * @param len 每颗子树调整的结束位置
     */
    private static void shiftDown(int[] num,int parent,int len) {
        int child = 2*parent + 1;
        //最起码保证有左孩子
        while (child < len) {
            //判断左右孩子最大值的前提是必须有右孩子
            if (child+1 < len && num[child] < num[child+1]) {
                child++;//此时保存了最大值的下标
            }
            if (num[child] > num[parent]) {
                swap(num,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

堆排序总结:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

2.3 交换排序

2.3.1 冒泡排序

冒泡排序,是一种简单的排序算法。其重复的走访待排序的数据,一次比较两个元素,如果它们的顺序错误就将它们交换过来。走访待排序的工作需要执行到不再交换为止。

代码实现:

    /**
     * 冒泡排序
     * 时间复杂度:O(N^2)
     * 空间复杂度:O(1)
     * 稳定性:稳定
     * @param num
     */
    public static void bubbleSort(int[] num) {
        for (int i = 0; i < num.length-1; i++) {
            for (int j = 0; j < num.length-1-i; j++) {
                if (num[j] > num[j+1]) {
                    swap(num,j,j+1);
                }
            }
        }
    }

可以在时间复杂度上进行优化,当待排序的数据有序的时候,就可以直接跳出循环,这样在有序的情况下,时间复杂度可以被优化为O(n)。

   public static void bubbleSort2(int[] num) {
        for (int i = 0; i < num.length-1; i++) {
            boolean flag = false;
            for (int j = 0; j < num.length-1-i; j++) {
                if (num[j] > num[j+1]) {
                    swap(num,j,j+1);
                    flag = true;
                }
            }
            if (!flag) {
                break;
            }
        }
    }

冒泡排序总结:

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

2.3.2 快速排序

快排的基本思想是:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后对左右子序列重复该过程,直到所有的元素都排列到相应位置为止。

 实现代码1(Hoare法):

排序算法性能王——快速排序-云社区-华为云

 可能有人会问:为什么在左边标注Key,而是右边的right先走,而不是左边的left先走呢?

如果先走左边的话,前面虽然过程都是一样的,但是left和right相遇之后的数据,是比基准(key)要大的数字,如果交换,那么就把比基准大的数字放到了前面。

    /**
     * 快速排序
     * 时间复杂度:n*logN【最好状态】 O(N^2)【最坏情况】
     * 空间复杂度:logN
     * 稳定性:不稳定
     * @param num
     */
    public static void quickSortHoare(int[] num,int left,int right) {
        //等于是因为代表只有一个节点,大于是因为left可能会大于right(有序或者逆序的情况)
        if(left >= right) {
            return;
        }
        int pivot = partition(num,left,right);
        quickSortHoare(num,left,pivot-1);
        quickSortHoare(num,pivot+1,right);
    }
    private static int partition(int[] num,int start,int end) {
        int i = start;
        int key = num[start];
        while (start < end) {
            //这里添加start < end 是因为可能会出现数组越界的情况
            //为啥这里取等号?
            //防止进入死循环,比如 3,4,2,5,3 这种情况。即 key与left和right相等。
            while (start < end && num[end] >= key) {
                end--;
            }
            while (start < end && num[start] <= key) {
                start++;
            }
            swap(num,start,end);
        }
        swap(num,i,start);
        return start;
    }

需要注意的是,在待排列数据 有序或者逆序数据量很大 的情况下,这种利用递归思路的快排可能会发生栈溢出的问题——时间复杂度O(N^2)。

这是因为我们递归的深度太深了,而函数的递归是在栈上开辟栈帧的。

ps:虽然IDEA提供了修改开辟栈帧大小的功能,但是不推荐使用。

 快排的使用场景一般是无序的应用场景,有序一般是使用插入或者希尔排序:

 实现代码2(挖坑法):

排序算法性能王——快速排序-云社区-华为云

    public static void quickSortHole(int[] num,int left,int right) {
        //等于是因为代表只有一个节点,大于是因为left可能会大于right
        if(left >= right) {
            return;
        }
        int pivot = partition2(num,left,right);
        quickSortHoare(num,left,pivot-1);
        quickSortHoare(num,pivot+1,right);
    }

    /**
     * 挖坑法
     * @param num
     * @param start
     * @param end
     * @return
     */

    private static int partition2(int[] num,int start,int end) {
        int key = num[start];
        while (start < end) {
            while (start < end && num[end] >= key) {
                end--;
            }
            num[start] = num[end];
            while (start < end && num[start] <= key) {
                start++;
            }
            num[end] = num[start];
        }
        num[start] = key;
        return start;
    }

 实现代码3(前后指针法):

快速排序思路(前后指针版),代码实现_有裂痕的石头的博客-CSDN博客_前后指针代码

基本思路:cur遇到比基准小的值就往后走,再次遇到小的时候(先遇到大的)停下来,交换,主要目的就是把大的往后移。

 /**
     * 前后指针法(快排)
     * @param num
     * @param left
     * @param right
     */
    public static void quickSortPointer(int[] num,int left,int right) {
        //等于是因为代表只有一个节点,大于是因为left可能会大于right(有序或者逆序的情况)
        if(left >= right) {
            return;
        }
        int pivot = partition3(num,left,right);
        quickSortHoare(num,left,pivot-1);
        quickSortHoare(num,pivot+1,right);
    }

    private static int partition3(int[] num,int start,int end) {
        int prev = start;
        int cur = start+1;
        while(cur <= end) {
            if (num[cur] < num[start] && num[++prev] != num[cur]) {
                swap(num,cur,prev);
            }
            cur++;
        }
        swap(num,start,prev);
        return prev;
    }

2.3.3 快速排序优化

  1. 三数取中法选key
  2. 递归到小的子区间时,可以考虑使用插入排序

分析:

所谓三数取中法:在带排列数据的最左端和最右端以及中间的元素中,找到三个数中的中位数,并

将这个中位数作为基准放到待排列数据中的第一位。

为什么使用三数取中的方法呢?

这是对于待排列数据是有序的情况下,如果没有对取基准作任何调整的话,可能会让递归的深度增加,影响算法的效率。

 /**
     * 快排优化
     * 时间复杂度:n*logN
     * 空间复杂度:最好:O(logN)。最坏。O(N) 当n足够大的时候,递归的深度就大
     * 稳定性:不稳定。
     * @param num
     * @param start
     * @param end
     */
    public static void insertSort2(int[] num,int start,int end) {
        for (int i = start+1; i <= end; i++) {
            int tmp = num[i];
            int j = i-1;
            for (; j >= start; j--) {
                if (num[j] > tmp) {
                    num[j+1] = num[j];
                }else {
                    break;
                }
            }
            num[j+1] = tmp;
        }
    }
    public static void quickSort(int[] num,int left,int right) {
        //等于是因为代表只有一个节点,大于是因为left可能会大于right(有序或者逆序的情况)
        if(left >= right) {
            return;
        }
        //小区间使用了插入排序,主要是优化了递归的深度
        if (right - left + 1 <= 7) {
            insertSort2(num,left,right);
            return;
        }
        //三数取中,用了这个方法每次的待排序序列,每次基本都是采取二分的方法。
        int index = midNumIndex(num,left,right);
        swap(num,left,index);
        int pivot = partition2(num,left,right);
        quickSort(num,left,pivot-1);
        quickSort(num,pivot+1,right);
    }
   /**
     * 挖坑法
     * @param num
     * @param start
     * @param end
     * @return
     */

    private static int partition2(int[] num,int start,int end) {
        int key = num[start];
        while (start < end) {
            while (start < end && num[end] >= key) {
                end--;
            }
            num[start] = num[end];
            while (start < end && num[start] <= key) {
                start++;
            }
            num[end] = num[start];
        }
        num[start] = key;
        return start;
    }
    //全排列-六种情况
    private static int midNumIndex(int[] num,int left,int right) {
        int mid = (left + right) / 2;
        if(num[left] < num[right]) {
            if (num[mid] < num[left]) {
                return left;
            }else if(num[mid] > num[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            if (num[mid] < num[right]) {
                return right;
            } else if (num[mid] > num[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }

 2.3.4 非递归的快排

    /**
     * 非递归实现快排
     * @param arr
     */
    public static void quickSort(int[] arr) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = arr.length-1;
        int pivot = partition2(arr,left,right);
        if (pivot > left+1) {
            stack.push(left);
            stack.push(pivot-1);
        }
        if (pivot < right-1) {
            stack.push(pivot+1);
            stack.push(right);
        }
        while (!stack.empty()) {
            right = stack.pop();
            left = stack.pop();
            pivot = partition2(arr,left,right);
            if (pivot > left+1) {
                stack.push(left);
                stack.push(pivot-1);
            }
            if (pivot < right-1) {
                stack.push(pivot+1);
                stack.push(right);
            }
        }
    }

 快速排序总结:

  1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(logN)
  4. 稳定性:不稳定

2.4 归并排序

归并排序是建立在归并操作上的一种有效的算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再将两个有序表合并成一个有序表,称为二路归并。

 实现代码:

   private static void mergeSortFunc(int[] arr,int left, int right) {
        if (left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        // 1.分解左边
        mergeSortFunc(arr,left,mid);
        // 2.分解右边
        mergeSortFunc(arr,mid+1,right);
        // 3.进行合并
        merge(arr,left,right,mid);
    }
    private static void merge(int[] arr, int start, int end,
                              int mid) {
        int[] tmpArr = new int[end-start+1];
        //tmpArr 数组的下标
        int k = 0;
        int s1 = start;
        int s2 = mid+1;
        //两个归并段都有数据
        while (s1 <= mid && s2 <= end) {
            if (arr[s1] <= arr[s2]) {
                tmpArr[k++] = arr[s1++];
            }else {
                tmpArr[k++] = arr[s2++];
            }
        }
        //到走这里时候,说明有一段的数据中没了数据,需要拷贝另一端的全部数据到数组中。
        while (s1 <= mid) {
            tmpArr[k++] = arr[s1++];
        }
        while (s2 <= end) {
            tmpArr[k++] = arr[s2++];
        }
        //把排好序的数字拷贝会原数组
        for (int i = 0; i < k; i++) {
            arr[i+start] = tmpArr[i];
        }
    }

    /**
     * 时间复杂度;O(n*logN)
     * 空间复杂度:O(N)
     * 稳定性:稳定排序
     * 直接插入排序,冒泡排序,归并
     * @param arr
     */

    public static void mergerSort(int[] arr) {
        mergeSortFunc(arr,0,arr.length-1);
    }

非递归实现归并排序: 

 /**
     * 非递归实现归并排序
     * @param arr
     */
    public static void mergerSort2(int[] arr) {
        int gap = 1;

        while(gap < arr.length) {

            for (int i = 0; i < arr.length; i += gap*2) {
                int s1 = i;
                int e1 = s1+gap-1;
                if (e1 >= arr.length) {
                    e1 = arr.length-1;
                }
                int s2 = e1+1;
                if (s2 >= arr.length) {
                    s2 = arr.length-1;
                }
                int e2 = s2+gap-1;
                if (e2 >= arr.length) {
                    e2 = arr.length-1;
                }
                merge(arr,s1,e2,e1);
            }
            gap *= 2;
        }
    }
    private static void merge(int[] arr, int start, int end,
                              int mid) {
        int[] tmpArr = new int[end-start+1];
        //tmpArr 数组的下标
        int k = 0;
        int s1 = start;
        int s2 = mid+1;
        //两个归并段都有数据
        while (s1 <= mid && s2 <= end) {
            if (arr[s1] <= arr[s2]) {
                tmpArr[k++] = arr[s1++];
            }else {
                tmpArr[k++] = arr[s2++];
            }
        }
        //到走这里时候,说明有一段的数据中没了数据,需要拷贝另一端的全部数据到数组中。
        while (s1 <= mid) {
            tmpArr[k++] = arr[s1++];
        }
        while (s2 <= end) {
            tmpArr[k++] = arr[s2++];
        }
        //把排好序的数字拷贝会原数组
        for (int i = 0; i < k; i++) {
            arr[i+start] = tmpArr[i];
        }
    }

归并排序总结

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定

2.5 海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

三、排序算法复杂度及稳定性分析

排序方法最好平均最坏空间复杂度稳定性
冒泡排序O(n)O(n^2)O(n^2)O(1)稳定
插入排序O(n)O(n^2)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)不稳定
希尔排序O(n^1.3)O(n^1.3)O(n^1.5)O(1)不稳定
堆排序O(n * log(n))O(n * log(n))O(n * log(n))O(1)不稳定
快速排序O(n * log(n))O(n * log(n))O(n^2)O(log(n)) ~ O(n)不稳定
归并排序O(n * log(n))O(n * log(n))O(n * log(n))O(n)稳定

四、计数排序(了解)

主要思路:

一种非比较排序。计数排序对一定范围内的整数排序时候的速度非常快,一般快于其他排序算法。但计数排序局限性比较大,只限于对整数进行排序,而且待排序元素值分布较连续、跨度小的情况。

如果一个数组里所有元素都是整数,而且都在 0-k 以内。对于数组里每个元素来说,如果能知道数组里有多少项小于或等于该元素,就能准确地给出该元素在排序后的数组的位置。

如给定一个 0~5 范围内的数组[2,5,3,0,2,3,0,3],对于元素 5 为其中最大的元素,创建一个大小为(5-0+1 = 6)的计数数组,如果原数组中的值对应计数数组的下标,则下标对应计数数组的值加 1。

提问上面是通过数组的最大值来确定计数数组的长度的,但如果需要对学生的成绩进行排序,如学生成绩为:[95,93,92,94,92,93,95,90],那应该如何处理呢?

如果按照上面的方法来处理,则需要一个大小为 100 的数组,但是可以看到其中的最小值为 90,那也就是说前面 0~89 的位置都没有数据存放,造成了资源浪费。

如果我们知道了数组的最大值和最小值,则计数数组的大小为(最大值 - 最小值 + 1),如上面数组的最大值为 99,最小值为 90,则定义计数数组的大小为(95 - 90 + 1 = 6)

代码实现:

    /**
     * 计数排序
     * @param num
     */
    public static void countSort(int[] num) {
        int maxVal = num[0];
        int minVal = num[0];

        for (int i = 0; i < num.length; i++) {
            if (num[i] < minVal) {
                minVal = num[i];
            }
            if (num[i] > maxVal) {
                maxVal = num[i];
            }
        }
        int len = maxVal - minVal + 1;
        int[] count = new int[len];
        //开始遍历num数组,开始计数。
        for (int i = 0; i < num.length; i++) {
            int val = num[i];
            count[val-minVal]++;
        }
        //这时num数组的下标
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            //确保当前count数组可以打印完成
            while (count[i] != 0) {
                num[index = i+minVal;
                index++;
                count[i]--;
            }
        }
    }

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/372202.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【多尺度密集递归融合网络:超分】

A novel image super-resolution algorithm based on multi-scale dense recursive fusion network &#xff08;基于多尺度密集递归融合网络的图像超分辨率算法&#xff09; 随着卷积神经网络(CNN)技术的成熟度,超限分辨的图像重建(SR)方法基于CNN正在蓬勃发展,取得了许多显…

RIP路由协议的更新(电子科技大学TCP/IP第二次实验)

一&#xff0e;实验目的 1、掌握 RIP 协议在路由更新时的发送信息和发送方式 2、掌握 RIP 协议的路由更新算法 二&#xff0e;预备知识 1、静态路由选择和动态路由选择 2、内部网关协议和外部网关协议 3、距离向量路由选择 三&#xff0e;实验原理 RIP 协议&#xff08…

使用Python进行数据分析——线性回归分析

大家好&#xff0c;线性回归是确定两种或两种以上变量之间互相依赖的定量关系的一种统计分析方法。根据自变量的个数&#xff0c;可以将线性回归分为一元线性回归和多元线性回归分析。一元线性回归&#xff1a;就是只包含一个自变量&#xff0c;且该自变量与因变量之间的关系是…

AMBA-AXI(一)burst 传输-INCR/WRAP/Fixed

&#x1f4a1;Note&#xff1a;本文是根据AXI协议IHI0022F_b_amba_axi_protocol_spec.pdf&#xff08;issue F&#xff09;整理的。主要是分享AXI3.0和4.0部分。如果内容有问题请大家在评论区中指出&#xff0c;有补充或者疑问也可以发在评论区&#xff0c;互相学习&#x1f64…

JUC 体系的基石——AQS

—— AQS&#xff08;AbstractQueuedSynchronizer&#xff09; 概念 抽象队列同步器&#xff1b;volatile cas 机制实现的锁模板&#xff0c;保证了代码的同步性和可见性&#xff0c;而 AQS 封装了线程阻塞等待挂起&#xff0c;解锁唤醒其他线程的逻辑。AQS 子类只需要根据状…

182、【动态规划/数组】leetcode ——647. 回文子串:动态规划+双指针(C++版本)

题目描述 原题链接&#xff1a;647. 回文子串 解题思路 &#xff08;1&#xff09;动态规划 动态规划的思路是每次判定子串两端对称位置是否相等&#xff0c;然后再基于已有的内侧对称情况判定是否为回文串。 动态规划五步曲&#xff1a; &#xff08;1&#xff09;dp[i][…

语音识别与Python编程实践

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

uni-app入门并使用学习

笔记课程 工具准备 下载HBuilderX 点击下载HBuilderX 下载微信开发者工具 点击下载微信开发者工具 使用参考uni-app官网 官网 新建项目运行 文件---新建----项目 运行到谷歌浏览器H5 运行------谷歌浏览器打开---打开成功&#xff08;第一次可能需要安装插件&#xff0…

React(三) ——新、旧生命周期

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React生命周期&#x1f30b;初始化阶段&#x1f463;运行中阶段&#x1f3d3;销毁阶段&#x1f3eb;新生命周期的替代&#x1f69a;react中性…

MS9123是一款单芯片USB投屏器,内部集成了USB2 0控制器和数据收发模块、视频DAC和音视频处理模块,MS9123可以通过USB接口显示或者扩展PC、

MS9123是一款单芯片USB投屏器&#xff0c;内部集成了USB2.0控制器和数据收发模块、视频DAC和音视频处理模块&#xff0c;MS9123可以通过USB接口显示或者扩展PC、智能手机、平板电脑的显示信息到更大尺寸的显示设备上&#xff0c;支持CVBS、S-Video视频接口。 主要功能特征 C…

基本中型网络的仿真(RYU+Mininet的SDN架构)-以校园为例

目录 ​​​​​​​具体问题可以私聊博主 一、设计目标 1.1应用场景介绍 1.2应用场景设计要求 网络配置方式 网络技术要求 网络拓扑要求 互联互通 二、课程设计内容与原理 &#xff08;1&#xff09;预期网络拓扑结构和功能 &#xff08;1&#xff09;网络设备信息 …

aws ecr 使用golang实现的简单镜像转换工具

https://pkg.go.dev/github.com/docker/docker/client#section-readme 通过golang实现一个简单的镜像下载工具 总体步骤 启动一台海外区域的ec2实例安装docker和awscli配置凭证访问国内ecr仓库编写web服务实现镜像转换和自动推送 安装docker和awscli sudo yum remove awsc…

超市怎么做微信小程序_线上超市小程序开发可以实现什么功能呢

1。开发超市小程序有什么价值&#xff1f; 1、对于消费者来说&#xff1a;通过超市小程序能够更加直接的购买到想要的产品&#xff0c;消费者无需再到门店寻找商品可以直接通过超市小程序进行在线浏览&#xff1b;通过在线搜索的方式能够更加便捷的搜索到相应的商品&#xff0…

第一篇自我介绍(单片机)

小白的单片机之旅 &#x1f914;自我介绍&#x1f914; &#x1f60a;学习目标&#x1f60a; &#x1f61c;关于单片机&#x1f61c; &#x1f31d;小结&#x1f31d; &#x1f389;博客主页&#xff1a;小智_x0___0x_ &#x1f389;欢迎关注&#xff1a;&#x1f44d;点赞&…

JavaSE学习笔记day14

二、Set Set集合是Collection集合的子接口,该集合中不能有重复元素!! Set集合提供的方法签名,与父接口Collection的方法完全一致!! 即没有关于下标操作的方法 Set接口,它有两个常用的子实现类HashSet,TreeSet 三、HashSet HashSet实现了Set接口,底层是hash表(实际上底层是HashM…

QML 中的 5 大布局

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 在 QML 中,可以通过多种方式对元素进行布局 - 手动定位、坐标绑定定位、锚定位(anchors)、定位器和布局管理器。 说到 anchors,可能很多人都不太了解,它是 QML 中一个非常重要的概念,主要提供了一种相…

C语言几种判断语句简述

C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 C 语言把任何非零和非空的值假定为 true&#xff0c;把零或 null 假定为 fals…

Vuex基础语法

Vuex vuex官网 文章目录Vuexvuex的工作原理图2.vuex的环境搭建3.vuex的使用1.actons2. mutations3.getters4.vuex中的map映射属性4.1 mapState和mapGetters4.2 mapMutations和mapActions5.vuex多组件通信1.通过计算属性获得2.通过mapState获得6.vuex模块化和命名空间6.1模块化…

为什么要用线程池?

1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 2.提高响应速度。当任务到达时&#xff0c;任务可以不需要的等到线程创建就能立即执行。 3.提高线程的可管理性。线程是稀缺资源&#xff0c;如果无限制的创建&#xff0c;不仅会消耗系统资源&#…

王道《操作系统》学习(一)——计算机系统概述

1.1 操作系统的概念、功能 1.1.1 操作系统的概念&#xff08;定义&#xff09; &#xff08;1&#xff09;操作系统是系统资源的管理者 &#xff08;2&#xff09;向上层用户、软件提供方便易用的服务 &#xff08;3&#xff09;是最接近硬件的一层软件 1.1.2 操作系统的功能…