板子:计数排序与基数排序
两个都不靠比较元素大小来排序。计数排序先数出每个值出现几次,直接算出每个元素该放在哪里。基数排序从个位开始,每一趟按这一位「分配」到 10 个队列,再按 0~9 的顺序「收集」起来。
计数排序(★★)
void CountSort(int A[], int B[], int n, int k) { // 元素取值在 [0, k);结果放进 B
int *C = (int *)calloc(k, sizeof(int)); // C[x]:先统计 x 出现了几次
for (int i = 0; i < n; i++) C[A[i]]++;
for (int x = 1; x < k; x++) C[x] += C[x - 1]; // 累加以后 C[x]:不大于 x 的元素个数
for (int i = n - 1; i >= 0; i--) { // 从后往前放,保证稳定
B[C[A[i]] - 1] = A[i]; // 不大于 A[i] 的有 C[A[i]] 个,它放在下标 C[A[i]]-1
C[A[i]]--; // 下一个相同的值,放在它前面一格
}
free(C);
}
void cmpCountSort(int a[], int b[], int n) { // 教材试题解析版:两两比较来计数
int *count = (int *)calloc(n, sizeof(int)); // count[i]:比 a[i] 小的元素个数
for (int i = 0; i < n - 1; i++)
for (int j = i + 1; j < n; j++)
if (a[i] <= a[j]) count[j]++; // 带等号:相等时算后面那个大,保证稳定
else count[i]++;
for (int i = 0; i < n; i++) b[count[i]] = a[i]; // count[i] 正好是 a[i] 在结果中的下标
free(count);
}复杂度:CountSort 时间 cmpCountSort 时间
基数排序(★)
void RadixSort(LinkList L, int d) { // 带头结点的单链表;关键字是非负整数,最多 d 位
LNode *front[10], *rear[10]; // 10 个队列(基数 r = 10)的队头、队尾指针
for (int p = 0, base = 1; p < d; p++, base *= 10) { // 从个位开始,一共 d 趟
for (int r = 0; r < 10; r++) front[r] = rear[r] = NULL;
LNode *s = L->next;
while (s != NULL) { // 分配:每个结点按当前这一位,接到对应队列的队尾
LNode *nx = s->next; // 先记下后继,s 马上要被摘下来
int r = s->data / base % 10; // 取出当前这一位
s->next = NULL;
if (front[r] == NULL) front[r] = rear[r] = s;
else { rear[r]->next = s; rear[r] = s; }
s = nx;
}
LNode *tail = L; // 收集:按 0~9 的顺序,把非空队列首尾相接
for (int r = 0; r < 10; r++)
if (front[r] != NULL) {
tail->next = front[r];
tail = rear[r];
}
tail->next = NULL;
}
}复杂度:时间
关键边界
| 代码 | 为什么 |
|---|---|
| 计数排序从后往前放 | 相同的值里,后出现的先放到靠后的位置,原来的相对次序就保持不变,所以稳定 |
B[C[A[i]] - 1] 要减 1 | C[x] 是「不大于 |
cmpCountSort 的 <= | 一个等号决定稳定与否。不带等号的话,相等时前面元素的 count 加 1,它会排到后面去 |
| 基数排序接到队尾,按 0~9 收集 | 每一趟都必须稳定,否则高位那一趟会打乱低位已经排好的次序,结果就错了 |
| 基数排序从个位开始 | 这是最低位优先(LSD)。最后一趟按最高位排,高位相同的元素保持低位已经排好的次序 |
易错点
- 基数排序的空间是
,不是 :链表结点本来就在,额外用的只有 个队列的指针。用数组实现的话,要额外开一个长度为 的数组。 - 基数排序是唯一一个不基于比较的排序(考纲范围内)。计数排序是教材
*选学内容。 - 计数排序只适合值域不大的整数,
很大时空间开销太大。
链接
- 📕 返回:数据结构代码板子
- ⬅️ 上一篇:折半插入排序与希尔排序
- 🔗 值当下标的思路:辅助数组计数与标记
- 🔗 8.5 归并排序、基数排序和计数排序