代码说明
代码是我亲自码的,调试通过的,代码中有算法思想和详细的注释,一目了然。
项目已经上传到我的github:https://github.com/yisun03/sort
项目中还有另外得九种排序算法的c++实现代码以及其思想。
十种排序算法清代如下(附我的blog链接):
1 选择排序:https://blog.csdn.net/weixin_39408343/article/details/107063290
2 插入排序:https://blog.csdn.net/weixin_39408343/article/details/107070155
3 冒泡排序:https://blog.csdn.net/weixin_39408343/article/details/107070658
4 希尔排序:https://blog.csdn.net/weixin_39408343/article/details/107071758
5.1 归并排序递归实现:https://blog.csdn.net/weixin_39408343/article/details/107083607
5.2 归并排序非递归实现:https://blog.csdn.net/weixin_39408343/article/details/107084688
6.1 快速排序递归实现:https://blog.csdn.net/weixin_39408343/article/details/107086104
6.2 快速排序非递归实现:https://blog.csdn.net/weixin_39408343/article/details/107087359
7 堆排序:https://blog.csdn.net/weixin_39408343/article/details/107092851
8 计数排序:https://blog.csdn.net/weixin_39408343/article/details/107094547
9 桶排序:https://blog.csdn.net/weixin_39408343/article/details/107113821
10 基数排序:https://blog.csdn.net/weixin_39408343/article/details/107115403
术语说明
1、稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
2、非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
3、原地排序:原地排序指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
4、非原地排序:需要利用额外的数组来辅助排序。
5、时间复杂度:一个算法执行所消耗的时间。
6、空间复杂度:运行完一个算法所需的内存大小。
性能分析
时间复杂度:O(k*n) :k为最大值的位数,也就是代码中的digits。
对于基数排序的时间复杂度,也可以说是O(n),忽略掉了常数k,而快速排序的时间复杂度是n(long(n)),那为什么 基数排序的应用没有快速排序广呢,其实一般基数排序中的常数k是不太能忽略的,而且也不是说快排的应用就一 定比基数排序广,这得具体情况具体分析。
空间复杂度:O(m) : m为桶的个数也就是10,这是从个位数开始比较得说法。
稳定的非原地排序
void sort::sort_radix(std::vector<int> &data)
{
// 思想:
// 使用了桶排序中桶的思想,但它比桶排序更精明,它只需要十个桶,因为他的排序思想是分别对元素中
// 的个位,十位,百位....进行排序.
// 也就是说,首先对所有数以个位数的大小进行排序,然后再对所有数以他们的十位数进行排序,依次类推.
// 在整个过程中会使得原始序列逐渐趋近有序,待将最高位排完之后完全有序.
// 想想为什么是从个位开始而不是从最高位开始呢,按道理从最高位开始的话每次都能得出一部分数的正确大小关系.
// 确实可以从最高位开始,而且可以减少比较次数,但是从最高位开始会有一个致命缺点,那就是在如果从高位开始,在对高位相同的
// 数继续排序时,又需要另外创建十个桶对他们排序,其实也就是说最终的结果就是真多每一个不同的元素都会为它创建一个桶,
// 如果待排序序列有10000个不同的元素,那么从高位开始比较的方法就需要创建10000个桶,而从个位开始比较的方法可以重复使用
// 那10个桶,如果序列个数更多那么这样的性能差异就更明显,所以虽然减少了比较次数但浪费了非常多的空间,得不偿失.
// 所以我们说基数排序的话都默认的是从个位开始比较的.
if(data.size() < 2)
{
return;
}
// 遍历待排序序列获取最大值,并计算出最大值的位数digits,这决定我们要循环排序的次数.
int length = static_cast<int>(data.size());
int max = data.at(0);
for(int i = 1; i < length; i++)
{
if(data.at(i) > max)
{
max = data.at(i);
}
}
// 计算最大值位数.
int digits = 1;
while(max/10 > 0)
{
++digits;
max /= 10;
}
// 创建10个桶,因为需要频繁地往桶里面插入元素,所以我们使用list容器,将十个桶放入vector中.
std::vector<std::list<int>> bucket_list;
bucket_list.resize(10);
// 从个位开始进行每一趟的排序,其实就是将待排序序列的元素放入当前排序位数
// (个/十/百...)数字对应的桶中.
for(int i = 1; i <= digits; i++)
{
for(int j = 0; j < length; j++)
{
// 计算出当前元素data.at(j)在本轮属于哪一个桶.
// pow()函数需要include<cmath>.
int radix = static_cast<int>(data.at(j)/pow(10,i-1)) % 10;
bucket_list.at(radix).push_back(data.at(j));
}
// 每完成一轮便将桶里的元素按顺序合并放入原序列.
int k = 0;
for(int n = 0; n < 10; n++)
{
for(auto value : bucket_list.at(n))
{
data.at(k++) = value;
}
// 同时需要将桶清空以便下一轮的排序.
bucket_list.at(n).clear();
}
}
}