算法思想
可以使用队列来实现扑克牌的排序。
基本思路:
1、将扑克牌按照花色和数字的组合读入队列中。
2、通过比较扑克牌的花色和数字,将队列中的元素按照花色和数字的升序规则进行重新排列。
自然语言描述
1. 数据结构定义
Card 结构体:表示一张扑克牌,包含两个成员: suit:表示花色(如 A, B, C, D)。 number:表示数字(如 1, 2, 3, 4, 5)。
Queue 结构体:表示一个队列,用于存储排序后的牌。包含以下成员: cards:一个数组,用于存储牌。 front 和 rear:分别表示队列的前后指针,用于管理队列的入队和出队操作。
2. 队列操作函数
initializeQueue:初始化队列,将 front 和 rear 设置为 -1,表示队列为空。
isQueueEmpty:检查队列是否为空。如果 front 为 -1,则队列为空。
isQueueFull:检查队列是否已满。如果 rear 的下一个位置是 front,则队列已满。
enqueue:将一张牌加入队列。如果队列已满,则提示错误;否则将牌放入队列的 rear 位置,并更新 rear。
dequeue:从队列中取出一张牌。如果队列为空,则提示错误;否则取出 front 位置的牌,并更新 front。
3. 排序逻辑
getSuitValue:将花色转换为数值,便于比较。例如: A 对应 1,B 对应 2,C 对应 3,D 对应 4;
compareCards:比较两张牌的优先级。先比较花色,如果花色相同,则比较数字。
sortCards:使用标准库函数 qsort 对牌进行排序。排序规则由 compareCards 函数定义。
4. 主函数逻辑
初始化牌组:定义一组牌,例如 A3, D3, A1, B5, C3, B2, C1, D4。
排序牌组:调用 sortCards 函数,按照花色和数字的优先级对牌进行排序。
初始化队列:创建一个空队列。
将排序后的牌入队:将排序后的牌依次加入队列。
打印排序结果:从队列中依次取出牌,并打印每张牌的花色和数字。
5. 代码执行流程
定义一组牌。
1)对牌进行排序,排序规则为: 先按花色排序:A < B < C < D。
2)同花色按数字排序:1 < 2 < 3 < 4 < 5。
3)将排序后的牌加入队列。
4)从队列中依次取出牌并打印,最终输出排序结果。
伪代码
定义结构体 Card
字符类型 suit
整数类型 number
结束定义
定义结构体 Queue
数组 cards[MAX_CARDS]
整数类型 front
整数类型 rear
结束定义
函数 initializeQueue(队列 q)
q.front = -1
q.rear = -1
结束函数
函数 isQueueEmpty(队列 q)
如果 q.front 等于 -1
返回 true
否则
返回 false
结束如果
结束函数
函数 isQueueFull(队列 q)
如果 (q.rear + 1) 对 MAX_CARDS 取余 等于 q.front
返回 true
否则
返回 false
结束如果
结束函数
函数 enqueue(队列 q, 扑克牌 card)
如果 isQueueFull(q) 为 true
输出 "Queue is full"
返回
结束如果
如果 isQueueEmpty(q) 为 true
q.front = 0
q.rear = 0
否则
q.rear = (q.rear + 1) 对 MAX_CARDS 取余
结束如果
q.cards[q.rear] = card
结束函数
函数 dequeue(队列 q)
如果 isQueueEmpty(q) 为 true
输出 "Queue is empty"
返回 空扑克牌 { '\0', 0 }
结束如果
扑克牌 card = q.cards[q.front]
如果 q.front 等于 q.rear
q.front = -1
q.rear = -1
否则
q.front = (q.front + 1) 对 MAX_CARDS 取余
结束如果
返回 card
结束函数
函数 getSuitValue(字符 suit)
开关 (suit)
情况 'A': 返回 1
情况 'B': 返回 2
情况 'C': 返回 3
情况 'D': 返回 4
默认情况: 返回 0
结束开关
结束函数
函数 compareCards(指向扑克牌 a 的指针, 指向扑克牌 b 的指针)
扑克牌指针 cardA = 指向扑克牌 a 的指针 转换为 Card 指针类型
扑克牌指针 cardB = 指向扑克牌 b 的指针 转换为 Card 指针类型
整数 suitA = getSuitValue(cardA.suit)
整数 suitB = getSuitValue(cardB.suit)
如果 suitA 不等于 suitB
返回 suitA - suitB
否则
返回 cardA.number - cardB.number
结束如果
结束函数
函数 sortCards(扑克牌数组 cards, 整数 n)
使用 qsort 函数对 cards 数组进行排序,排序依据为 compareCards 函数
结束函数
主函数 main()
扑克牌数组 cards = { {'A', 3}, {'D', 3}, {'A', 1}, {'B', 5}, {'C', 3}, {'B', 2}, {'C', 1}, {'D', 4} }
整数 n = cards 数组元素个数
sortCards(cards, n)
队列 q
initializeQueue(q)
对于 i 从 0 到 n - 1
enqueue(q, cards[i])
结束循环
输出 "Sorted cards: "
当!isQueueEmpty(q) 时
扑克牌 card = dequeue(q)
输出 card.suit 和 card.number
结束循环
输出换行符
返回 0
结束主函数
代码实现
1、qsort快速排序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CARDS 100 // 定义最大牌数
// 牌的结构体
typedef struct {
char suit; // 花色
int number; // 数字
} Card;
// 队列的结构体
typedef struct {
Card cards[MAX_CARDS]; // 存储牌的数组
int front, rear; // 队列的前后指针
} Queue;
// 初始化队列
void initializeQueue(Queue* q) {
q->front = q->rear = -1; // 初始时队列为空
}
// 判断队列是否为空
int isQueueEmpty(Queue* q) {
return q->front == -1; // 如果 front 为 -1,队列为空
}
// 判断队列是否已满
int isQueueFull(Queue* q) {
return (q->rear + 1) % MAX_CARDS == q->front; // 如果 rear 的下一个位置是 front,队列已满
}
// 入队
void enqueue(Queue* q, Card card) {
if (isQueueFull(q)) {
printf("Queue is full\n"); // 如果队列已满,打印提示信息
return;
}
if (isQueueEmpty(q)) {
q->front = q->rear = 0; // 如果队列为空,初始化 front 和 rear
}
else {
q->rear = (q->rear + 1) % MAX_CARDS; // 否则,rear 向后移动
}
q->cards[q->rear] = card; // 将牌放入队列
}
// 出队
Card dequeue(Queue* q) {
if (isQueueEmpty(q)) {
printf("Queue is empty\n"); // 如果队列为空,打印提示信息
Card emptyCard = { '\0', 0 }; // 返回一张空牌
return emptyCard;
}
Card card = q->cards[q->front]; // 取出队首的牌
if (q->front == q->rear) {
q->front = q->rear = -1; // 如果队列中只有一张牌,出队后队列为空
}
else {
q->front = (q->front + 1) % MAX_CARDS; // 否则,front 向后移动
}
return card; // 返回取出的牌
}
// 获取花色的数值,用于比较
int getSuitValue(char suit) {
switch (suit) {
case 'A': return 1; // A 对应 1
case 'B': return 2; // B 对应 2
case 'C': return 3; // C 对应 3
case 'D': return 4; // D 对应 4
default: return 0; // 其他情况返回 0
}
}
// 比较两张牌的优先级
int compareCards(const void* a, const void* b) {
Card* cardA = (Card*)a; // 将 void 指针转换为 Card 指针
Card* cardB = (Card*)b; // 将 void 指针转换为 Card 指针
int suitA = getSuitValue(cardA->suit); // 获取 cardA 的花色值
int suitB = getSuitValue(cardB->suit); // 获取 cardB 的花色值
if (suitA != suitB) {
return suitA - suitB; // 如果花色不同,按花色排序
}
return cardA->number - cardB->number; // 如果花色相同,按数字排序
}
// 对牌进行排序
void sortCards(Card cards[], int n) {
qsort(cards, n, sizeof(Card), compareCards); // 使用 qsort 函数进行排序
}
int main() {
// 初始化牌组
Card cards[] = {
{'A', 3}, {'D', 3}, {'A', 1}, {'B', 5},
{'C', 3}, {'B', 2}, {'C', 1}, {'D', 4}
};
int n = sizeof(cards) / sizeof(cards[0]); // 计算牌的数量
sortCards(cards, n); // 对牌进行排序
Queue q;
initializeQueue(&q); // 初始化队列
// 将排序后的牌入队
for (int i = 0; i < n; i++) {
enqueue(&q, cards[i]);
}
// 打印
printf("Sorted cards: ");
while (!isQueueEmpty(&q)) {
Card card = dequeue(&q); // 出队
printf("%c%d ", card.suit, card.number); // 打印牌
}
printf("\n");
return 0;
}
2、基数排序:
#include <iostream>
#include <queue>
#include <vector>
// 牌的结构体
struct Card {
char suit; // 花色
int number; // 数字
};
// 获取花色的数值,用于比较
int getSuitValue(char suit) {
switch (suit) {
case 'A': return 1; // A 对应 1
case 'B': return 2; // B 对应 2
case 'C': return 3; // C 对应 3
case 'D': return 4; // D 对应 4
default: return 0; // 其他情况返回 0
}
}
// 基数排序
void radixSortCards(std::vector<Card>& cards) {
// 先按数字进行排序
std::vector<std::queue<Card>> queuesNumber(13);
for (const auto& card : cards) {
queuesNumber[card.number - 1].push(card);
}
cards.clear();
for (auto& queue : queuesNumber) {
while (!queue.empty()) {
cards.push_back(queue.front());
queue.pop();
}
}
// 再按花色进行排序
std::vector<std::queue<Card>> queuesSuit(4);
for (const auto& card : cards) {
int suitValue = getSuitValue(card.suit) - 1;
queuesSuit[suitValue].push(card);
}
cards.clear();
for (auto& queue : queuesSuit) {
while (!queue.empty()) {
cards.push_back(queue.front());
queue.pop();
}
}
}
int main() {
// 初始化牌组
std::vector<Card> cards = {
{'A', 3}, {'D', 3}, {'A', 1}, {'B', 5},
{'C', 3}, {'B', 2}, {'C', 1}, {'D', 4}
};
radixSortCards(cards); // 对牌进行排序
std::queue<Card> q; // 创建一个队列
// 将排序后的牌入队
for (const auto& card : cards) {
q.push(card);
}
// 输出
std::cout << "Sorted cards: ";
while (!q.empty()) {
Card card = q.front(); // 获取队首元素
q.pop(); // 出队
std::cout << card.suit << card.number << " ";
}
std::cout << std::endl;
return 0;
}
其中对基数排序函数进行解释:
void radixSortCards(std::vector<Card>& cards) {
// 函数定义,接收一个 std::vector<Card> 类型的引用作为参数,用于存储扑克牌集合
// 函数没有返回值,因为它直接修改传入的向量
// 先按数字进行排序
std::vector<std::queue<Card>> queuesNumber(13);
// 创建一个包含 13 个元素的 std::vector,每个元素是一个 std::queue<Card> 类型的队列
// 13 代表扑克牌的数字范围(1 - 13),每个队列对应一个数字
for (const auto& card : cards) {
// 遍历传入的扑克牌向量
// const auto& 表示使用常量引用,避免不必要的复制,提高效率
queuesNumber[card.number - 1].push(card);
// 根据当前扑克牌的数字,将其放入对应的队列中
// 数组索引从 0 开始,使用 card.number - 1 作为队列的索引
}
cards.clear();
// 清空原有的扑克牌向量,为重新收集排序后的扑克牌做准备
for (auto& queue : queuesNumber) {
// 遍历存储扑克牌的队列向量
// auto& 表示使用引用,以便直接操作队列
while (!queue.empty()) {
// 当前队列不为空则继续循环
cards.push_back(queue.front());
// 将最早进入队列的扑克牌添加到原向量的末尾
queue.pop();
// 从队列中移除队首元素,以处理下一个元素
}
}
}
运行结果展示
与冒泡排序对比
冒泡排序:
1、时间复杂度为O(n²)
2、空间复杂度为O(1)
3、适用范围:小规模的数据处理、需要自行编写代码实现排序。
qsort排序:
1、时间复杂度为O(n log n)
2、空间复杂度为O(n)
3、适用范围:大规模的数据处理、在stdlib.h头文件基础上调用C语言函数库中的qsort函数进 行快速排序,无需自行编写代码,操作方便。
学习心得
1、qsort函数的学习
功能:qsort 函数是 C 标准库中用于对数组进行排序的函数,它具有通用性,可以对各种不同类型的数组进行排序。使用 qsort 函数,可以对整数数组、结构体数组、字符串数组等各种类型的数组进行排序,只需要提供合适的比较函数来定义排序规则即可,大大提高了代码的复用性和灵活性。
qsort函数的原型:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
qsort 函数的各个参数含义如下:
1)base:指向要排序的数组的起始地址,在 sortCards 函数中,传入的是 cards 数组名,在 C 语言中数组名会被自动转换为指向数组首元素的指针。在本文中 cards 作为 qsort 函数的第一个参数,指向了要排序的扑克牌数组的起始位置。
2)nmemb:表示数组中元素的数量。在本文中传入的是 n,即 cards 数组中扑克牌的数量。
3)size:表示每个数组元素的大小(以字节为单位)。在本文中使用 sizeof(Card) 来获取 Card 结构体类型的大小,确保 qsort 函数能够正确地处理每个元素。
4)compar:compar 参数是 qsort 函数中用于接受一个函数指针的参数,该函数指针指向一个比较函数,该比较函数用于确定两个元素的顺序:比较函数要返回一个 bool 类型的值,以此表明两个元素的相对顺序。 若比较函数返回 true,表示第一个参数应排在第二个参数前面;若返回 false,则反之。 并且比较函数要满足严格弱序的要求,也就是具有传递性、非自反性和反对称性。在本文中, sortCards 函数里,传入的是 compareCards 函数名,compareCards 函数会根据扑克牌的花色和数字来比较两张牌的优先级,并返回相应的比较结果(小于 0、等于 0 或大于 0),qsort 函数会根据这个结果来对数组元素进行排序。
2、基数排序
定义:基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。
基本原理:
将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后从最低位开始,依次进行排序。这样从最低位排序一直到最高位排序完成后,数列就变成有序的了。
譬如:
以十进制为例,假设有一组数据 {329, 457, 657, 839, 436, 720, 355} 。
1. 首先按照个位数字进行排序,将数字分配到对应的桶(队列)中。个位数字为0的放入第0个桶,为1的放入第1个桶,以此类推。第一轮排序后,数据在桶中的顺序为 720, 436, 355, 457, 657, 329, 839 。
2. 接着按照十位数字进行排序,将上一轮桶中的数据依次取出,再根据十位数字放入相应桶中。第二轮排序后顺序为 720, 329, 436, 839, 355, 457, 657 。
3. 最后按照百位数字进行排序,得到最终的有序序列 329, 355, 436, 457, 657, 720, 839 。
算法特点:
1)时间复杂度:基数排序的时间复杂度为 O(d(n + k)),其中 d 是数字的最大位数,n 是待排序元素的个数,k 是基数(如十进制的基数是10)。
2)空间复杂度:空间复杂度为 O(n + k),需要额外的空间来存储桶(队列)。
3)稳定性:基数排序是稳定的排序算法,即相同数值的元素在排序前后的相对位置不变。
适用情况:
基数排序适用于元素取值范围不大、位数固定的情况,如对大量的身份证号码、邮政编码等进行排序。但如果数据的位数相差很大,或者取值范围非常大,可能会导致性能下降。
思考:
基数排序先从个位开始比较而不是先从百位开始,原因是:
1)保证排序稳定性
基数排序是稳定的排序算法,先从个位开始比较能确保相同高位数字的元素,在低位排序后相对顺序不变。如果先从百位开始排序,那么在后续对十位、个位排序时,可能会改变百位相同元素的相对顺序,从而破坏稳定性。
2)符合排序逻辑
先从个位排序,再依次到十位、百位等高位,是按照数字的权重从低到高进行排序。只有先将低位排序稳定,才能在更高位排序时,不会因低位的不确定性而影响整体排序结果。如果先对高位进行排序,当高位相同时,低位的无序会导致整体排序不准确。例如,对于 329 和 355 ,若先按百位排序,它们在同一组,此时无法确定它们的顺序,只有先对个位、十位排序后,才能准确确定它们的顺序。