排序算法

一 比较类排序

通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlog2n),因此也称为非线性时间比较类排序。

快排

优点:快排总体的平均效率是最好的。平均时间复杂度为O(nlog2n),空间复杂度为O(nlog2n)
缺点:当数组本身已经排序好了,而每一轮排序都以最后一个数字作为比较标准,此时快排效率只有O(n2)

#include <iostream>
#include <random>

//  实现快排的关键在于先在数组中取一个数字,接下来把数组中的数字划分。
//  比选择的数字小的数字移动到数组的左边,比选择的数字大的数字移动到数组的右边。

int Partition(int data[], int length, int start, int end)
{
    //  检查输入数据
    if(data == nullptr || length <= 0 || start < 0 || end >=length)
        throw new std::invalid_argument("Invalid Parameters");

    //  使用标准库的随机引擎生成随机下标
    std::default_random_engine e;
    std::uniform_int_distribution<unsigned> u(start,end);
    int index = u(e);

    //  将选择的数字移动到数据末尾,方便比较
    std::swap(data[index], data[end]);

    //  small记录上一个比选择的数字要小的数字的下标
    int small = start - 1;
    for (index = start; index < end; ++index) {
        if(data[index] < data[end])
        {
            ++small;
            if (small != index)
                std::swap(data[index],data[small]);
        }
    }

    ++small;
    std::swap(data[small],data[end]);

    //  返回选择的数字的下标
    return small;
}

//  递归思路分别对每次选中的数字的左右两边进行排序。
void QuickSort(int data[], int length, int start, int end)
{
    if (start == end)
        return;
    int index = Partition(data, length, start, end);
    if (index > start)
        QuickSort(data, length, start, end);
    if (index < end)
        QuickSort(data, length, index+1, end);
}

int main(int,char**)
{
    //  生成随机数组检测
    int arr[10];
    std::default_random_engine e;
    std::uniform_int_distribution<unsigned> u(0,10);
    for (int i = 0; i < 10; ++i) {
        arr[i] = u(e);
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    QuickSort(arr,10,0,9);

    for (int i = 0; i < 10; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;

    return  0;
}

插入排序

平均、最坏时间复杂度:O(n2)
最好时间复杂度:O(n)
空间复杂度:O(1)

void insertSort(int arr[], int length)
{
    if(arr == nullptr || length <= 0)
        return;

    //  current:要插入的值
    //  preIndex:插入点
    int current;
    int preIndex;
    for (int i = 1; i < length; ++i) {
        current = arr[i];
        preIndex = i - 1;
        while (current < arr[preIndex] && preIndex >= 0)
        {
            arr[preIndex+1] = arr[preIndex];
            --preIndex;
        }
        arr[preIndex+1] = current;
    }
}

希尔排序

1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。算法最开始以一定的步长进行插入排序(步长一般是n/2)。然后会继续以一半的步长(n/4)进行插入排序,最终算法以步长为1进行插入排序。
Donald Shell最初建议步长选择为 n/2 并且对步长取半直到步长达到1。
已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,…)。
ShellSort的复杂度分析非常复杂,不同gap序列的设计对应不同的复杂度。

时间复杂度:根据步长序列的不同而不同。
最好时间复杂度:O(nlog2n)
空间复杂度:O(1)

原理:(竖着看 10 > 13 > 25 > 45)
希尔排序原理

void shellSort(int arr[], int length)
{
    if(arr == nullptr || length <= 0)
        return;

    //  gap:步长
    //  current:要插入的值
    //  preIndex:插入点
    for (int gap = length / 2; gap > 0; gap /= 2) {

        //  插入排序
        for (int i = gap; i < length; i += gap) {
            int current = arr[i];
            int preIndex = i - gap;
            while (preIndex >= 0 && arr[preIndex] > current)
            {
                arr[preIndex + gap] = arr[preIndex];
                preIndex -= gap;
            }
            arr[preIndex + gap] = current;
        }
    }
}

堆排序

堆排序的基本思想是:将待排序序列构造成一个最大堆,此时,整个序列的最大值就是二叉树的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。
最坏,最好,平均时间复杂度:O(nlog2n)
空间复杂度:O(1)

// len:堆的大小
int len;

//  维护最大堆性质
void heapify(int arr[], int i)
{
    //  left:左结点
    //  right:右结点
    //  largest:最大值下标
    int left = i * 2 + 1;
    int right = i * 2 + 2;
    int largest = i;

    if (left < len && arr[left] > arr[largest])
    {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest])
    {
        largest = right;
    }

    //  当根结点变化时,递归维护子树的最大堆性质
    if (largest != i)
    {
        std::swap(arr[i],arr[largest]);
        heapify(arr,largest);
    }
}

