基数排序算法(图文并茂,Java实现)
基数排序主要是利用多个关键字进行排序,在日常生活中,扑克牌就是一种多关键字的排序问题。扑克牌有 4 种花色,即红桃、方块、梅花和黑桃,每种花色从 A 到 K 共 13 张牌。这 4 种花色就相当于 4 个关键字,而每种花色从 A 到 K 就相当于对不同的关键字进行排序。
基数排序正是借助这种思想,对不同类的元素进行分类,然后对同一类元素进行排序,通过这样的过程完成对元素序列的排序。在基数排序中,通常将对不同元素的分类称为分配,排序的过程称为收集。
基数排序具体算法思想是,假设第 i 个元素 ai 的关键字为 keyi,keyi 是由 d 位十进制数组成的,即 keyi=kidkid-1…ki1,其中 ki1 为最低位,kid 为最高位。关键字的每一位数字都可作为一个子关键字。首先将元素序列按照最低的关键字进行排序,然后从低位到高位直到最高位依次进行排序,这样就完成了排序过程。
例如,一组元素序列的关键字为 (334,45,21,467,821,562,342,45)。这组关键字最多为 3 位,在排序之前,首先将所有的关键字都看作一个由 3 位数字组成的数,即(324,285,021,467,821,562,342,045)。
对这组关键字进行基数排序需要进行 3 趟分配和收集:
一般情况下,可采用链表实现基数排序。对最低位进行分配和收集的过程如下图所示。其中,数组 f[i] 保存第 i 个链表的头指针,数组 r[i] 保存第 i 个链表的尾指针。

图 1 第一趟分配和收集的过程
对十位数字分配和收集的过程如下图所示:

图 2 第二趟分配和收集的过程
对百位数字分配和收集的过程如下图所示:

图 3 第三趟分配和收集的过程
经过第一趟排序,即将个位数字作为关键字进行分配后,关键字被分为 10 类,个位数字相同的数字被划分为一类,然后对分配后的关键字进行收集,得到以个位数字非递减排序的序列。
同理,经过第二趟分配和收集后,得到以十位数字非递减排序的序列。经过第三趟分配和收集后,得到最终的排序结果。
基数排序的算法主要包括分配和收集。链表类型描述如下:
基数排序的分配算法实现如下:
其中,数组 f[j] 和 r[j] 分别存放第 j 个子表的第一个元素的位置和最后一个元素的位置。基数排序的收集算法实现如下:
基数排序通过多次调用分配算法和收集算法来实现排序,其算法实现如下:
容易看出,基数排序需要 2radix 个队列指针,分别指向每个队列的队头和队尾。假设待排序的元素为 n 个,每个元素的关键字为 d 个,则基数排序的时间复杂度为 O(d×(n+radix))。
基数排序正是借助这种思想,对不同类的元素进行分类,然后对同一类元素进行排序,通过这样的过程完成对元素序列的排序。在基数排序中,通常将对不同元素的分类称为分配,排序的过程称为收集。
基数排序具体算法思想是,假设第 i 个元素 ai 的关键字为 keyi,keyi 是由 d 位十进制数组成的,即 keyi=kidkid-1…ki1,其中 ki1 为最低位,kid 为最高位。关键字的每一位数字都可作为一个子关键字。首先将元素序列按照最低的关键字进行排序,然后从低位到高位直到最高位依次进行排序,这样就完成了排序过程。
例如,一组元素序列的关键字为 (334,45,21,467,821,562,342,45)。这组关键字最多为 3 位,在排序之前,首先将所有的关键字都看作一个由 3 位数字组成的数,即(324,285,021,467,821,562,342,045)。
对这组关键字进行基数排序需要进行 3 趟分配和收集:
- 首先需要对该关键字序列的最低位进行分配和搜集;
- 然后对十位数字进行分配和收集;
- 最后对最高位数字进行分配和收集。
一般情况下,可采用链表实现基数排序。对最低位进行分配和收集的过程如下图所示。其中,数组 f[i] 保存第 i 个链表的头指针,数组 r[i] 保存第 i 个链表的尾指针。

