基数排序 (Radix Sort)-20230715
- 前言
基数排序适用于多关键字排序,与前述的比较排序不同,实现基数排序不需要对关键字进行比较和移动。简而言之,基数排序是一类借助多关键字排序的思想对单逻辑关键字实现排序的方法。
- 多关键字排序
先看一个具体的例子。以扑克牌的排序为例,一般扑克包含52张牌,每张牌都有两个关键字花色和面值,且定义“花色”的地位高于“面值”,在比较任意两张牌的大小时,可以先比较“花色”,若花色相同,再比较面值的大小。通常打牌的时候采用的办法是最高位优先(MSD=Most Significant Digit first),先按照不同的花色分为有序的4堆,然后分别对每一堆按照面值的大小整理有序。
也可以采用另一种办法LSD: 先按不同的“面值”分为13堆,然后将这13堆按照面值的自小到大堆垛在一起(3在2之上,4在3之上,最上面是4张A),然后将这副牌点到过来重新按照花色顺序分为4堆,最后将这4堆牌自小到大合在一起(按照花色),同样可以得到一副满足上述关系的牌。这两种整理扑克的方法便是两种多关键字的排序方法。
再看一个整数排序的具体例子。对3位正整数进行计数排序,正整数的基为[0…9],如果LSD采用最低位优先的方法,那么我们需要四次迭代就可以把整体的序列排好序,对每位上数字的排序,可以选择任何稳定的排序方法,约定成俗的方法是计数排序法(counting sort),之所以选择计数排序,是因为对于大数据序列, 基(radix)很多都重复,执行计数排序能最大限度提升效率。下图引用自《算法导论》,对7个3位整数从低位到高位进行排序,可以观察到三轮迭代后,整个数组序列呈现有序状态。
读者可以发现,最低位优先的基数排序,在迭代过程中,数组序列保持为一体,无需执行分割后合并的操作流程,实现代码简洁易懂。
也可以采用另一种办法MSD,从直觉上理解而言,先从最重要的位着手,似乎看上去更容易实现排序,因为数字的高位最数值大小起决定作用。仔细对MSD过程进行分析,就会发现,MSD的基数排序过程中将伴随着额外的数据堆(数组)的产生,每一次分割后,最高位上基数相同的数据归为一组,那么一组数据最多就可以分为Radix个子数组,子数组遵从相同逻辑,每个子数组也可能产生radix个子数组的子数组,分析至此,不难发现,如果采用MSD的基数排序,就需借助递归模型,递归终止条件为子数组中有且仅有一个数据元素。对上面相同的数组进行MSD的基数排序,观察子数组的产生过程和数量。
从树状图中观察,第一次递归后,产生四个子数组;其中两个子树中包含的数量大于1,继续进行递归循环,第二次递归过程中,子数组分裂为单个原子元素。
- 算法实现
基数排序LSD算法的藉由静态链表或者计数排序方法实现,而MSD算法可以由链表或者数组实现。下面分别就LSD算法和MSD算法的实现分别进行解释。
假定要对正整数进行排序,那么它的基为 [0…9],实现算法的伪代码记作:
RADIX-SORT(A, n, d)
{
for i=1 to d
{
Use a stable sort method to sort array A[1:n] on digit i
}
}
基数排序算法非常简单直接,A[i]中元素包含d个基数,其中第1位是最低位,第 d位是最高位。约定成俗,按照counting sort的稳定排序方式对第i位进行排序。
3.1 基于计数法的LSD基数排序(radix sort)
其中counting_sort函数的作用是对第pass趟对应的位进行排序,值得一提的是,对于临时数组temp,由于累加计数的原因,它的下标起始位置位index=1,所以返回的值位temp+1,忽略掉下标index=0的位置。在radix_sort函数中,特别注意的是free函数必须整个区域释放堆,释放局部内存会导致程序无法运行,所以free(ptr-1)而非free(ptr),ptr-1对应的是temp开辟的整体内存。
值得学习的另外一个技巧是,通过附加变量保存,在后续的循环中及时释放堆内存。
/**
* @file radix_sort.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-07-11
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_C
#define RADIX_SORT_C
#include "radix_sort.h"
int *counting_sort(int *arr, int n, int pass)
{
int *temp;
int *count;
int i;
int j;
int div;
temp=(int *)malloc(sizeof(int)*(n+1));
count=(int *)malloc(sizeof(int)*K);
memset(temp,0,sizeof(int)*(n+1));
memset(count,0,sizeof(int)*K);
for(div=1,i=1;i<pass;i++)
{
div*=10;
}
for(i=0;i<n;i++)
{
count[arr[i]/div%K]++;
}
for(j=1;j<K;j++)
{
count[j]=count[j]+count[j-1];
}
for(i=n-1;i>=0;i--)
{
temp[count[arr[i]/div%K]]=arr[i];
count[arr[i] / div % K]--;
}
return (temp+1); //temp[0] has no use in the program
}
void radix_sort(int *arr, int n, int d)
{
int *ptr;
int *target_ptr;
int i;
for(ptr=arr,i=1;i<=d;i++)
{
target_ptr=counting_sort(ptr,n,i);
if(ptr!=arr) //arr belongs to stack in the main program, you can't release during operation
{
free(ptr-1); //free full memory block, it should be complete instead of part
}
ptr=target_ptr;
}
memcpy(arr,ptr,sizeof(int)*n);
return;
}
void display_arr(int *arr, int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
#endif
3.2 基于静态链表的LSD基数排序(《数据结构》严蔚敏,清华大学)
首先以静态链表储存n个待排记录,并令表头指针指向第一个记录,如图(a)表示;第一趟分配(distribute)对最低位数的关键字进行,改变记录的指针值,将链表中的记录分配至10个链队列中去,每个队列(链表)中的记录关键字的个位数相等,如(b)所示,其中f[i]和e[i]分别为第i个队列(链表)的头指针和尾指针;第一趟收集改变所有非空对垒(链表)的队尾记录的指针域,令其指向下一个非空队列的对头记录,重新将10个队列(链表)中记录联成一个链表,如图©所示。第二趟分配,第二趟收集及第三趟分配和第三趟收集分别对十位数和百位数进行的,其过程和个位数相同,如图(d)~(g)所示。
代码实现为,
头文件radix_sort.h
/**
* @file radix_sort.h
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-06-29
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_H
#define RADIX_SORT_H
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define EQ(a,b) ((a)==(b))
#define LT(a,b) ((a)<(b))
#define LQ(a,b) ((a)<=(b))
//MAX size of record is 20
#define MAX_LEN 15
#define MAX_SIZE 20
#define RADIX 10
#define MAX_NUM_OF_KEY 8
#define MAX_SPACE 100
typedef char KeysType;
typedef char* Record;
typedef int ArrType[RADIX];
typedef struct SLCell
{
KeysType keys[MAX_NUM_OF_KEY];
Record otheritems;
int next;
} SLCell;
typedef struct SLList
{
SLCell r[MAX_SPACE];
int keynum; //number of keys in each record
int recnum; //number of records in the static linked list
} SLList;
/**
* @brief create the static linked list from the file
*
* @param fp File pointer
* @param list SLList pointer
*/
void create_list(FILE *fp, SLList *list);
/**
* @brief It builds the linked list based on the the ith key
* make sure the keys[i] is the same in the same static linked list
*
* @param r Records collections for SLCell
* @param i ith key
* @param f f points to the first record in each static link list
* @param e e points to the last record in each static link list
*/
void sort_distribute(SLCell *r,int i, ArrType f, ArrType e);
/**
* @brief collect(string) all linked list in the sort of keys[i] list
*
* @param r Records collections for SLCell
* @param i ith key
* @param f f points to the first record in each static link list
* @param e e points to the last record in each static link list
*/
void sort_collect(SLCell *r, int i, ArrType f, ArrType e);
/**
* @brief Use static linked list to order the element in the list
*
* @param list Pointer to static linked list
*/
void radix_sort(SLList *list);
/**
* @brief Find the successor of the current element
*
* @param j Index of current element
* @return int Index of successor
*/
int find_successor(int j);
/**
* @brief Get the ordinal of the object
*
* @param ch keys[i]
* @return int Ordinal of the object
*/
int get_ord(char ch);
void display_list(SLList list);
#endif
函数实现radix_sort.c
/**
* @file radix_sort.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-06-29
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_C
#define RADIX_SORT_C
#include "radix_sort.h"
void create_list(FILE *fp, SLList *list)
{
int i;
int n;
char str[MAX_LEN];
n=0;
while(fgets(str,MAX_LEN,fp)!=NULL)
{
n++;
}
fseek(fp,0,SEEK_SET); //move the FILE pointer to the start position
list->recnum=n;
list->keynum=3;
for(i=1;i<=n;i++)
{
list->r[i].otheritems = (Record)malloc(sizeof(char) * MAX_LEN);
memset(list->r[i].otheritems, 0, sizeof(char) * MAX_LEN);
memset(list->r[i].keys, 0, MAX_NUM_OF_KEY*sizeof(char));
fscanf(fp, "%s %s",list->r[i].keys,list->r[i].otheritems);
}
return;
}
void sort_distribute(SLCell *r, int i, ArrType f, ArrType e)
{
int j;
int p;
for(j=0;j<RADIX;j++)
{
f[j]=0;
}
for(p=r[0].next;p;p=r[p].next)
{
j=get_ord(r[p].keys[i]);
if(!f[j])
{
f[j]=p;
}
else
{
r[e[j]].next=p; //link the array of r[e[j]].next
}
e[j]=p;
}
}
void sort_collect(SLCell *r, int i, ArrType f, ArrType e)
{
int t;
int j;
for(j=0;!f[j];j=find_successor(j));
r[0].next=f[j];
t=e[j];
while(j<RADIX)
{
for(j=find_successor(j); j<(RADIX-1) && !f[j];j=find_successor(j));
if(j<RADIX && f[j])
{
r[t].next=f[j];
t=e[j];
}
}
r[t].next=0;
return;
}
void radix_sort(SLList *list)
{
int i;
ArrType f;
ArrType e;
for(i=0;i<list->recnum;i++)
{
list->r[i].next=i+1;
}
list->r[list->recnum].next=0;
for(i=list->keynum-1;i>=0;i--)
{
sort_distribute(list->r,i,f,e);
sort_collect(list->r,i,f,e);
}
}
int find_successor(int j)
{
return (j+1);
}
int get_ord(char ch)
{
return (ch-'0');
}
void display_list(SLList list)
{
int p;
p=list.r[0].next;
for (p = list.r[0].next; p; p=list.r[p].next)
{
printf("%s,",list.r[p].keys);
}
printf("\n");
}
#endif
测试函数radix_sort_main.c
/**
* @file radix_sort_main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-06-29
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_MAIN_C
#define RADIX_SORT_MAIN_C
#include "radix_sort.c"
int main(void)
{
FILE *fp;
SLList list;
fp = fopen("data.txt", "r");
create_list(fp, &list);
// printf("The data is shown before the sorting:\n");
//display_list(list);
radix_sort(&list);
printf("The data is shown after the sorting:\n");
display_list(list);
getchar();
fclose(fp);
return EXIT_SUCCESS;
}
#endif
data.txt文件
278 fox
109 the
063 jump
930 out
589 box
184 it
505 looks
269 better
008 or
083 not
3.3 基于动态链表的基数排序(MSD)
最高位优先策略的基数排序会产生不同的组,而且随着排序的推进,更多的分组将被产生。由于分组的数量取决于实际排序的序列具体值,程序无法提前确定,递归算法便应运而生。本例中的待排对象是正整数,递归过程中应用链表进行层层深入递归。 定义结构体中包含固定数组,已经目前数组中包含的实际元素数量,最后定义和基数相同的链表指针数组,示意图进行解释。
头文件定义radix_sort.h
/**
* @file radix_sort.h
* @author your name (you@domain.com)
* @brief https://www.geeksforgeeks.org/msd-most-significant-digit-radix-sort/
* @version 0.1
* @date 2023-07-15
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_H
#define RADIX_SORT_H
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define N 100
#define RADIX 10
int len=0;
typedef struct link_node
{
int data[N];
int size;
struct link_node *next[RADIX];
}link_node;
/**
* @brief Use radix sort to sort the array
*
* @param arr Target array
* @param n Number of elements in the array
*/
void radix_sort(int *arr, int n);
/**
* @brief Recursively sort the array by using radix sort method
*
* @param root Root pointer
* @param exp Exponent
* @param sorted_arr Sorted array
*/
void radix_sort_msd(link_node *root,int exp, int *sorted_arr);
/**
* @brief Create a new node object
*
* @return link_node* -Return the pointer to link_node
*/
link_node *create_new_node();
/**
* @brief Find the max number from the array
*
* @param arr Array
* @param n Number of element in the array
* @return int
*/
int find_max(int *arr, int n);
/**
* @brief Work out the exponent on the basis of max. number
*
* @param max Max value
* @return int -Return exponent value
*/
int calc_exp(int max);
/**
* @brief display element of array
*
* @param arr
* @param n
*/
void display_arr(int *arr, int n);
#endif
函数功能实现
/**
* @file radix_sort.c
* @author your name (you@domain.com)
* @brief https://www.geeksforgeeks.org/msd-most-significant-digit-radix-sort/
* @version 0.1
* @date 2023-07-15
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_C
#define RADIX_SORT_C
#include "radix_sort.h"
void radix_sort(int *arr, int n)
{
link_node *root;
int i;
int exp;
int sorted_arr[n];
root = create_new_node();
memcpy(root->data, arr, sizeof(int) * n);
root->size = n;
exp = calc_exp(find_max(arr, n));
radix_sort_msd(root, exp, sorted_arr);
memcpy(arr, sorted_arr, sizeof(int) * n);
return;
}
void radix_sort_msd(link_node *root, int exp, int *sorted_arr)
{
if(exp<=0)
{
return; //recursion termination condition 1
}
int i;
int j;
for(i=0;i<root->size;i++)
{
j=root->data[i]/exp %RADIX;
if(root->next[j]==NULL)
{
root->next[j]=create_new_node();
}
root->next[j]->data[root->next[j]->size++]=root->data[i];//from the smallest
}
for(i=0;i<RADIX;i++)
{
if(root->next[i]!=NULL)
{
if(root->next[i]->size>1)
{
radix_sort_msd(root->next[i],exp/10,sorted_arr);
}
else //recursion termination condition 2
{
sorted_arr[len++]=root->next[i]->data[0];
}
}
}
}
link_node *create_new_node()
{
link_node *temp_node;
temp_node = (link_node *)malloc(sizeof(link_node));
memset(temp_node, 0, sizeof(link_node)); // set all bit as zero/null
return temp_node;
}
int find_max(int *arr, int n)
{
int i;
int max;
max=*(arr+0);
for(i=1;i<n;i++)
{
if(max<arr[i])
{
max=arr[i];
}
}
return max;
}
int calc_exp(int max)
{
int exp;
exp=1;
while(max>RADIX)
{
max/=RADIX;
exp*=10;
}
return exp;
}
void display_arr(int *arr, int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d ",arr[i]);
}
printf("\n\n");
}
#endif
测试函数
/**
* @file radix_sort_main.c
* @author your name (you@domain.com)
* @brief https://www.geeksforgeeks.org/msd-most-significant-digit-radix-sort/
* @version 0.1
* @date 2023-07-15
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_MAIN_C
#define RADIX_SORT_MAIN_C
#include "radix_sort.c"
int main(void)
{
int arr[] = {720, 457, 657, 839, 436, 329, 555};
int n = sizeof(arr) / sizeof(int);
printf("The number is listed before sorting:\n");
display_arr(arr, n);
radix_sort(arr, n);
printf("The number is listed after sorting:\n");
display_arr(arr, n);
getchar();
return EXIT_SUCCESS;
}
#endif
3.4 基于数组的基数排序(MSD)
在此不再赘述,直接上实现函数的代码
/**
* @file radix_sort.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2023-07-16
*
* @copyright Copyright (c) 2023
*
*/
#ifndef RADIX_SORT_C
#define RADIX_SORT_C
#include "radix_sort.h"
int find_max(int *arr, int n)
{
int i;
int max;
max=*(arr+0);
for(i=1;i<n;i++)
{
if(max<arr[i])
{
max=arr[i];
}
}
return max;
}
int get_exp(int max)
{
int exp;
exp=1;
while(max>10)
{
max/=RADIX;
exp*=RADIX;
}
return exp;
}
void radix_sort(int *arr, int n)
{
int sorted_arr[n];
int exp;
exp=get_exp(find_max(arr,n));
radix_sort_msd(arr,n,exp,sorted_arr);
memcpy(arr,sorted_arr,sizeof(int)*n);
return;
}
void radix_sort_msd(int *arr, int n, int exp, int *sorted_arr)
{
if(exp<=0) //recursion termination #1
{
return;
}
int i;
int j;
int buckets[RADIX][n];
int counter[RADIX];
memset(buckets, 0, sizeof(buckets));
memset(counter,0,sizeof(counter));
for (i = 0; i < n; i++) // 720, 457, 657, 839, 436, 329, 555
{
j=(arr[i]/exp)%RADIX;
buckets[j][counter[j]++]=arr[i];
}
for (i = 0; i < RADIX; i++) // 720, 457, 657, 839, 436, 329, 555
{
if(counter[i]!=0)
{
if(counter[i]>1)
{
radix_sort_msd(buckets+i,counter[i],exp/10,sorted_arr);
}
else //recursion termination condition #2
{
sorted_arr[len++]=buckets[i][0];
}
}
}
}
void display_arr(int *arr, int n)
{
int i;
for(i=0;i<n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
#endif
4.小结
本文对基数排序常见的两种方式LSD和MSD进行分析,并通过C语言实现其功能。