//  用数组构建最大堆
void buildMaxHeadp(int arr[], int length)
{
    len = length;

    for (int i = len / 2; i >= 0; --i) {
        heapify(arr, i);
    }
}

//  堆排序
void heapSort(int arr[], int length)
{
    if (arr == nullptr || length <= 0)
        return;

    buildMaxHeadp(arr, length);

    //  每次取最大堆中的root结点,放到数组尾部,并重新维护最大堆性质。
    for (int i = length - 1; i > 0; --i) {
        std::swap(arr[0], arr[i]);
        --len;
        heapify(arr, 0);
    }
}

归并排序

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

最坏,最好,平均时间复杂度:O(nlog2n)
空间复杂度:O(n)

//  归并
//  arr:原数组 res:结果数组    start:下标起始位置
void merge(int arr[], int res[], int start, int mid, int end)
{
    int indexOfA = start;
    int indexOfB = mid + 1;
    int indexOfRes = start;

    while (indexOfA <= mid && indexOfB <= end)
    {
        if (arr[indexOfA] < arr[indexOfB])
        {
            res[indexOfRes++] = arr[indexOfA++];
        }
        else
        {
            res[indexOfRes++] = arr[indexOfB++];
        }
    }

    while (indexOfA <= mid)
        res[indexOfRes++] = arr[indexOfA++];

    while (indexOfB <= end)
        res[indexOfRes++] = arr[indexOfB++];
}

//  归并排序
//  arr:原数组 des:结果数组    length:原数组长度
void mergeSort(int arr[], int des[], unsigned start, unsigned end, unsigned length)
{
    if (arr == nullptr || des == nullptr || start > end)
        return;

    if (start == end)
    {
        des[start] = arr[start];
    }
    else
    {
        //  新建一个临时数组res存放子排序的结果
        int* res = (int*)malloc(sizeof(int) * length);

        if (res != nullptr)
        {
            unsigned mid = (start + end) / 2;
            mergeSort(arr, res, start, mid, length);
            mergeSort(arr, res, mid + 1, end, length);

            //将子排序的结果归并到目标数组中
            merge(res, des, start, mid, end);
        }

        free(res);
    }
}

二 非比较类排序

不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。

计数排序

平均、最好、最坏时间复杂度:O(n+k)
空间复杂度:O(n+k)

#include <iostream>
#include <random>

//  最大年龄
const int oldestAge = 99;

void sortAge(unsigned ages[], int length)
{
    //  检查输入
    if(ages == nullptr || length <= 0)
        return;

    // 定义辅助数组,每个下标记录对应年龄段的人数。
    int timesOfAge[oldestAge + 1];
    for (int i = 0; i <= oldestAge; ++i) {
        timesOfAge[i] = 0;
    }

    for (int j = 0; j < length; ++j) {
        int age = ages[j];
        if (age < 0 || age > oldestAge)
            throw new std::out_of_range("age out of range.");

        ++timesOfAge[age];
    }

    //  计数排序
    int index = 0;
    for (int i = 0; i <= oldestAge; ++i) {
        for (int j = 0; j < timesOfAge[i]; ++j) {
            ages[index] = i;
            ++index;
        }
    }
}


int main() {
    std::default_random_engine e;
    std::uniform_int_distribution<unsigned> u(0,oldestAge);

    unsigned ages[30];
    for (int i = 0; i < 30; ++i) {
        ages[i] = u(e);
        std::cout << ages[i] << ' ';
    }
    std::cout << std::endl;

    sortAge(ages, 30);

    for (int i = 0; i < 30; ++i) {
        std::cout << ages[i] << ' ';
    }
    std::cout << std::endl;

    return 0;
}

