计数排序:
- 计数排序是通过外设一个数组,以所排列的元素的值做为数组的下标来记录所有等于该值的元素的个数,最后根据需求正反输出
- 特别适用于元素值之间跨度不大的数组的排序(如果元素值之间跨度不大、但元素值特别大的情况可以通过所有元素减去某统一值的方法来削减空间开销)
- 时间复杂度:O(n)
- 空间复杂度:O(x) //x为元素中最大的数
void CountingSort(int ary[], int size, int max) //算法导论上的
{
int *temp = new int[max];
int *result = new int[size];
memset(temp, 0, max * 4);
for (int i = 0; i < size; ++i)
++temp[ary[i]];
for (int i = 1; i < max; ++i)
temp[i] += temp[i - 1];//将等于i的元素的个数加上等于i-1的元素的个数,此循环结束后, //temp[i]存储的就是i在结果中的位置
for (int i = size - 1; i >= 0; --i)//一定要降序,否则排序会失去稳定性,此处的稳定性是指:
{ //对于相同值的元素,彼此的相对位置是不变的
//即在原始数组中先出现的,在结果中也一定先出现
//这一点对包含在基数排序中的计数排序来说是必须的
result[temp[ary[i]] - 1] = ary[i];//temp[ary[i]]-1就是ary[i]在结果中位置的下标
--temp[ary[i]]; //如果有相同元素,则让该元素在结果中的位置向前移动一位
} //以免其他相同值的元素都落在同一下标上
for (int i = 0; i < size; ++i) //将结果复制进原数组
ary[i] = result[i];
delete[]temp;
delete[]result;
}
void CountingSort2(int ary[], int size, int max) //自己写的,不稳定
{
int *temp = new int[max];
memset(temp, 0, max * 4);
for (int i = 0; i < size; ++i)
++temp[ary[i]];
for (int i = 0, j = 0; i < max; ++i) //虽然嵌套了,但实际复杂度应该也为O(n)
if (temp[i])
for (int k = 0; k < temp[i]; ++k)
ary[j++] = i;
delete[]temp;
}
void testCountingSort()
{
int arr[] = { 8,5,4,9,7,3,1,2,6 };
cout << "arr is old :";
for (int i = 0; i < 9; i++)
{
cout << arr[i] << " ";
}
cout << endl;
CountingSort(arr, 9,10); //排序后
cout << "arr is new :";
for (int i = 0; i < 9; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
输出:
结果:
arr is old :8 5 4 9 7 3 1 2 6
arr is new :1 2 3 4 5 6 7 8 9
基数排序
- 基数排序是通过先排序元素的最低有效数字再逐位排序直到最高有效数字来排序的,也就是按照元素每个数位上的数来排序,且顺序是从最低位向最高位;如果元素的位数不同的话,在较短的元素的前面加0来统一位数
- 关于此算法,很重要的一点就是按位排序的算法要“稳定”:
- 对于相同值的元素,彼此的相对位置是不变的
- 即在原始数组中先出现的,在结果中也一定先出现
- 每位上的数字稳定为0~9,所以计数排序(而且是稳定的)尤其合适作为基数排序中的按位排序
- 在一台典型的顺序存取计算机上,有时采用基数排序来对有多个关键字域的记录进行排序,比如日期:年、月、日
- 给定n个d位数,每一位可以取k种可能的值,基数排序算法能以O(d(n+k))的时间正确的对这些数排序
- 给定n个b位数以及任何r<=b,基数排序能在O((b/r)(n+2^r))时间正确的对这些数排序
- 时间复杂度:O(n)
- 空间复杂度:O(x) //x为某一位中最大的数
void RadixSort(int ary[], int digit, int size, int max)
{
int *result = new int[size];
int *temp = new int[max];
int radix = 1;
int t;
for (int i = 0; i < digit; ++i, radix *= 10) //循环元素的位数次
{
memset(temp, 0, max * 4); //每次循环前初始化temp,否则temp的元素值会累加
for (int j = 0; j < size; ++j)
{
t = ary[j] / radix % 10; //得到各个数位上的数
++temp[t];
}
for (int j = 1; j < max; ++j)
temp[j] += temp[j - 1];
for (int j = size - 1; j >= 0; --j)
{
t = ary[j] / radix % 10;
result[temp[t] - 1] = ary[j];
--temp[t];
}
for (int i = 0; i < size; ++i) //将结果复制进原数组
ary[i] = result[i];
}
delete[]result;
delete[]temp;
}
void testRadixSort()
{
int arr[] = { 329,457,657,839,436,720,355,839,220,653 };
cout << "arr is old :";
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl;
RadixSort(arr, 3,10, 10); //排序后
cout << "arr is new :";
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
结果:
arr is old :329 457 657 839 436 720 355 839 220 653
arr is new :220 329 355 436 457 653 657 720 839 839
桶排序:
- 当输入符合均匀分布时,桶排序就能以线性时间运行
- 桶排序的思想就是把元素的区间划分为n个大小相同的子区间,称为桶;然后将n个数分布到各个桶中去,因为是均匀分布,所以不会出现很多个元素落在同一桶中的情况;为得到结果,先对各个桶中的元素排序,再按顺序将各个桶中的元素输出即可
- 即使输入不符合均匀分布,但只要满足各个桶的尺寸的平方与总的元素数呈线性关系,那么也能以线性时间运行
- 时间复杂度:O(n)
- 空间复杂度:O(n)
void BucketSort(int ary[], int size) //以排序百分制成绩为例
{
vector<int> *bucket = new vector<int>[size + 1];
int index, last;
for (int i = 0; i < size; ++i)
{
index = ary[i] / 10; //计算应该放入哪个桶
bucket[index].push_back(ary[i]);
last = bucket[index].size() - 1; //计算插入前的最后一个元素的下标
while (last >= 0 && bucket[index][last] > ary[i]) //排序
{
swap(bucket[index][last], bucket[index][last + 1]);
--last;
}
}
int k = 0;
for (int i = 0; i < size + 1; ++i) //按桶顺序将各桶中的元素复制进原数组
for (int j = 0; j < bucket[i].size(); ++j)
ary[k++] = bucket[i][j];
delete[]bucket;
}
void testBucketSort()
{
int arr[] = { 0,45,57,89,100,70,35,39,20,53 };
cout << "arr is old :";
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl;
BucketSort(arr, 10); //排序后
cout << "arr is new :";
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
结果:
结果:
arr is old :0 45 57 89 100 70 35 39 20 53
arr is new :0 20 35 39 45 57 53 70 89 100