图 1 第一趟分配和收集的过程
对十位数字分配和收集的过程如下图所示:

图 2 第二趟分配和收集的过程
对百位数字分配和收集的过程如下图所示:

图 3 第三趟分配和收集的过程
经过第一趟排序,即将个位数字作为关键字进行分配后,关键字被分为 10 类,个位数字相同的数字被划分为一类,然后对分配后的关键字进行收集,得到以个位数字非递减排序的序列。
同理,经过第二趟分配和收集后,得到以十位数字非递减排序的序列。经过第三趟分配和收集后,得到最终的排序结果。
基数排序的算法主要包括分配和收集。链表类型描述如下:
class SListCell // 链表的结点类型 { String key[]; // 关键字 int next; final int MaxKeyNum=20; SListCell(int keynum) { key = new String[keynum]; // 关键字 next = -1; } SListCell() { key = new String[MaxKeyNum]; // 关键字 next = -1; } } public class RadixSort { SListCell data[]; int keynum; int length; RadixSort(int keynum, int length) { this.length = length; // 链表的当前长度 data = new SListCell[length + 1]; // 存储元素,data[0] 为头结点 this.keynum = keynum; // 每个元素的当前关键字个数 for (int i = 0; i <= length; i++) { data[i] = new SListCell(length + 1); for (int j = 0; j < keynum; j++) { data[i].key[j] = new String(); } } } }
基数排序的分配算法实现如下:
public void Distribute(int i, int f[], int r[], int radix) // 为 data 中的第 i 个关键字 key[i] 建立 radix 个子表,使同一子表中的元素的 key[i] 相同 // f[0···radix - 1] 和 r[0···radix - 1] 分别指向各个子表中第一个元素和最后一个元素 { for(int j=0; j<radix; j++) // 将各个子表初始化为空表 f[j] = 0; int p = data[0].next; while(p != 0) { int j = Integer.parseInt(data[p].key[i]); // 将对应的关键字字符转换为整数类型 if (f[j] == 0) // 若 f[j] 是空表,则 f[j] 指示第一个元素 f[j] = p; else data[r[j]].next = p; r[j] = p; // 将 p 所指的结点插入第 j 个子表中 p = data[p].next; } }
其中,数组 f[j] 和 r[j] 分别存放第 j 个子表的第一个元素的位置和最后一个元素的位置。基数排序的收集算法实现如下:
public void Collect(int f[], int r[], int radix) // 按 key[i] 将 f[0···Radix - 1] 所指的各个子表依次链接成一个链表 { int j = 0; while(f[j] == 0) // 找第一个非空子表 j++; data[0].next = f[j]; // data[0].next 指向第一个非空子表中的第一个结点 int t = r[j]; while(j<radix - 1) { j++; while (j < radix - 1 && f[j] == 0) // 找下一个非空子表 j++; if (f[j] != 0) // 将非空链表链接在一起 { data[t].next = f[j]; t = r[j]; } } data[t].next = 0; // t 指向最后一个非空子表中的最后一个结点 }
基数排序通过多次调用分配算法和收集算法来实现排序,其算法实现如下:
public void RadixSort(int radix) // 对 L 进行基数排序,使得 L 成为按关键字非递减的链表,L.r[0] 为头结点 { int f[] = new int[radix]; int r[] = new int[radix]; for(int j=0; j<radix; j++) // 将各个子表初始化为空表 f[j]=0; for(int j=0; j<radix; j++) // 将各个子表初始化为空表 r[j]=0; for(int i=0; i<keynum; i++) // 由低位到高位依次对各关键字进行分配和收集 { Distribute(i, f, r, radix); // 第 i 趟分配 Collect(f, r, radix); // 第 i 趟收集 System.out.print("第"+(i+1)+"趟收集后:"); PrintList2(); } }
容易看出,基数排序需要 2radix 个队列指针,分别指向每个队列的队头和队尾。假设待排序的元素为 n 个,每个元素的关键字为 d 个,则基数排序的时间复杂度为 O(d×(n+radix))。