代码说明
代码是我亲自码的,调试通过的,代码中有算法思想和详细的注释,一目了然。
项目已经上传到我的github:https://github.com/yisun03/sort
项目中近期会陆续更新十种排序算法的c++实现代码以及其思想。
术语说明
1、稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
2、非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
3、原地排序:原地排序指在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
4、非原地排序:需要利用额外的数组来辅助排序。
5、时间复杂度:一个算法执行所消耗的时间。
6、空间复杂度:运行完一个算法所需的内存大小。
性能分析
时间复杂度:代码中1,2,4要遍历原数组,令原数组长度为n,则计算量为3*n;
代码中3需要便利统计容器,最大值和最小值差值为d,所以统计容器长度为d,计算量为d,
所以时间复杂度为O(3*n + d),去掉系数为O(n+d)。
注意:很多人都会说计数排序的时间复杂度为O(n),说性能优于快速排序,但其实并不是,因为差值d一般都不是一个小的数,是不容忽视的,这就是为什么说计数排序时间复杂度优于快速排序但快速排序的应用广的一部分原因。另一部分原因是因为计数排序的缺陷,它只适用于整数序列,而且差值d太大的话空间复杂比较差,该缺陷有一个弥补的排序方法,那就是桶排序,桶排序见我的另一篇blog: (更新中)
空间复杂度:O(d)
稳定的非原地排序
void sort::sort_count(std::vector<int> &data)
{
// 思想:
// 计数排序的思想比较新颖,不再是基于元素之间的比较,而是将待排序序列的元素当作容器的索引,记录索引出现的次数;
// 比如临时容器的a[i] = n,表示元素i出现n次;
// 记录玩出现次数之后,将临时容器从小到大将元素汇总起来,则变为有序;
// 我的方法是有所改进,临时容器记录的不是元素出现的次数,而是记录小于等于该元素的元素个数;
// 这样做的优点是可以保证稳定排序.
// 计数排序的缺点:只能对整数序列进行排序,而且不适合最大元素和最小元素差得很大的情况(为什么呢).
// 1.遍历待排序数组,获取最大和最小元素,并求得插值d.
int max = data.at(0);
int min = data.at(0);
int length = static_cast<int>(data.size());
for(int i = 1; i < length; i++)
{
if(data.at(i) > max)
{
max = data.at(i);
}
if(data.at(i) < min)
{
min = data.at(i);
}
}
int d = max - min;
// 2.创建统计容器,并遍历待排序序列进行统计元素出现的次数.
// 容器元素默认值为0.
std::vector<int> count_data;
count_data.resize(d+1);
for(int i = 0; i < length; i++)
{
// min值作为一个偏移量的角色.
++count_data.at(data.at(i) - min);
}
// 3.改进以实现稳定排序,对统计容器做变形,统计容器元素存的不再是待排序元素出现的次数,
// 而是记录小于等于该索引的元素个数.
int sum = 0;
for(auto &value : count_data)
{
sum += value;
value = sum;
}
// 4.倒序遍历原始待排序序列,从统计容器中找到正确位置输出到结果容器,
// 如果没有第3步,那么就是简单的输出,是非稳定的.
std::vector<int> sorted_data;
sorted_data.resize(data.size());
// 注意:此处的i的类型不能用auto来排段,因为size()返回的类型为unsigned long,又因为是i--,
// 所以如果是用auto的活,这个将是一个死循环,而且基本上会造成std::out_of_range错误.
for(int i = static_cast<int>(data.size() - 1); i >= 0; i--)
{
// 代码有点绕:
// data.at(i)-min是当前元素与最小值的差值,
// 以差值作为count_data的索引值,则count_data.at(data.at(i)-min)代表小于等于当前元素data.at(i)的个数,
// 所以count_data(data.at(i)-min)-1表示当前元素data.at(i)的升序序号.
// 将当前元素data.at(i)放入sorted_data的正确的位置上.
sorted_data.at(count_data.at(data.at(i) - min) - 1) = data.at(i);
// 随后更新统计容器的元素值,这和倒序遍历是实现稳定排序的关键.
--count_data.at(data.at(i)-min);
}
// 5.最后将已排序容器赋给原始序列,排序结束.
data = sorted_data;
}