o(n)线性排序算法
O(n) 排序算法
前言
有总结过各类常用的排序算法,但是那些排序算法平均的时间复杂度是O(nlogn),所以我要介绍三种时间复杂度为O(n)的线性时间复杂度的排序算法。
计数排序
计数排序利用了哈希的性质,将一个中间数组来记录数值对应的下标,最后查询对应的下标进行放置;
步骤如下:
- 找出待排序的数组中最小和最大值,计算最大和最小值之间的差值;
- 计算每个数值出现的次数,接着进行累加计算出数值的位置;
- 反向填充数组,根据查询下标找到位置后填充数值;
实现
#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,但是这是用空间复杂度来换的,即便上面有进行过优化,但是面对一个较大值和较小值的数组,其仍然会对空间造成很大的浪费。
基数排序
将所有数值在每一位上面进行排序,排序方法利用计数排序的原理;
步骤:
- 计算数值中最大值的位数,用作后面比较的次数;
- 计算所有数值在每一位上面的排序,参考计数排序;
实现
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。
比较
相关推荐
randy0 2020-11-17
lixiaotao 2020-10-07
美丽的泡沫 2020-09-08
nongfusanquan0 2020-08-18
hang0 2020-08-16
earthhouge 2020-08-15
算法改变人生 2020-07-28
troysps 2020-07-19
Broadview 2020-07-19
chenfei0 2020-07-18
风吹夏天 2020-07-07
yangjingdong00 2020-07-05
数据与算法之美 2020-07-05
shawsun 2020-07-04
数据与算法之美 2020-07-04
要知道时间复杂度只是描述一个增长趋势,复杂度为O的排序算法执行时间不一定比复杂度为O长,因为在计算O时省略了系数、常数、低阶。实际上,在对小规模数据进行排序时,n2的值实际比 knlogn+c还要小。
Evankaka 2020-07-04
田有朋 2020-06-28