十种排序方法详解与C++实现

93 阅读4分钟

基础知识

  • 稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序;
  • 非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
  • 原地排序:原地排序就是指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序;
  • 非原地排序:需要利用额外的数组来辅助排序。
  • 时间复杂度:一个算法执行所消耗的时间(次数,因为同一程序在不同系统的执行时间可能不同);
  • 空间复杂度:运行完一个算法所需的内存大小。

实验数据

// 大小为16的数组
// 正序,乱序,倒序
int nums0[] = {2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50, 100};
int nums1[] = {3, 44, 38, 5, 47, 15, 36, 26, 27, 100, 2, 46, 4, 19, 50, 48};
int nums2[] = {100, 50, 48, 47, 46, 44, 38, 36, 27, 26, 19, 15, 5, 4, 3, 2};
每次统计正序、乱序、倒序在不同排序方法中的循环轮数和比较次数

一、冒泡排序

算法思路:

  1. 比较相邻的元素,如果第一个比第二个大,则交换他们两个;
  2. 针对每一组相邻的元素做同样工作,从开始第一队到最后一对,这样在最后的元素会是最大数(每次把最大数移到最后)(第i轮比较len-i次)
  3. 针对所有元素重复len-1次(第一轮比较len-1次,最后一轮比较1次)

特点:每一轮冒泡过后,在过去一轮遍历中访问过的元素中的最大元素,一定会到达它最终应当处在的位置。

// 冒泡排序初始版
void bubblesort0(int nums[], int len) {
    int id = 1;
    // 此处len-1 or len均可
    // i<len: i = 0...len-1
    //        j < len-1...0
    //        0 为 len-1时候,j<0条件不满足,不会进入内循环
    // i在0到len-2之间时候有意义,外循环最多len-1次
    for (int i = 0; i < len-1; i++) {
        for (int j = 0; j < len - i - 1; j++) {
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j+1];
                nums[j + 1] = temp;
            }
            cout << id++ << " ";
            print(nums, len);
        }
        cout << endl;
    }
}
// 此时案例比较次数120次
// (len-1 + 1)*(len-1)/2  

优化1:

将for循环改成while格式,效率上没有变化,但有利于接下来优化操作

// 冒泡排序优化1
void bubblesort1(int nums[], int len) {
    int i = len - 1;
    int id = 1;
    while(i>0){
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
            // cout << id++ << " ";
            cout << "第" << len-i << "轮循环,第" << id++ << "次比较   ";
            print(nums, len);
        }
        i--;
        cout << endl;
    }
}

优化2:

设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。
由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

// 冒泡排序优化2
void bubblesort2(int nums[], int len) {  
    int i = len - 1;
    int id = 1;
    int epoch = 1;
    while (i > 0) {
        int pos = 0;
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                pos = j;  // 保留这一轮最后一次交换位置
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较, 上一个交换位置为" << pos << "   ";
            print(nums, len);
        }
        i = pos; 
        epoch++;
        // pos后面均有序
        // 下一轮交换,只到上一轮的最后一次交换位置
        // 下一轮交换j最大为pos-1,会与pos位置进行比较
        cout << endl;
    }
}

优化3

设置一标志性变量flag, 用于记录每轮是否发生交换,没有的话则不需要进行下一轮。 没有优化2巧妙强大,优化2如果pos为0,则等价于flag为false,flag只能用于所有排序完成,没有办法优化中间排序过程。

// 冒泡排序优化3
void bubblesort3(int nums[], int len) {   
    int i = len - 1;
    int id = 1;
    bool flag = false;
    int epoch = 1;
    while (i > 0) {
        flag = false;
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                int temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
                flag = true;
            }
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较"<< "   ";
            print(nums, len);
        }
        epoch++;
        if (!flag) {
            break;
        }
        i--;
        cout << endl;		
    }
}

优化4

传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中。 进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。

