实现基数排序(数组+链表)

本文介绍了基数排序的原理及其实现过程,通过一个C语言的示例程序展示了基数排序的详细步骤。程序中定义了数据节点结构,实现了获取数字位数的功能,并使用链表作为辅助数据结构进行排序。基数排序通过多次迭代,根据每个数字的每一位进行桶排序,最终得到有序序列。
/*
 * 此程序用于实现基数排序
 * 作者:zhy
 * 时间:2021/7/24
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 用于存放数据的节点
typedef struct inode
{
	int data;
	struct inode *next;
}inode,*pnode;

int getcount(int obj);

int getnum(int obj,int info);

// 基数排序实现函数
void radixsort(int *arr,int len);

// 基数排序子函数
void _radixsort(int *arr,int len,int info);

int main(int argc,const char *argv[])
{
	int arr[]={144,203,738,905,347,215,836,26,527,602,946,504,219,750,848};
	int len=sizeof(arr)/sizeof(int);

	int yy; for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
	radixsort(arr,len);  // 基数排序。

	// 显示排序结果。
	for (yy=0;yy<len;yy++) printf("%2d ",arr[yy]); printf("\n");
	return 0;
}

void radixsort(int *arr,int len)
{
	if (len < 2) return;

	int info = 1;		// 最大的数据有少位
	for (int ii = 0; ii < len;ii++)
	{
		info = info > getcount(arr[ii]) ? info:getcount(arr[ii]);
	}
	printf ("info = %d\n",info);

	for (int ii = 1; ii <= info;ii++)
	{
		_radixsort(arr,len,ii);
	}
}

int getcount(int obj)
{
	int ii = 1;
	while((obj/=10) != 0) ii++;
	return ii;
}

int getnum(int obj,int info)
{
	int a[3] = {0};
	int i = 0;
	while(obj != 0)
	{
		a[i]= obj%10;
		obj = obj/10;
		i = i + 1; 
	}
	return a[info - 1];
}

void _radixsort(int *arr,int len,int info)
{
	// 用于存放数据指针的桶
	pnode element[10];
	memset(element,0,sizeof(pnode)*10);
	int pp = 0;		// 当前元素放入的桶
	for (int ii = 0;ii < len;ii++)
	{
		inode *tmp = (inode*)malloc(sizeof(inode));
		tmp -> data = arr[ii];
		pp = getnum(arr[ii],info); 
		tmp -> next =  element[pp];
		element[pp] = tmp;
	}


	int p = len - 1;		//数组下标

	for(int ii = 9;ii >= 0 ;ii--)
	{
		inode * a = element[ii];
		inode *tmp = a;
		while(a != NULL)
		{
			arr[p--] = a -> data;
			printf("arr[%d] = %d\n",p+1,arr[p+1]);
			if (a-> next != NULL)
			{
				tmp = a -> next;
				free(a);
				a = tmp;
				continue;
			}
			free(a);
			break;
		}
	}
}

<think>我们被要求使用链表实现基数排序。参考引用[3][4]可知,基数排序(桶排序)通过分配收集过程来实现排序,对于整数,我们可以按照每一位数字(从低位到高位)进行分配收集。使用链表可以避免大量的数据移动。 引用[3]提到基数排序的步骤: 1. 得到最大数的位数,确定循环次数。 2. 从最低位开始,按照当前位的值(0-9)将数据分配到对应的桶(链表)中。 3. 按顺序将各个桶中的数据收集起来,形成新的链表。 4. 重复步骤23,直到最高位。 引用[4]提到链式基数排序实现难点:使用f数组(每个桶的头指针)e数组(每个桶的尾指针)来管理每个桶的链表,这样可以高效地将元素链接到桶的末尾。 因此,我们可以这样设计: - 使用一个链表(比如单链表)来存储待排序的数据。 - 对于每一位(从个位开始),我们创建10个桶(0号桶到9号桶),每个桶是一个链表。 - 分配过程:遍历待排序链表,根据当前位的数字将节点放入相应桶的末尾(用尾指针可以高效追加)。 - 收集过程:按顺序(0到9)将每个桶的链表首尾相连,形成新的待排序链表。 - 重复进行,直到处理完最高位。 注意:我们假设排序的是非负整数。如果有负数,需要特殊处理(比如分成负数非负数两部分分别排序,或者使用偏移量)。 根据引用[1]的代码,我们可以知道如何获取最大位数,但这里我们将使用链表操作。 步骤详细: 1. 计算最大位数:遍历链表找到最大值,然后计算最大值的位数(或者用引用[1]的方法,但需要先遍历链表得到数组?实际上我们也可以直接遍历链表求最大值)。 2. 我们使用一个链表来存储数据,然后进行多次分配收集。 3. 分配时,我们创建10个桶(链表),每个桶有一个头指针一个尾指针(可以用两个数组:head[10]tail[10],初始化为空)。 4. 遍历原链表每个节点,计算当前位的数字(比如第k位,k从0开始表示个位),然后将其放入对应的桶中(如果桶为空,则头指针指向该节点;否则,尾指针的next指向该节点,然后更新尾指针)。同时,注意将节点的next置为NULL,因为每个桶内都是独立的。 5. 收集:将0号桶到9号桶依次连接起来(即前一个桶的尾指针的next指向下一个桶的头指针,注意跳过空桶)。这样形成一个新的链表。 6. 重复步骤45,直到所有位都处理完毕。 代码实现思路: 首先,定义链表节点: struct Node { int data; Node* next; }; 然后,实现一个函数计算最大位数: int getMaxDigit(Node *head) { if (head == nullptr) return 0; int maxVal = head->data; Node *p = head->next; while (p) { if (p->data > maxVal) maxVal = p->data; p = p->next; } int d = 0; while (maxVal) { d++; maxVal /= 10; } return d; } 但是注意:如果maxVal为0,则d=0,但实际位数应该是1。所以需要处理0的情况。另外,如果数字都是0,那么最大位数为1。我们可以这样修改: if (maxVal == 0) return 1; while (maxVal) { ... } 或者,我们可以用另一种方法:先求最大值,然后求其位数,注意0的情况。 另一种方法(类似引用[1]): int maxVal = ...; // 最大数 int d = 1, p = 10; while (maxVal >= p) { p *= 10; d++; } return d; 但是注意,如果maxVal=0,那么不会进入循环,返回1。所以这样也可以。 然后,基数排序主函数: Node* radixSort(Node *head) { if (head == nullptr || head->next == nullptr) return head; // 空链表或只有一个节点 // 计算最大位数 int maxVal = getMaxValue(head); int maxDig = 1; int base = 10; while (maxVal >= base) { base *= 10; maxDig++; } // 创建桶的头尾指针数组 Node* bucketHead[10] = {nullptr}; Node* bucketTail[10] = {nullptr}; Node *cur = head; int factor = 1; // 从个位开始 // 进行maxDig次分配收集 for (int i = 0; i < maxDig; i++) { // 分配过程 cur = head; while (cur) { int digit = (cur->data / factor) % 10; // 当前位的数字 // 将当前节点从原链表取下,注意保存下一个节点 Node *next = cur->next; cur->next = nullptr; // 放入对应的桶 if (bucketHead[digit] == nullptr) { bucketHead[digit] = cur; bucketTail[digit] = cur; } else { bucketTail[digit]->next = cur; bucketTail[digit] = cur; } cur = next; } // 收集过程:将桶中的节点按顺序收集起来,形成新的链表 head = nullptr; Node *tail = nullptr; for (int j = 0; j < 10; j++) { if (bucketHead[j] != nullptr) { // 如果当前头指针为空,则设置头指针 if (head == nullptr) { head = bucketHead[j]; tail = bucketTail[j]; } else { tail->next = bucketHead[j]; tail = bucketTail[j]; } // 清空桶 bucketHead[j] = nullptr; bucketTail[j] = nullptr; } } factor *= 10; // 处理下一位 } return head; } 注意:上面的分配过程中,我们每次分配前需要将桶清空(实际上在收集后已经清空了)。另外,在分配时,我们将节点从原链表取下,然后放入桶中。 但是,上面的代码在每次分配前,原链表已经被拆分了,所以cur从head开始,然后head在收集过程中被重新赋值。 另外,我们还需要一个getMaxValue函数来获取链表最大值: int getMaxValue(Node *head) { if (head == nullptr) return 0; int maxVal = head->data; Node *p = head->next; while (p) { if (p->data > maxVal) maxVal = p->data; p = p->next; } return maxVal; } 但是,如果链表中有负数,上面的方法需要调整(因为负数取模会得到负数,而且排序规则也不同)。这里我们假设是非负整数。 下面我们写一个完整的示例,包括创建链表打印链表。 注意:上面的代码在收集桶的时候,我们直接将每个桶的链表连接起来。但是要注意,每个桶的链表在分配时已经保持了原始顺序(即稳定性),所以基数排序是稳定的。 但是,上面的代码在桶内连接时,我们直接使用了桶的尾指针,这样在收集时只需要连接桶的头上一个桶的尾即可。 但是,上面的代码在每一轮分配收集后,最后一个桶的尾指针的next可能不为空(因为在分配时我们将节点的next置为null,然后在桶内连接时又连接了next,所以收集后链表是正常的)。 然而,上面的代码在收集过程中,当连接两个非空桶时,我们将前一个桶的尾指针的next指向当前桶的头,这是正确的。然后更新尾指针为当前桶的尾。这样整个链表就连接好了。 但是,有一个边界情况:当某个桶为空时,我们跳过,所以连接时不会出现问题。 因此,我们按照上面的思路编写代码。 但是,注意:在分配过程中,我们取下了节点,所以原链表已经被破坏,我们通过收集重新形成链表。 下面给出完整代码示例: </think> 以下是使用C++链表实现基数排序的完整方法代码示例,基于引用[3][4]描述的链式基数排序原理: ### 链表实现基数排序原理 1. **核心思想**:按数字的每位(从低位到高位)进行多次分配收集 2. **数据结构**: - 10个桶(0-9)对应每位数字 - 每个桶使用链表存储相同位值的数字 - 使用头指针数组`head`尾指针数组`tail`管理桶 3. **时间复杂度**:$O(d \times n)$($d$为最大位数,$n$为元素数量) 4. **空间复杂度**:$O(n)$(仅需链表指针空间) ### 完整代码实现 ```cpp #include <iostream> using namespace std; // 链表节点结构 struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; // 计算最大位数 int getMaxDigits(Node* head) { int maxVal = 0; for (Node* cur = head; cur; cur = cur->next) { if (cur->data > maxVal) maxVal = cur->data; } int digits = 0; while (maxVal) { digits++; maxVal /= 10; } return digits > 0 ? digits : 1; // 处理全0情况 } // 基数排序主函数 Node* radixSort(Node* head) { if (!head || !head->next) return head; const int BUCKETS = 10; Node* head[BUCKETS] = {nullptr}; // 桶头指针数组 Node* tail[BUCKETS] = {nullptr}; // 桶尾指针数组 int maxDigits = getMaxDigits(head); int factor = 1; // 当前位数因子 for (int d = 0; d < maxDigits; d++) { // 分配过程:按当前位放入桶中 Node* cur = head; while (cur) { int digit = (cur->data / factor) % 10; Node* next = cur->next; if (!head[digit]) { // 桶为空 head[digit] = tail[digit] = cur; } else { // 桶非空 tail[digit]->next = cur; tail[digit] = cur; } cur->next = nullptr; // 断开原链表连接 cur = next; } // 收集过程:连接所有桶 head = nullptr; Node* last = nullptr; for (int i = 0; i < BUCKETS; i++) { if (head[i]) { if (!head) { head = head[i]; last = tail[i]; } else { last->next = head[i]; last = tail[i]; } } // 重置桶指针 head[i] = tail[i] = nullptr; } factor *= 10; // 处理更高位 } return head; } // 打印链表 void printList(Node* head) { while (head) { cout << head->data << " "; head = head->next; } cout << endl; } // 测试示例 int main() { // 创建测试链表: 189 -> 12 -> 445 -> 64 -> 743 Node* head = new Node(189); head->next = new Node(12); head->next->next = new Node(445); head->next->next->next = new Node(64); head->next->next->next->next = new Node(743); cout << "原始链表: "; printList(head); head = radixSort(head); cout << "排序后: "; printList(head); // 释放内存(实际使用需补充) return 0; } ``` ### 关键实现细节 1. **桶管理**: - 使用`head[0]-head[9]``tail[0]-tail[9]`分别记录每个桶的首尾节点 - 尾指针确保$O(1)$时间完成节点追加(引用[4]) 2. **分配过程**: ```cpp int digit = (cur->data / factor) % 10; // 提取当前位 tail[digit]->next = cur; // 追加到桶尾 tail[digit] = cur; // 更新尾指针 ``` 3. **收集过程**: ```cpp last->next = head[i]; // 连接非空桶 last = tail[i]; // 更新收集链表的尾 ``` 4. **稳定性保持**: - 按顺序处理桶(0→9) - 每个桶内保持原始相对顺序(引用[3]) ### 性能优化点 1. **动态计算位数**:避免固定位数的冗余计算 2. **内存效率**:仅使用指针操作,无需额外存储空间 3. **负数处理**:可通过偏移量转为正数处理(需额外步骤)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值