桶排序

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

平均时间复杂度:O(n+k)
最好时间复杂度:O(n)
最坏时间复杂度:O(n2)
空间复杂度:O(n+k)其中k为桶的数量

//  桶排序
void bucketSort(int arr[], int length, unsigned bucketSize)
{
    if (arr == nullptr || length <= 0)
        return;

    //  通过数组的最大值、最小值来设置桶的数量与大小
    int minValue = arr[0];
    int maxValue = arr[0];

    for (int i = 1; i < length; ++i) {
        if (arr[i] < minValue) {
            minValue = arr[i];                // 输入数据的最小值
        } else if (arr[i] > maxValue) {
            maxValue = arr[i];                // 输入数据的最大值
        }
    }

    //  桶大小 默认是5
    unsigned DEFAULT_BUCKET_SIZE = 5;
    bucketSize = bucketSize || DEFAULT_BUCKET_SIZE;

    //  桶数量
    unsigned bucketCount = (maxValue - minValue) / bucketSize + 1;

    //  构造桶
    std::vector<std::list<int>> buckets;
    for (int i = 0; i < bucketCount; ++i) {
        buckets.push_back(std::list<int>());
    }

    //  将数组中的数据插入桶中
    for (int i = 0; i < length; ++i) {
        int index = arr[i] / bucketSize;
        buckets.at(index).push_back(arr[i]);
    }

    //  从桶中取出数据 start:arr数组起始下标
    int start = 0;
    for (auto bucket : buckets) {
        bucket.sort();
        for (auto item: bucket) {
            arr[start++] = item;
        }
    }
}

基数排序

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

平均时间复杂度:O(n * k)
最好时间复杂度:O(n * k)
最坏时间复杂度:O(n * k)
空间复杂度:O(n+k)其中k为桶的数量

/*
 * 对数组按照个、十、百、千等进制位进行桶排序
 *
 * 参数说明:
 *     arr -- 数组
 *     length -- 数组长度
 *     exp -- 进制位。
 *
 * 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
 *    (01) 当exp=1表示按照"个位"对数组a进行排序
 *    (02) 当exp=10表示按照"十位"对数组a进行排序
 *    (03) 当exp=100表示按照"百位"对数组a进行排序
 *    ...
 */
void bucketSortByDigit(int arr[], int length, int exp)
{
    // res:排序后的结果,临时变量,最后赋值给arr
    int *res = (int *)malloc(sizeof(int) * length);

    //  固定桶的数量为10,分别统计0,1,2,3,... 9出现的次数
    int buckets[10] = {0};

    //  桶排序
    for (int i = 0; i < length; ++i) {
        ++buckets[(arr[i] / exp) % 10];
    }

    // 让每一个桶中分布的数,对应原数组中的数组下标
    for (int i = 1; i < 10; ++i) {
        buckets[i] += buckets[i - 1];
    }

    // 根据buckets中记录的下标,将arr[]重新排序 i必须由大到小
    for (int i = length - 1; i >= 0; --i) {
        res[ buckets[(arr[i] / exp) % 10] - 1 ] = arr[i];

        //  更新buckets[],防止每次从对应的桶中取相同的数据
        --buckets[(arr[i] / exp) % 10];
    }

    // 将res中的排序结果拷贝给arr
    for (int i = 0; i < length; ++i) {
        arr[i] = res[i];
    }
    free(res);
}

//  基数排序
void  radixSort(int arr[], int length)
{
    if (arr == nullptr || length <= 0)
        return;

    //  maxVale:数组最大值
    int maxValue = arr[0];
    for (int i = 1; i < length; ++i) {
        if (arr[i] > maxValue)
            maxValue = arr[i];
    }

    //  从个位开始进行桶排序,直到最大值的最高位
    int exp = 1;
    while (maxValue / exp)
    {
        bucketSortByDigit(arr, length, exp);
        exp *= 10;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值