计数排序(Counting Sort)、基数排序(Radix Sort)、桶排序(Bucket Sort)适合对一定范围内的整数进行排序,它们是典型的用空间换时间,在某些时候,平均时间复杂度可以比O(nlogn)更低。
计数排序(Counting Sort)
核心思想:统计每个整数在序列中出现的次数,进而推导出每个整数在序列中索引
第一步:找到数组中的最大、最小值
id minObject = self.sortArray[0];
id maxObject = self.sortArray[0];
// 找到最大最小值
for (NSInteger i = 1; i < self.sortArray.count; i++) {
if ([self compareObjcet:self.sortArray[i] anotherObject:minObject]) {
minObject = self.sortArray[i];
}
if ([self compareObjcet:maxObject anotherObject:self.sortArray[i]]) {
maxObject = self.sortArray[i];
}
}
第二步:统计每个元素出现的次数
// 构造初始均为0的计数数组
NSInteger begain = ((Student *)minObject).age;
NSInteger end = ((Student *)maxObject).age;
NSMutableArray *counts = [NSMutableArray array];
for (NSInteger index = begain; index <= end; index++) {
[counts addObject:@(0)];
}
// 统计每个元素出现的次数
for (NSInteger i = 0; i < self.sortArray.count; i++) {
Student *s = self.sortArray[i];
NSInteger count = [counts[s.age - begain] integerValue];
count++;
counts[s.age - begain] = @(count);
}
第三步:将统计数组中元素出现次数的数组改造成记录包含数据出现的次数数组,那么当前该次数减1即为该数组应该存放的下标。
for (NSInteger i = 1; i < counts.count; i++) {
counts[i] = @([counts[i] integerValue] + [counts[i - 1] integerValue]);
}
第四步:遍历待排序数组,把各个元素放到对应位置。
// 倒叙遍历原数组 其目的是为了保证稳定性 和 相同基数下的有序性
NSMutableArray *newArray = [NSMutableArray arrayWithArray:self.sortArray];
for (NSInteger i = self.sortArray.count - 1; i >= 0; i--) {
Student *s = self.sortArray[i];
NSInteger count = [counts[s.age - begain] integerValue];
newArray[--count] = s;
counts[s.age - begain] = @(count);
}
for (NSInteger i = 0; i < newArray.count; i++) {
self.sortArray[i] = newArray[i];
}
小结:代码中其实已经做了相应的优化,最基础的版本第一步不计算最小值,从0开始。没有第三步,存入的位置即为排序整数的值。然后遍历新数组中值不为0的放入原数组即为排列好的数组。
基数排序(Radix Sort)
适用范围:正整数,或者可以提供正整数排序的类(对象)
核心思想(以整数为例):依次对数组中元素的个位十位百位千位进行计数排序。
这种方式有效的降低了空间复杂度。
第一步:基于基数的计数排序
因为已知是十进制排序,那么计数数组肯定是0~9 十个数字。
- (void)sortArrayByRadix:(NSInteger)radix
{
// 构造初始均为0的计数数组
NSMutableArray *counts = [NSMutableArray array];
for (NSInteger index = 0; index < 10; index++) {
[counts addObject:@(0)];
}
// 统计每个元素出现的次数
for (NSInteger i = 0; i < self.sortArray.count; i++) {
Student *s = self.sortArray[i];
NSInteger count = [counts[(s.age / radix) % 10] integerValue];
count++;
counts[(s.age / radix) % 10] = @(count);
}
for (NSInteger i = 1; i < counts.count; i++) {
counts[i] = @([counts[i] integerValue] + [counts[i - 1] integerValue]);
}
// 倒叙遍历原数组 其目的是为了保证稳定性 和 相同基数下的有序性 也是为什么要按个位、十位、百位依次排序而不能倒过来的原因
NSMutableArray *newArray = [NSMutableArray arrayWithArray:self.sortArray];
for (NSInteger i = self.sortArray.count - 1; i >= 0; i--) {
Student *s = self.sortArray[i];
NSInteger count = [counts[(s.age / radix) % 10] integerValue];
newArray[--count] = s;
counts[(s.age / radix) % 10] = @(count);
}
for (NSInteger i = 0; i < newArray.count; i++) {
self.sortArray[i] = newArray[i];
}
}
第二步:计算出最大值,确定按基数排序到的位数
- (void)sort
{
id maxObject = self.sortArray[0];
// 找到最大最小值
for (NSInteger i = 1; i < self.sortArray.count; i++) {
if ([self compareObjcet:maxObject anotherObject:self.sortArray[i]]) {
maxObject = self.sortArray[i];
}
}
for (NSInteger radix = 1; radix <= ((Student *)maxObject).age; radix *= 10) {
[self sortArrayByRadix:radix];
NSLog(@"%@",self.sortArray);
}
}
小结:基数排序中重要的一点就是必须按照从低位到高位的顺序依次排序,而不能反过来,其原因就是在相同基数下,保证其低于当前位的数据是有序的。(这也是代码中最后一步,需要倒序排序的关键)
桶排序(Bucket Sort)
执行流程:
①创建一定数量的桶(比如用数组、链表作为桶)
②按照一定的规则,将序列中的元素分配到对应的桶中
③分别对桶进行单独排序
④将所有非空桶的元素合并成有序序列
通过对执行流程的描述可以看出,计数排序和基数排序都类似于特殊情况的桶排序。此处就不再做太多陈述了。