• LeetCode题解01_十大排序算法(C/C++实现)


    排序算法可以说是算法的基础,很多leetcode题目也会有涉及
    为了篇幅不至于太长,排序算法原理和leetcode题目分成2篇笔记进行介绍

    • 1.本篇:针对经典的十大排序算法进行介绍
    • 2.下一篇:介绍排序相关的leetcode题目解法点击查看

    00.排序前准备

    提示:为了方便介绍,下面默认都是按照升序进行排序的;为了节省篇幅,只列出了核心函数,且将实现一个功能的部分代码合并成一行处理了

    根据是否利用额外空间?

    排序算法可以分为内部排序(In-place)和外部排序(Out-place)

    • 内部排序:数据记录在内存中进行排序
    • 外部排序:因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存

    针对排序后2个相等键值的顺序和排序前顺序是否一致?

    对排序算法的稳定性有要求,稳定表示的就是排序前后相对顺序一致

    排序算法的简要总结

    在这里插入图片描述

    其中:n:数据规模;k:"桶"的个数

    扩展:公共函数

    排序算法中有很多交换2个元素的内容的需求,因此,将几种常见的交换函数实现列举如下:

    //指针版本
    void swap(int *a, int *b){ 
     int temp = *a;
     *a = *b;
     *b = temp;
    }
    
    /* 不用系统函数的话,swap有下面3种常见实现 */
    //交换数值         //位运算            //指针
    int tmp = s[i];         s[i] ^= s[j];      int temp = *a;
    s[i] = s[j];            s[j] ^= s[i];      *a = *b;
    s[j] = tmp;             s[i] ^= s[j];      *b = temp;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    01.冒泡排序

    在这里插入图片描述

    原理:比较相邻的元素,如果第一个比第二个大,就交换他们两个

    • 1.对每一对相邻元素作同样操作,从开始第一对到结尾最后一对;结束后,最后的元素是最大
    • 2.针对所有的元素重复以上的步骤,除了最后一个
    • 3.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

    注意:如果是比较相邻2个元素的排序算法,最外层的下标通常到只能取到len - 2,因为只有len - 2

    //C
    //简单理解:外层for用来规定有几对的数进行比较;内层for从右向左,跑完就会排序好一个数
    void bubble_sort(int arr[], int len) {
        for (int i = 0; i < len - 1; i++)				 //i没有参加比较,只是用来规定次数
            for (int j = 0; j < (len - 1) - i; j++) {	 //每次保证循环的最后一个元素有序
                if (arr[j] > arr[j + 1]) swap(&arr[j], &arr[j + 1]); //swap     
            }
    }
    
    //C++
    template <typename T> 
    void bubble_sort(T arr[], int len) {
        for (int i = 0; i < len - 1; i++)
            for (int j = 0; j < len - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) swap(arr[j], arr[j + 1]);     
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    02.选择排序

    总结:遍历未排序部分找合适的元素,放在已排序部分末尾

    在这里插入图片描述

    原理:一句话,未排序中找最小值,放在已排序末尾

    • 1.首先在未排序序列中找到最小元素,存放到排序序列的起始位置

    • 2.再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾

    提示:无论什么数据都是 O(n^2) 的时间复杂度,所以用到它时,数据规模越小越好;这个算法时间复杂度很差,且不稳定,除了可以要求,基本不用

    //C
    void selection_sort(int arr[], int len) {
        for (int i = 0 ; i < len - 1; i++) {    //每次将最小元素放在i的位置上,注:i
            int min = i;                        //***记录位置,暂时将当前置为最小***
            for (int j = i + 1; j < len; j++){  //遍历未排序的元素,注:j 的范围
                if (arr[j] < arr[min]) min = j;	//找到目前最小值,记录下标
           }
           swap(&arr[min], &arr[i]);            //交换
        }
    }
    //C++,当函数必须是static函数
    template <typename T> 			//整数和浮点数皆可使用,若要使用仿函数时必须设定大于(>)的运算子
    void selection_sort(std::vector<T>& arr) {
        for (int i = 0; i < arr.size() - 1; i++) {
            int min = i;
            for (int j = i + 1; j < arr.size(); j++)
                if (arr[j] < arr[min]) min = j;    
            std::swap(arr[i], arr[min]);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    03.插入排序

    总结:遍历已排序部分找合适位置给新元素

    在这里插入图片描述

    原理:一句话,顺序遍历,遇到新元素,找到新元素在前面已排列的合适位置插入

    通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
    步骤:

    • 1.待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列

    • 2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

    void insertion_sort(int arr[], int len) {
        int i, j, min;
        for (i = 1; i < len; i++) {   				 	//无序数组,从左到右,默认第一个元素有序
            min = arr[i];             				 	//无序数组中一个元素   
            for (j = i; j > 0 && arr[j-1] > min; j--)	//有序数组,从右到左,数组里一定不是j--
                arr[j] = arr[j-1];                   	//预留出空位j-1
            arr[j] = min;           
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    相关题目:LC315_计算右侧小于当前元素的个数(本题好的地方,逆向的插入 + 二分查找优化已排序部分查找)

    题目:一个整数数组 nums ,按要求返回一个新数组 counts (counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量)

    • 插入排序思路(超时)从后向前的插入排序,从右向左看是升序 + 已排序部分用二分法优化查找
    • 不考虑位置,常规思路:升序排序后,新数组的下标就表示有几个元素小于自己
    • 只考虑自己右侧的元素:从后向前,不断将后面元素升序排列,间接的得到想要的结果
    • 插入排序:顺序遍历,遇到新元素,找到新元素在后面已排列的合适位置插入
    • 细节:用一个vector来维护后面已排序的升序列表,便于找下标
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> sorted, res(nums.size());           	//插入排序中已排序部分用sorted维护
        for (int i = nums.size() - 1; i >= 0; --i) {    	//从后向前,外层是插入排序
            int left = 0, right = sorted.size();        	//二分的双指针,左闭右开
            while (left < right) {
                int mid = left + (right - left) / 2;
                if (sorted[mid] >= nums[i]) right = mid;
                else left = mid + 1;
            }
            res[i] = right;
            sorted.insert(sorted.begin() + right, nums[i]);	//sort是升序
        }
        return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    04.希尔排序

    总结:分段的插入排序,不断缩小排序区间的过程,分-总 的思想

    在这里插入图片描述

    介绍非稳定排序算法,分段跳跃式的插入排序,是基于插入排序的第2点优化的

    • 1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
    • 2.但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

    原理:分割若干子序列进行插入排序(一次移动多个位置) + 不断缩小子序列个数(gap控制)

    步骤

    • 1.对准备排序的数据,根据某一增量分为若干子序列,并对子序列分别进行插入排序

    • 2.逐渐将增量减小,重复步骤1,直至增量为1,此时数据序列基本有序,最后进行插入排序

    注意:子序列里的元素不是挨着的,而是间隔为gap大小,跳跃选取的

    总结:外层for控制gap缩小,内层for执行插入排序

    //C
    void shell_sort(int arr[], int len) {  
        int i, j, min, gap;
        //外层for是用来控制gap不断缩小的
        //初始间隔gap为数组一半,每次循环gap减半,跳跃式的插入排序
        for (gap = len >> 1; gap > 0; gap = gap >> 1) { 		//插入排序外面的一层壳
            for (i = gap; i < len; i++) {    					//无序数组;i=gap, 默认第一个有序,从第二个开始
                min = arr[i];                					//无序数组中第一个元素
                for (j = i; j >= gap && arr[j-gap] > min; j -= gap)//有序数组;从右向左
                    arr[j] = arr[j - gap];   					//将j-gap这个位置空出来
                arr[j] = min;                					//内层for将空出来的位置更新
            }
        }
    }
    //C++(另一种实现)
    template<typename T>
    void shell_sort(T array[], int length) {
        int h = 1;
        while (h < length / 3) h = 3 * h + 1;
        while (h >= 1) {
            for (int i = h; i < length; i++) {
                for (int j = i; j >= h && array[j] < array[j - h]; j -= h) 
                    std::swap(array[j], array[j - h]);
            }
            h = h / 3;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    05.快速排序

    总结:类似partition思路,左小右大

    在这里插入图片描述

    思路:分治法 + 左小右大;每次的小区间都是第一个元素是pivot;最坏运行情况是 O(n^2),比如说顺序数组的快排

    分治步骤

    • 1.先从数组中取出一个数作为key值,称为 “基准”(pivot)
    • 2.将比这个数小的数全部放在它的左边
    • 3.对左右两个小数列重复1和2,直至各区间只有1个数

    总结:不断挖坑的过程,第1个坑是key位置,这个位置数据要提前保留好,便于后面回填

    • 1.从右到左,找到符合条件的数据,填入第一个坑,而这个数据的位置变成新坑

    • 2.从左到右,找到符合条件的数据(与1的条件相反),填入上面的新坑…

    问题:为什么不能执行swap(&arr[i], &arr[j])?

    下面体现了最后的元素放置,如果直接swap,导致永远都是i和j位置互换,pivot没有位置放

    index    start      i       j
    ​       arr[j]    pivot   arr[i]
    
    • 1
    • 2
    //递归,选取 左闭右闭 区间处理
    void quick_sort(int arr[], int start, int end) {
        if (start >= end) return;
        int i = start, j = end - 1;     		//一定要减1,因为会取到arr[j],左闭右闭
        int pivot = arr[start];      		//基准
        while (i < j) {                 		//当前组内寻找一遍,即i和j相遇,只有1个元素结束
            while (i < j && pivot < arr[j]) j--;          //向前寻找
            arr[i] = arr[j];
            while (i < j && pivot >= arr[i]) i++;         //向后寻找
            arr[j] = arr[i];
            //swap(&arr[i], &arr[j]);绝对不能这么用
        }
        arr[i] = pivot;                 		//当组内找完一遍以后就把中间数key回归
        quick_sort(arr, start, i-1);    		//注意:不是所谓的mid,i位置空出来是因为它是pivot
        quick_sort(arr, i+1, end);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    06.归并排序(需要额外空间)

    在这里插入图片描述

    思路:分治法(Divide and Conquer);一句话,两个区间"交替"取最小元素放入新区间

    有2种实现方法

    • 1.自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法)

    • 2.自下而上的迭代

    分治思想:下面一定要体会好先递归后处理的逻辑顺序,先递归体现的是分的思想,递归开始一层层返回时,就会执行到处理逻辑的代码(两个区间排成有序区间),体现的是治的思想

    细节:区间的封闭性一定要统一好,下面采用左闭右闭;使用了额外空间

    void merge_sort(int arr[], const int len) {
        int tmp[len];
        merge_sort(arr, tmp, 0, len - 1);        //左闭右闭
    }
    void merge_sort(int arr[], int tmp[], int start, int end) {
        if (start >= end) return;                //终止条件,只有一个元素
        int mid = start + (end - start) / 2;     //防止overflow
        int start1 = start, end1 = mid;          //2段左闭右闭区间
        int start2 = mid + 1, end2 = end;
        
        //分的思想
        merge_sort(arr, tmp, start1, end1);
        merge_sort(arr, tmp, start2, end2);
        
        //合的思想:二合一 (3个while用于合并2个有序数组,用tmp存临时合并结果)
        int k = start;                                  //注:start开始
        while (start1 <= end1 && start2 <= end2)
            tmp[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
        while (start1 <= end1) tmp[k++] = arr[start1++];
        while (start2 <= end2) tmp[k++] = arr[start2++];
        for (k = start; k <= end; k++) arr[k] = tmp[k];	//注:start开始(临时->正式)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    07.堆排序

    示例:设有一个无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }

    在这里插入图片描述

    堆排序:不严格的BST,不要求左右精确,只要求根大于结点即可

    详细:子结点键值或索引总是 < 或 > 它的父节点;一种利用堆的概念来排序的选择排序,分为2种方法:

    • 大根堆:每个节点的值都 >= 其子节点的值

    • 小根堆:每个节点的值都 <= 其子节点的值

    步骤

    • 1.建堆 (从第一个非叶子节点开始)

    • 2.自动排序 (把堆首(最大值)和堆尾互换 -> 把堆尺寸缩小1,重建堆 -> 直到堆的尺寸为 1)

    思想:从第一个非叶子节点向上,先建一个大根堆;然后每次取出大根堆的root就可以了

    void heap_sort(int arr[], int len) {		
        //1.create heap(自下向上),i从最后一个非叶子节点开始调整
        for (int i = len / 2 - 1; i >= 0; i--)  //注意:>=0
            adjust_heap(arr, i, len - 1);   	//注意:len-1,闭区间,函数会自动调节成大根堆
        
        //2.sort(自上向下)
        //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
        for (int i = len - 1; i > 0; i--) {
            swap(&arr[0], &arr[i]);         	//大根堆,保证i位置存储的是相对的最大值
            adjust_heap(arr, 0, i - 1);     	//把堆的尺寸缩小1,重新调整堆
        }
    }
    void adjust_heap(int arr[], int start, int end){//大根堆,左闭右闭
        //1.建立父子节点
        int dad = start, son = dad * 2 + 1;
        //2.子节点在范围内才比较(注:<=)
        while(son <= end) {
            //子节点中最大的
            if ((son + 1 <= end) && (arr[son] < arr[son + 1])) son++;
            //如果父节点 > 子节点代表调整完毕,直接跳出函数
            if (arr[dad] > arr[son]) return;
            swap(&arr[dad], &arr[son]); 		//交换节点,向下调整
            dad = son; son = dad * 2 + 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    思考:大根堆的应用场景?

    • 升序:每次为了在大根堆中取出一个元素放在最末尾,因此用大根堆

    • topk小:先让k个元素构成根堆,第k+1个元素要将前k个元素中最大的元素替换掉,因此采用大根堆

    实现:stl中大小根堆可以用优先级队列priority_quque(优先级高的在队列前面)实现

    前提:使用前要包含#include ,不好记话,就直接记忆成里面的仿函数类与常规使用的sort里的cmp作用相反;

    • 1.升序队列,小根堆 —简单记忆,root最小,后面逐渐增大,因此是升序

      priority_queue , greater> q;

    • 2.降序队列,大根堆,---默认是 <,简单记忆就是root最大,后面逐渐减小,因此是降序

      priority_queue , less> q;

    08.计数排序

    计数排序:将输入数据值转化为key存储在额外开辟空间,计数([元素]=出现次数),回填数组;线性时间复杂度

    要求:数据必须事先有确定范围

    举例:比如 1 2 1 2 2 3,计数([元素] = 出现次数)[1] = 2, [2] = 3, [3] = 1,总次数是2 + 3 + 1 = 6,共6个位置,从后向前先放大数(反向操作也可以),出现次数就放几个元素

    特征:不是比较排序,需要额外空间放出现次数信息;速度快于任何比较排序算法

    //正常的计数排序局限性:负数不可以用;优点;不用比较
    //count_sort修改了负数不可以的情况,下面使用C最原始的方法,C++直接用map更简单
    void count_sort(int arr[], int len) {
        int min = arr[0], max = arr[0], i;
        //1.find the max and min element
        for (i = 1; i < len; i++){
            min = arr[i] < min ? arr[i] : min;      //如果允许用库函数,直接min()就可以
            max = arr[i] < max ? max : arr[i];
        }
        int num = max - min + 1;
        int *pcount = (int*)malloc(num * sizeof(int));//省略判断指针nullptr
        memset(count, 0, num * sizeof(int));
        //2.count
        for (i = 0; i < len; i++) pcount[arr[i] - min]++;   //注:len, -min ,可处理负数   
        //3.assemble
        int index = 0;
        for (i = 0; i < num; i++){                          
            while(pcount[i]--) arr[index++] = min + i;      //注:min+是base,回填
        }    
        free(pcount); pcount = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    09.桶排序

    在这里插入图片描述

    桶排序:计数排序的升级版;数据映射到不同的桶中,桶内排序,再合并桶

    为了使桶排序更加高效,我们需要做到这2点:

    • 1.在额外空间充足的情况下,尽量增大桶的数量

    • 2.使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

    同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要

    //C++(桶是用单链表实现的,类似于解决哈希碰撞的链式法)
    class solution {
    public:
        void bucket_sort(int arr[], int len) {
            //1.得到BUCKET_NUM个桶,每个桶有一个val=0,next=NULL的节点
            vector<ListNode*> buckets(BUCKET_NUM,(ListNode*)(0));//外层是数组
            //2.元素放入对应桶中(桶内排序好)
            for(int i = 0; i < len; ++i) {
                int index = arr[i] / BUCKET_NUM;             //桶编号
                ListNode *head = buckets.at(index);          //具体哪个桶
                buckets.at(index) = insert(head, arr[i]);    //元素放入桶
            }
            //3.合并桶
            ListNode *head = buckets.at(0);
            for(int i = 1; i < BUCKET_NUM; ++i) head = merge(head, buckets.at(i));
            //4.将所有值全部取出来
            for(int i = 0; i < len; ++i) {
                arr[i] = head->val;head = head->next;  //i++
            }
        }
        
        //insert(元素放入桶时要直接排序好),返回是头节点
        ListNode* insert(ListNode* head, int val){
            //1.新增加一个节点
            ListNode dummy; dummy.next = head;      	//增加一个节点,指向头节点
            //2.遍历
            ListNode *pre, *cur;               //链表:dummy -> head -> ...
            pre = &dummy; cur = head;                
            //遍历链表,找到正确位置,判断的是cur
            while(NULL != cur && cur->val <= val){
                pre = cur; cur = cur->next;
            }
            //3.插入
            ListNode *newNode = new ListNode(val);   //根据val构建一个节点
            newNode->next = cur; pre->next = newNode;
            return dummy.next;
        }
        
        //merge(归并排序也可使用,合并2个链表),返回是头节点
        ListNode* merge(ListNode *head1,ListNode *head2){
            ListNode dummy, *pre = &dummy;
            while(NULL != head1 && NULL != head2) {
                if(head1->val <= head2->val){
                    pre->next = head1; head1 = head1->next;    //i++
                }else{
                    pre->next = head2; head2 = head2->next;    //i++
                }
                pre = pre->next;
            }
            if(NULL!=head1) pre->next = head1;     //没有处理完的链表要单独处理
            if(NULL!=head2) pre->next = head2;
            return dummy.next;
        }
    private:
        const int BUCKET_NUM = 10;              //桶的个数,这个依情况而定
        //单链表节点
        struct ListNode{
            int val;
            ListNode* next;
            explicit ListNode(int i=0):val(i), next(NULL){}
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    10.基数排序

    在这里插入图片描述

    基数排序:非比较型算法,原理是将整数按位数切割成不同的数字,然后按每个位数分别比较

    详细:一句话,根据bit分桶,然后组合桶,一次排序一bit位,这样一直bit0,bit1的下去;这个bit不是二进制bit(是个位、十位、百位...的意思),属于稳定的排序

    基数排序 vs 计数排序 vs 桶排序

    这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

    • 计数排序:每个桶只存储单一键值
    • 桶排序 :每个桶存储一定范围的数值
    • 基数排序:根据键值的每位数字来分配桶

    简单举例:10个桶,此时处理个位数

    1.遍历一遍,就知道每个桶中应该放几个元素了

    • 求桶的index的除数,如798
    • 个位桶index=(798/1)%10=8
    • 十位桶index=(798/10)%10=9
    • 百位桶index=(798/100)%10=7

    2.将桶按顺序排好:0号桶,1号桶,…,9号桶,这会形成一个连续的区间,每个桶长度是元素个数

    3.重新遍历数组,填充每个桶,因为上一步已经给每个桶留出了合适的长度,填充就完了

    //C++
    class solution {
    public:
        //先处理个位,再处理十位...
        void radix_sort(int data[], int n) {
            int count_bit = maxbit(data, n);    //最外层循环的个数
            int *tmp = new int[n];              //合并桶的临时额外空间
            int *buckets = new int[BUCKET_NUM]; //计数器,桶里存的是元素个数
            int i, index;
            int radix = 1;             //控制处理位数
    
            while(count_bit--) {                //进行count_bit次排序
                for(i = 0; i < BUCKET_NUM; i++) buckets[i] = 0;//清空桶          
                //1.统计每个桶中的元素个数
                for(i = 0; i < n; i++){
                    index = (data[i] / radix) % BUCKET_NUM;    //桶的索引
                    buckets[index]++;                          //桶中是元素个数
                 }
                //2.合并后,桶的占用的空间
                //每个桶顺序放置,buckets[i]表示第i个桶放完,元素的个数
                //每个桶的数量 = 以前桶数量和 + 自己的数量
                for(i = 1; i < BUCKET_NUM; i++) buckets[i] += buckets[i - 1];
                //3.将所有桶中记录依次收集到tmp中(逆序放置)
                for(i = n - 1; i >= 0; i--){
                    index = (data[i] / radix) % BUCKET_NUM;    //桶的编号
                    tmp[buckets[index]--] = data[i];           //放元素在相应区间
                 }
                //3.将临时数组tmp的内容复制到data中
                for(i = 0; i < n; i++) data[i] = tmp[i];
                radix = radix * BUCKET_NUM;//radix会是1, 10, 100, 1000,....
            }
            delete []tmp; delete []buckets;
        }
        int maxbit(int data[], int n) {    //辅助函数,求所有数据中最大数的位数
            int maxData = data[0];    
            for (int i = 1; i < n; ++i) {
                if (maxData < data[i]) maxData = data[i];
            }
            int count_bit = 1;
            while (maxData / 10) ++count_bit;
            return count_bit;
        }
    private:
        const int BUCKET_NUM = 10;        //桶的个数,这个依情况而定
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    参考

  • 相关阅读:
    OAuth2简介
    【运维小知识】(一)——centos系统安装(小白入门级)
    python——单例模式
    c++多态
    【Python】PyQt5 Designer工具配置
    能量守恒和打造能量缺口
    “Java基础全方位解析,从入门到精通“
    STC51单片机30——单个数码管显示
    【云原生 | Kubernetes 系列】---Prometheus 监控Java服务
    基于文本IO实现的小程序
  • 原文地址:https://blog.csdn.net/weixin_44531336/article/details/126416471