• o(n)线性排序算法


    O(n) 排序算法

    前言

    前面有总结过各类常用的排序算法,但是那些排序算法平均的时间复杂度是O(nlogn),所以我要介绍三种时间复杂度为O(n)的线性时间复杂度的排序算法。

    计数排序

    计数排序利用了哈希的性质,将一个中间数组来记录数值对应的下标,最后查询对应的下标进行放置;

    步骤如下:

    1. 找出待排序的数组中最小和最大值,计算最大和最小值之间的差值;
    2. 计算每个数值出现的次数,接着进行累加计算出数值的位置;
    3. 反向填充数组,根据查询下标找到位置后填充数值;

    实现

    #include <iostream>
    #include <vector>
    using namespace std;
    
    vector<int> counting_sort(vector<int> nums) {
        int max = nums[0], min = nums[0];
        size_t len = nums.size();
        for (size_t i = 1; i < len; i++) {
            if (max < nums[i]) {
                max = nums[i];
            }
            if (min > nums[i]) {
                min = nums[i];
            }
        }
        int k = max - min + 1;
        vector<int> temp(k, 0);
        // 第一步:计算每个数字出现的次数
        for (size_t i = 0; i < len; i++) {
            temp[nums[i] - min] += 1;
        }
        // 第二步:累加
        for (size_t i = 1; i < len; i++) {
            temp[i] += temp[i-1];
        }
        vector<int> result(len, 0);
        // 第三步:将数字放在相应的位置
        for (size_t i = 0; i < len; i++) {
            result[--temp[nums[i] - min]] = nums[i];
        }
        return result;
    }
    
    int main() {
        vector<int> res = counting_sort({10, 9, 8, 7, 6, 5, 4, 3, 2, 1});
        for (auto re : res) {
            cout << re << "  ";
        }
        return 0;
    }
    

    缺点和优点

    利用了哈希的原理,其时间复杂度为n,但是这是用空间复杂度来换的,即便上面有进行过优化,但是面对一个较大值和较小值的数组,其仍然会对空间造成很大的浪费。

    基数排序

    将所有数值在每一位上面进行排序,排序方法利用计数排序的原理;

    步骤:

    1. 计算数值中最大值的位数,用作后面比较的次数;
    2. 计算所有数值在每一位上面的排序,参考计数排序;

    实现

    void redis_sort(vector<int>& nums) {
        int bits = max_bit(nums);
        int len = nums.size();
        vector<int> temp(len, 0), count(10, 0);
        for (int i = 1, redix = 1; i <= bits; i++, redix *= 10) {
            // 注意,每次分配前需要清空计数器
            count.assign(10, 0);
            // 第一步:计算每个数值下标出现的次数
            for (int j = 0; j < len; j++) {
                count[(nums[j]/redix)%10]++;
            }
            // 第二步:累加计算下标
            for (int j = 1; j < 10; j++) {
                count[j] += count[j-1];
            }
            // 第三步:根据bit的下标找到位置来填充
            for (int j = len-1; j >= 0; j--) {
                int k = (nums[j]/redix)%10;
                temp[count[k]-1] = nums[j];
                count[k]--;
            }
            // 第四部:排好序的数组赋值
            for (int j = 0; j < len; j++) {
                nums[j] = temp[j];
            }
        }
    }
    

    缺点和优点

    因为其下标在0-10之间,所以有效的控制了空间复杂度,但是其复杂度较计数排序增加了,明显其时间复杂度为O(k * n),k代表数字位数,这取决于数字位的选择,比如比特位数,其决定了要进行多少轮的处理;虽然增加了时间复杂度,但依旧比那些需要进行比较的排序算法较快一些。

    桶排序

    桶排序的原理在于将数组分配到一定数量的桶中,每个桶在个别排序,最后合并排序。

    实现

    const int BUCKET_NUM = 10;
    
    // 链表的插入排序
    LinkNode* insert(LinkNode* head, int val) {
        LinkNode *newhead = new LinkNode(0);
        newhead->_next = head;
    
        LinkNode *node = new LinkNode(val);
        LinkNode *temp = newhead;
        while (temp->_next != NULL && temp->_next->_data <= val) {
            temp = temp->_next;
        }
        node->_next = temp->_next;
        temp->_next = node;
        return newhead->_next;
    }
    
    // 两个排序链表的合并
    LinkNode* merge(LinkNode* head, LinkNode* bucket_node) {
        LinkNode* newhead = new LinkNode(0);
        LinkNode* temp = newhead;
        while (head && bucket_node) {
            if (head->_data > bucket_node->_data) {
                temp->_next = bucket_node;
                bucket_node = bucket_node->_next;
            }
            else {
                temp->_next = head;
                head = head->_next;
            }
            temp = temp->_next;
        }
        if (head != NULL) {
            temp->_next = head;
        }
        else if (bucket_node != NULL) {
            temp->_next = bucket_node;
        }
        return newhead->_next;
    }
    
    vector<int> BucketSort(vector<int> nums) {
        int len = nums.size();
        vector<LinkNode*> buckets(BUCKET_NUM, (LinkNode*)(0));
        // 第一步:对数值进行插入排序
        for (int i = 0; i < len; i++) {
            int idx = nums[i] % BUCKET_NUM;
            LinkNode* head = buckets[idx];
            buckets[idx] = insert(head, nums[i]);
        }
        // 第二步:将桶中的值进行合并
        LinkNode *head = NULL;
        for (int i = 0; i < BUCKET_NUM; i++) {
            head = merge(head, buckets[i]);
        }
        // 第三步:将排序好的链表赋值
        vector<int> result(len, 0);
        for (int i = 0; i < len, head != NULL; i++, head = head->_next) {
            result[i] = head->_data;
        }
        return result;
    }
    

    缺点和优点

    如果数组中的每个数值都会均匀的落入每个桶中,则其最优的时间复杂度在n,但是如果数值都集中的加入到固定的几个桶中,甚至是都落入一个桶中,那么这样在对数值进行插入排序的时候就变成了双层循环,则其最差时间复杂度为n^2。

    比较

  • 相关阅读:
    个人理财小助手 —— 简介
    我的分页控件(未完,待续)——控件件介绍及思路
    静态变量 静态对象 静态函数和非静态函数的区别。(我的理解,大家看看对不对)
    通过“访问多种数据库”的代码来学习多态!(.net2.0版)
    Step By Step 一步一步写网站[1] —— 填加数据
    个人理财小助手 —— 数据库(一)
    几个鸟叫的声音
    Step By Step 一步一步写网站[1] —— 帧间压缩,表单控件
    面向对象相关
    论程序的成长—— 你写的代码有生命力吗?
  • 原文地址:https://www.cnblogs.com/George1994/p/6866965.html
Copyright © 2020-2023  润新知