void bubblesort4(int nums[], int len) {   
    int low = 0;
    int high = len-1;
    int temp, j;
    int id = 0;
    int epoch = 1;
    while (low < high) {
        for (j = low; j < high; j++) {
            if (nums[j] > nums[j + 1]) {
                temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较" << "   ";
            // cout << id++ << " " ;
            print(nums, len);
    }
    high--;
    for (j = high; j >low; j--) {
            if (nums[j] < nums[j-1]) {
                temp = nums[j];
                nums[j] = nums[j-1];
                nums[j-1] = temp;
            }
            // cout << id++ << " ";
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较" << "   ";
            print(nums, len);
        }
        epoch++;
        low++;
        cout << endl;
    }
}

优化5

优化2+优化4

void bubblesort5(int nums[], int len) {   
    int low = 0;
    int high = len - 1;
    int temp, j;

    int id = 0;
    int epoch = 1;
    while (low < high) {
        int pos = 0;
        for (j = low; j < high; j++) {
            if (nums[j] > nums[j + 1]) {
                pos = j;
                temp = nums[j];
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
            // cout << id++ << " ";
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较, 上一个交换位置为" << pos << "   ";
            print(nums, len);
        }
        high = pos;
        for (j = high; j > low; j--) {
            if (nums[j] < nums[j - 1]) {
                pos = j;
                temp = nums[j];
                nums[j] = nums[j - 1];
                nums[j - 1] = temp;
            }
            // cout << id++ << " ";
            cout << "第" << epoch << "轮循环,第" << id++ << "次比较, 上一个交换位置为" << pos << "   ";
            print(nums, len);
        }
        low = pos;
        cout << endl;
        epoch++;
    }
}

实验结果

方法 比较轮数与次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120

二、 快速排序

算法思路:

  1. 先从数列中取一个数作为基准数
  2. 分区过程,将比这个数大的全放右边,比这个数字小的全放左边
  3. 再对左右区间进行重复,直到各区间只有一个数

一趟快排过程为:

  1. 设置两个变量i,j,排序开始时候:i=0,j=len-1;
  2. 以第一个数组元素作为基准数,赋值给pivot,pivot=A[0]=A[i]
  3. 由后向前搜索j--,找到第一个小于pivot的值A[j],将A[i]与A[j]互换
  4. 由前向后搜索i++,找到第一个大于pivot的值A[i],将A[i]与A[j]互换
  5. 重复3、4,直到i==j
3, 44, 38, 5, 47, 15, 36, 26, 27, 100, 2, 46, 4, 19, 50, 48
pivot = 3
第一个小于3的数为2
2, 44, 38, 5, 47, 15, 36, 26, 27, 100, 3, 46, 4, 19, 50, 48
第一个大于3的数为44
2, 3, 38, 5, 47, 15, 36, 26, 27, 100, 44, 46, 4, 19, 50, 48

代码实现:

// 快速排序  
void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

int id = 1;// 比较
int countt = 0;//交换


int partition0(int nums[], int low, int high, int len) {
    int privotKey = nums[low];
    while (low < high) {
        while (low < high && nums[high] >= privotKey) {
            high--;
            cout << "id: " << id++ << " low:" << low << " high:" << high << endl;
        }
        swap(&nums[low], &nums[high]);
        countt++;
        print(nums, len);

        while (low < high && nums[low] <= privotKey) {
            low++;
            cout << "id: " << id++ << " low:" << low << " high:" << high << endl;
        }
        swap(&nums[low], &nums[high]);
        countt++;
        //cout << id++ << " ";
        print(nums, len);
    }
    return low;
}

void quickSort0(int nums[], int low, int high, int len, int epoch) {  
    cout << "epoch: " << epoch << endl;
    cout <<"low: " << low << " high: " << high << endl;
    if (low < high) {
        int privoLoc = partition0(nums, low, high, len);  // 找到low该在的位置
        cout << "privoloc: " << privoLoc << endl;
        quickSort0(nums, low, privoLoc-1, len, epoch+1);
        quickSort0(nums, privoLoc+1, high, len,epoch+1);
    }
}

优化1

随机选取基准值

/*随机选择枢轴的位置,区间在low和high之间*/
int SelectPivotRandom(int nums[], int low, int high)
{
    srand((unsigned)time(NULL));//产生枢轴的位置
    int pivotPos = rand() % (high - low) + low;

    swap(nums[pivotPos], nums[low]);//把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数
    return nums[low];
}

优化2

三数取中法,左中右三个值中选中间那个作为基准

/*函数作用:取待排序序列中low、mid、high三个位置上数据,选取他们中间的那个数据作为枢轴*/
int SelectPivotMedianOfThree(int nums[], int low, int high)
{
    int mid = low + ((high - low) >> 1);//计算数组中间的元素的下标
    //使用三数取中法选择枢轴

    if (nums[mid] > nums[high])//目标: nums[mid] <= nums[high]
    {
        swap(nums[mid], nums[high]);
    }

    if (nums[low] > nums[high])//目标: nums[low] <= nums[high]
    {
        swap(nums[low], nums[high]);
    }

    if (nums[mid] > nums[low]) //目标: nums[low] >= nums[mid]
    {
        swap(nums[mid], nums[low]);
    }

    //此时,nums[mid] <= nums[low] <= nums[high]
    return nums[low];
    //low的位置上保存这三个位置中间的值
    //分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77

三、插入排序

把n个待排序的元素看成一个有序表一个无序表。开始时候,有序表只包含1个元素,无序表包含n-1个元素,排序过程中每次从无序表取出第一个元素,将它插入有序表中适当位置(也是逐个比较),得到新的有序表,重复n-1次即可。

初始有序表长度1 3 44 38 5 47 15 36 26 27 100 2 46 4 19 50 48
    
有序表长度2    3 44 38 5 47 15 36 26 27 100 2 46 4 19 50 48
    
有序表长度3    3 38 44 5 47 15 36 26 27 100 2 46 4 19 50 48
    
              3 38 5 44 47 15 36 26 27 100 2 46 4 19 50 48
有序表长度4    3 5 38 44 47 15 36 26 27 100 2 46 4 19 50 48

有序表长度5    3 5 38 44 47 15 36 26 27 100 2 46 4 19 50 48

写的有点简略,有重复比较的地方,应该可以优化

void InsertSort(int nums[], int len) {
    for (int i = 1; i < len; i++) { // 若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入  

        if (nums[i] < nums[i - 1]) {
            cout << "第" << i << "轮循环,第" << id++ << "次比较    ";
            print(nums, len);
            int j = i - 1;
            int x = nums[i]; // 复制为哨兵, 即存储待排序元素
            while ((x < nums[j])&&(j>=0)) {
                nums[j + 1] = nums[j];
                j--;
                cout << "第" << i << "轮循环,第" << id++ << "次比较, 第"<<countt++<<"次交换  ";
                print(nums, len);
            }
            nums[j + 1] = x;
            // 这里应该不能增加交换次数,但可以增加比较次数(这里的赋值是之前的一部分)
            // cout << "第" << i << "轮循环,第" << id++ << "次比较, 第" << countt++ << "次交换  ";
            cout << "第" << i << "轮循环,第" << id++ << "次比较    ";
            print(nums, len);
    }
    else {
            cout << "第" << i << "轮循环,第" << id++ << "次比较    ";
            print(nums, len);
        }
    }
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77
插入排序15/15/015/77/5015/150/120

四、希尔排序

希尔排序是插入排序的一种,是简单插入排序的优化,也称为“缩小增量排序”。 把记录按照下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的元素越来越多,当增量减至1时,整个元素被分为1组,算法终止。

两种写法

写法1

void ShellSort0(int nums[], int len) {
    for (int gap = len / 2; gap > 0; gap /= 2)
    {
        // 一共有gap个组,对每组进行直接插入排序
        for (int i = 0; i < gap; ++i)
        {
            for (int j = i + gap; j < len; j += gap) {
                // 如果nums[j]<nums[j - gap], 寻找nums[j]的位置(插入排序)
                if (nums[j] < nums[j - gap]) {
                    int tmp = nums[j];
                    int k = j - gap;
                    while (k >= 0 && nums[k] > tmp) {
                        nums[k + gap] = nums[k];
                        k -= gap;
                        cout << id++ << " ";
                        print(nums, len);
                    }
                    nums[k + gap] = tmp;
                    cout << id++ << " ";
                    print(nums, len);
                }
                cout << id++ << " ";
                print(nums, len);
            }
        }
    }
}

写法2

void ShellSort1(int nums[], int len) {
    for (int gap = len / 2; gap > 0; gap /= 2)
    {
        // 一共有gap个组,对每组进行直接插入排序
        // 每一组,默认第一个值有序,从第二个值开始交换排序,所以初始化i=gap
        for (int i = gap; i < len; ++i)
        {
            int j = i; 
            while (j - gap >= 0 && nums[j - gap] > nums[j]) {
                swap(nums[j - gap], nums[j]);
                j = j - gap;
                cout << id++ << " ";
                print(nums, len);
            }
            cout << id++ << " ";
            print(nums, len);
        }
    }
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77
插入排序15/15/015/77/5015/150/120
希尔排序0/49/00/75/00/113/0
希尔排序0/49/00/63/00/81/0

五、选择排序

不断从未排序序列中找到最小元素,存放到排序序列的末尾。

选择排序是给每个位置选择当前元素最小的,如数组[5(1),7,5(2),2],第一轮排序第一个元素5会和2做交换,结果为[2,7,5(2),5(1)],则原数组中的5(1),5(2)相对顺序就改变了;
因此选择排序是一个不稳定的排序算法。

// 选择排序
void SelectSort(int nums[], int len) {
    for (int i = 0; i < len-1; i++) {
        int index = i;
        for (int j = i + 1; j < len; j++) {
            if (nums[index] > nums[j]) {
                index = j;
            }
            cout << id++ << " ";
            print(nums, len);
        }
        swap(nums[i], nums[index]);
    }
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77
插入排序15/15/015/77/5015/150/120
希尔排序0/49/00/75/00/113/0
希尔排序0/49/00/63/00/81/0
选择排序15/120/1515/120/1515/120/15

六、堆排序

堆的定义

  1. 完全二叉树(从上到下,从左到右依次填满,不够的右边叶子节点空着)
  2. 每一个节点都必须大于等于或小于等于其孩子节点的值

堆的特点

  1. O(logN)时间复杂度内向堆插入元素
  2. O(logN)时间复杂度内向堆删除元素
  3. O(1)时间复杂度获取堆中最大值或最小值

堆排序思路

  1. 建立大根堆,将n个元素组成的无序序列构建为一个大根堆
  2. 交换堆元素,交换堆尾元素和堆首元素,使堆尾元素为最大元素
  3. 重建大根堆,将前n-1个元素组成的无序队列调整为大根堆
  4. 重复执行2和3
// 堆排序
// 递归方法构建大根堆
void adjust(int nums[], int len, int index) {
    int left = 2 * index + 1;  // index的左子节点
    int right = 2 * index + 2;

    int maxIdx = index;
    if (left<len && nums[left]>nums[maxIdx]) {
        maxIdx = left;
    }
    cout << "id: " << id++ << " ";
    print(nums, 16);

    if (right<len && nums[right]>nums[maxIdx]) {
        maxIdx = right;
    }
    cout << "id: " << id++ << " ";
    print(nums, 16);

    if (maxIdx != index) {
        swap(nums[index], nums[maxIdx]);
        adjust(nums, len, maxIdx); // 此时maxIdx位置不是最大值了(所以可能底下不是大根堆)
        // countt++;
        cout << "count: " << countt++ << "   ";
        print(nums, 16);
    }
    cout << "id: " << id++ << " ";
    print(nums, 16);
}

void HeapSort(int nums[], int len) {
    // 构建大根堆
    // 从最后一个非叶子节点向上
    for (int i = len / 2 - 1; i >= 0; i--) {
        adjust(nums, len, i);
    }

    // 调整大根堆
    for (int i = len - 1; i >= 1; i--) {
        swap(nums[0], nums[i]);
        // countt++;
        adjust(nums, i, 0); 
        // cout << id++ << " ";
        cout << "count: " << countt++ << "   ";
        print(nums, 16);
    }
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77
插入排序15/15/015/77/5015/150/120
希尔排序00/49/00/75/00/113/0
希尔排序10/49/00/63/00/81/0
选择排序15/120/1515/120/1515/120/15
堆排序198/58/198/58/0150/42/

七、归并排序

  1. 把长度为n的输入序列分成两个长度为n/2的子序列
  2. 对这两个子序列分别采用归并排序
  3. 将排序好的子序列合并成一个最终序列

合并部分代码

void Merge(int nums[], int low, int mid, int high) {
    // low为第1有趣区的第1个元素,i指向第1个元素,
    // mid为第一个有序区的最后1个元素
    int i = low, j = mid + 1, k = 0;
    int* temp = new(nothrow) int[high - low + 1];
    if (!temp) {
        cout << "error";
        return;
    }
    while (i<=mid&&j<=high) {
        if (nums[i] <= nums[j]) {
            temp[k++] = nums[i++];
        }
        else {
            temp[k++] = nums[j++];
        }
        cout << "id: " << id++ << " ";
        print(nums, length);
    }
    while (i <= mid) {
        //若比较完之后,第一个有序区仍有剩余,则直接复制到t数组中
        temp[k++] = nums[i++];
        cout << "id: " << id++ << " ";
        print(nums, length);
    }
    while (j <= high) {
        temp[k++] = nums[j++];
        cout << "id: " << id++ << " ";
        print(nums, length);
    }
    for (i = low, k = 0; i <= high; i++, k++) {
        // cout << i << " ";
        nums[i] = temp[k];
    }
    // cout << endl;
    print(nums, length);
    delete[]temp;
}

递归版本

void MergeSort0(int nums[], int low, int high) {
    if (low < high) {
        int mid = (low + high) / 2;
        MergeSort0(nums, low, mid);
        MergeSort0(nums, mid+1, high);
        Merge(nums, low, mid, high);
    }
}

非递归版本

void MergeSort1(int nums[], int len) {
    int size = 1, low, mid, high;
    // size 可以等于len-1
    while (size <= len - 1) {
        // cout << "size:  " << size << endl;
        low = 0;
        while (low + size <= len - 1) {
            mid = low + size - 1;
            high = min(mid + size, len - 1);//第二个序列个数可能不足size
            Merge(nums, low, mid, high);
            low = high + 1;// 下一次归并时候第一区序列的首位
        }
        size *= 2;
    }
}

实验结果

方法 比较轮数与次数交换次数正序乱序倒序
冒泡排序015/12015/12015/120
冒泡排序115/12015/12015/120
冒泡排序21/1511/9215/120
冒泡排序31/1511/11015/120
冒泡排序48/1208/1208/120
冒泡排序51/144/698/120
快速排序016/120/317/54/5916/120/89
快速排序14/42/197/49/475/46/79
快速排序25/38/175/40/475/40/77
插入排序15/15/015/77/5015/150/120
希尔排序00/49/00/75/00/113/0
希尔排序10/49/00/63/00/81/0
选择排序15/120/1515/120/1515/120/15
堆排序198/58/198/58/0150/42/
归并排序递归/70//70/0/70/
归并排序非递归/81//81/0/81/

非比较排序

八、计数排序

将输入的数据值转化为键存储在额外开辟的数组空间中,具有线性时间复杂度,要求输入的数据有确定范围的整数。

计数排序有几大局限性

1、当我们的数据范围非常大时,我们需要开辟很多的空间。夸张地讲,假如有1000万个数据,数据范围非常大,这个时候用计数排序是十分低效的;
2、当我们的数据之间跨度比较大,比如说出现了数字 1,但是下一个出现的数字是100002-9999中间的数字都没有出现,那么也浪费了很多的空间;
3、当我们的数据范围无法确定时,我们不知道要开辟多少大小的 *count[] * 数组;
4、计数排序还有一大局限性是,他不可以对小数进行排序,毕竟统计数组的下标都是整数呢。

void CountSort(int nums[], int len) {
    int max_element = nums[0];
    for (int i = 1; i < len; i++) {
        max_element = max(nums[i], max_element);
    }
    int* count = new(nothrow) int[max_element + 1]();

    for (int i = 0; i < len; i++) {//统计每个数字出现的次数
        count[nums[i]]++;
    }
    print(count, max_element+1);
    int k = 0;
    for (int i = 0; i < max_element+1; i++) {

        while (count[i]) {
            nums[k++] = i;
            count[i]--;
        }
    }
    delete count;
    print(nums, length);
}

九、桶排序

可以看作计数排序的升级版,计数排序有max_element+1个桶

思路:

  1. 根据待排序集合的数据,确定映射关系和桶的数量
  2. 遍历待排序集合,将每个元素移到对应桶
  3. 对每一个桶元素进行排序
  4. 一次输出每个桶中的元素

具体实现:

  1. 找出待排序数组的最大值max,最小值min
  2. 桶的数量为(max-min)/len + 1
  3. 遍历数组,计算每个元素放的桶 (nums[i]-min)/len
  4. 每个桶各自排序
//桶排序 设置几个桶,放入元素并对桶内排序(采用快排)
void BucketSort(int nums[], int len) {

    //1. 找出待排序数组的最大值max,最小值min
    //2. 桶的数量为(max - min) / len + 1
    //3. 遍历数组,计算每个元素放的桶(nums[i] - min) / len
    //4. 每个桶各自排序
    int max_element = nums[0], min_element = nums[0];
    for (int i = 1; i < len; i++) {
        max_element = max(nums[i], max_element);
        min_element = min(nums[i], min_element);
    }

    int bucket = (max_element - min_element) / len + 1;
    vector<vector<int>> Bucket(bucket);
    for (int i = 0; i < len; i++) {
        int id = (nums[i] - min_element) / len;
        Bucket[id].push_back(nums[i]);
        //将每个元素放入对应桶中
    }

    int k = 0;
    for (int i = 0; i < bucket; i++) {
        //对每个桶进行排序
        //int size = Bucket[i].size();
        sort(Bucket[i].begin(), Bucket[i].end());
        int size = Bucket[i].size();
        for (int j = 0; j < size; j++) {
            nums[k++] = Bucket[i].at(j);
        }
    }
    print(nums, length);
}

十、基数排序

按照低位进行排序,然后收集,再按高位排序,然后再收集,直到最高位

//基数排序
void RadixSort(int nums[], int len) {
    int base = 1, digit = GetMaxDigit(nums, len);
    int* tmp = new int[len];                 //临时数组  
    int* count = new int[10];              //统计数组,统计某一位数字相同的个数
    int* start = new int[10];              //起始索引数组,某一位数字相同数字的第一个的位置

    //最大位数为多少,就循环多少次
    while (digit--) {
        memset(count, 0, 10 * sizeof(int));//每一次都全初始化为0
        //不可以写sizeof(count),这是指针的大小(若为64位,则为8),和普通数组的数组名不一样
        for (int i = 0; i < len; i++) {
            int index = nums[i] / base % 10;  //每一位数字
            count[index]++;
        }

        memset(start, 0, 10 * sizeof(int));//每一次都全初始化为0
        for (int i = 1; i < 10; i++)
            start[i] = count[i - 1] + start[i - 1];

        memset(tmp, 0, len * sizeof(int));   //每一次都全初始化为0
        for (int i = 0; i < len; i++) {
            int index = nums[i] / base % 10;
            tmp[start[index]++] = nums[i];    //某一位相同的数字放到临时数组中合适的位置
        }

        memcpy(nums, tmp, len * sizeof(int));   //复制tmp中的元素到a
        base *= 10;   //比较下一位
        print(nums, length);
    }

    delete[] tmp;                          //释放空间
    delete[] count;
    delete[] start;
}

参考文献

  1. www.cnblogs.com/onepixel/ar…
  2. www.cnblogs.com/BobHuang/p/…
  3. blog.csdn.net/qq_36560894…
  4. zhuanlan.zhihu.com/p/437834776
  5. juejin.cn/post/699334…
  6. C++实现十大排序算法
  7. zhuanlan.zhihu.com/p/125769523
  8. cloud.tencent.com/developer/a…
  9. 算法总结十大排序算法
  10. blog.csdn.net/qq_19525389…
  11. www.cnblogs.com/wanglei5205…
  12. www.cnblogs.com/MAKISE004/c…