PTA 排序(快速排序,基数排序)

本文介绍了PAT考试中的排名生成问题,该问题涉及到排序算法的应用。首先,文章概述了快速排序的基本原理和C++实现,接着解释了基数排序的概念,它是桶排序的一种优化。在给定的样例输入和输出中,展示了如何根据得分和提交记录生成排名。解决方案包括将数据整理成结构体,并利用STL的sort函数进行排序,同时注意排序函数的编写和结构体数组的声明方式。此外,文章强调了在处理过程中初始化标志变量、处理重复满分成绩等问题的注意事项。

快速排序
在一堆数中选取一个主元,以主元为中心将这堆数分为大小两部分,对这两部分递归调用快排,最后当规模最够小的时候(小于预设的阈值CutOff)将使用简单排序(如插入排序)。

/*快速排序*/
//取主元 get pivot
long Median3(long a[], long Left, long Right)
{
	int Center = (Right +Left)/2;
	if (a[Left] > a[Center])
		Swap(&a[Left] , &a[Center]);
	if (a[Left] > a[Right])
		Swap(&a[Left], &a[Right]);
	if (a[Center] > a[Right])
		Swap( &a[Center] , &a[Right]);

	Swap(&a[Center], &a[Right - 1]);//把Povit藏到Right-1
	return a[Right - 1];
}
//快速排序
void QuickSort(long a[], int Left, int Right)
{	
	long Pivot;//主元
	int i, j;

	int CutOff = 10e2;
	if (CutOff <= Right - Left)
	{
		Pivot = Median3(a, Left, Right);
		i = Left; //左边指针
		j = Right - 1; //右边指针

		for (;;)
		{
			while (a[++i] < Pivot) {}
			while (a[--j] < Pivot) {}
			if (i < j)
				Swap(&a[i], &a[j]);
			else
				break;
		}
		Swap(&a[i], &a[Right - 1]); //Pivot 落位
		QuickSort(a, Left, i - 1);
		QuickSort(a, i + 1, Right);
	}
	else
		Insertion_sort(a+Left, Right - Left+1);
}
//统一接口
void Quick_sort(long a[],int N)
{
	QuickSort(a, 0, N - 1);

}
/*快速排序结束*/

快排c++背诵版:

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        quick_sort(nums,0,n-1,n);
        return nums;
    }
    void quick_sort(vector<int>& nums,int left,int right,int n)
    {
        if(left < right) //分裂到最小单位即停止
        {
            int pivotpos = partition(nums,left,right);
            quick_sort(nums,left,pivotpos-1,n);
            quick_sort(nums,pivotpos+1,right,n);
        }
    }

    int partition(vector<int>& nums,int left,int right)
    {
        int povit = nums[left]; //区间最左端为主元
        
        while(left < right)
        {
        while(left < right && nums[right]>= povit)
            right--;
        nums[left] = nums[right];//小数换到左边去
        while(left < right && nums[left] <= povit)
            left++;
        nums[right] = nums[left];//大数换到右边去
        }

        nums[left] = povit;//主元落位
        return left;
    }
};

快速排序效率:
在这里插入图片描述

基数排序:
桶排序的升级版,即是将数据按某种分类丢到M个桶里面,常用次位优先原则(例如对0~999范围内的N个数排序,先按照个位数的0到9分类将数据丢入相应 ”桶“ 中。然后进入第二轮分类,依次按照桶的顺序和桶内部顺序将数据按十位数的0到9的分类。随后第三轮分类同理。最后依次输出,即完成了排序)

特点:将数据切割成若干个分类进行比较,在类似对百万级的电话号码进行排序的问题上,使用基数排序效率较高。

次位优先模板:

/* 基数排序 - 次位优先 */
 
/* 假设元素最多有MaxDigit个关键字,基数全是同样的Radix */
#define MaxDigit 4
#define Radix 10
 
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node {
    int key;
    PtrToNode next;
};
 
/* 桶头结点 */
struct HeadNode {
    PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
  
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
    int d, i;
     
    for (i=1; i<=D; i++) {
        d = X % Radix;
        X /= Radix;
    }
    return d;
}
 
void LSDRadixSort( ElementType A[], int N )
{ /* 基数排序 - 次位优先 */
     int D, Di, i;
     Bucket B;
     PtrToNode tmp, p, List = NULL; 
      
     for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
         B[i].head = B[i].tail = NULL;
     for (i=0; i<N; i++) { /* 将原始序列逆序存入初始链表List */
         tmp = (PtrToNode)malloc(sizeof(struct Node));
         tmp->key = A[i];
         tmp->next = List;
         List = tmp;
     }
     /* 下面开始排序 */ 
     for (D=1; D<=MaxDigit; D++) { /* 对数据的每一位循环处理 */
         /* 下面是分配的过程 */
         p = List;
         while (p) {
             Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
             /* 从List中摘除 */
             tmp = p; p = p->next;
             /* 插入B[Di]号桶尾 */
             tmp->next = NULL;
             if (B[Di].head == NULL)
                 B[Di].head = B[Di].tail = tmp;
             else {
                 B[Di].tail->next = tmp;
                 B[Di].tail = tmp;
             }
         }
         /* 下面是收集的过程 */
         List = NULL; 
         for (Di=Radix-1; Di>=0; Di--) { /* 将每个桶的元素顺序收集入List */
             if (B[Di].head) { /* 如果桶不为空 */
                 /* 整桶插入List表头 */
                 B[Di].tail->next = List;
                 List = B[Di].head;
                 B[Di].head = B[Di].tail = NULL; /* 清空桶 */
             }
         }
     }
     /* 将List倒入A[]并释放空间 */
     for (i=0; i<N; i++) {
        tmp = List;
        List = List->next;
        A[i] = tmp->key;
        free(tmp);
     } 
}

主位优先模板:

/* 基数排序 - 主位优先 */
 
/* 假设元素最多有MaxDigit个关键字,基数全是同样的Radix */
 
#define MaxDigit 4
#define Radix 10
 
/* 桶元素结点 */
typedef struct Node *PtrToNode;
struct Node{
    int key;
    PtrToNode next;
};
 
/* 桶头结点 */
struct HeadNode {
    PtrToNode head, tail;
};
typedef struct HeadNode Bucket[Radix];
  
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
    int d, i;
     
    for (i=1; i<=D; i++) {
        d = X%Radix;
        X /= Radix;
    }
    return d;
}
 
void MSD( ElementType A[], int L, int R, int D )
{ /* 核心递归函数: 对A[L]...A[R]的第D位数进行排序 */
     int Di, i, j;
     Bucket B;
     PtrToNode tmp, p, List = NULL; 
     if (D==0) return; /* 递归终止条件 */
      
     for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
         B[i].head = B[i].tail = NULL;
     for (i=L; i<=R; i++) { /* 将原始序列逆序存入初始链表List */
         tmp = (PtrToNode)malloc(sizeof(struct Node));
         tmp->key = A[i];
         tmp->next = List;
         List = tmp;
     }
     /* 下面是分配的过程 */
     p = List;
     while (p) {
         Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
         /* 从List中摘除 */
         tmp = p; p = p->next;
         /* 插入B[Di]号桶 */
         if (B[Di].head == NULL) B[Di].tail = tmp;
         tmp->next = B[Di].head;
         B[Di].head = tmp;
     }
     /* 下面是收集的过程 */
     i = j = L; /* i, j记录当前要处理的A[]的左右端下标 */
     for (Di=0; Di<Radix; Di++) { /* 对于每个桶 */
         if (B[Di].head) { /* 将非空的桶整桶倒入A[], 递归排序 */
             p = B[Di].head;
             while (p) {
                 tmp = p;
                 p = p->next;
                 A[j++] = tmp->key;
                 free(tmp);
             }
             /* 递归对该桶数据排序, 位数减1 */
             MSD(A, i, j-1, D-1);
             i = j; /* 为下一个桶对应的A[]左端 */
         } 
     } 
}
 
void MSDRadixSort( ElementType A[], int N )
{ /* 统一接口 */
    MSD(A, 0, N-1, MaxDigit); 
}

例题:10-排序5 PAT Judge (25分)
The ranklist of PAT is generated from the status list, which shows the scores of the submissions. This time you are supposed to generate the ranklist for PAT.

Input Specification:
Each input file contains one test case. For each case, the first line contains 3 positive integers, N (≤10e​4
​ ), the total number of users, K (≤5), the total number of problems, and M (≤10e​5​​ ), the total number of submissions. It is then assumed that the user id’s are 5-digit numbers from 00001 to N, and the problem id’s are from 1 to K. The next line contains K positive integers p[i] (i=1, …, K), where p[i] corresponds to the full mark of the i-th problem. Then M lines follow, each gives the information of a submission in the following format:

user_id problem_id partial_score_obtained

where partial_score_obtained is either −1 if the submission cannot even pass the compiler, or is an integer in the range [0, p[problem_id]]. All the numbers in a line are separated by a space.

Output Specification:
For each test case, you are supposed to output the ranklist in the following format:

rank  user_id total_score   s[1] ... s[K]

where rank is calculated according to the total_score, and all the users with the same total_score obtain the same rank; and s[i] is the partial score obtained for the i-th problem. If a user has never submitted a solution for a problem, then “-” must be printed at the corresponding position. If a user has submitted several solutions to solve one problem, then the highest score will be counted.

The ranklist must be printed in non-decreasing order of the ranks. For those who have the same rank, users must be sorted in nonincreasing order according to the number of perfectly solved problems. And if there is still a tie, then they must be printed in increasing order of their id’s. For those who has never submitted any solution that can pass the compiler, or has never submitted any solution, they must NOT be shown on the ranklist. It is guaranteed that at least one user can be shown on the ranklist.

Sample Input:
7 4 20
20 25 25 30
00002 2 12
00007 4 17
00005 1 19
00007 2 25
00005 1 20
00002 2 2
00005 1 15
00001 1 18
00004 3 25
00002 2 25
00005 3 22
00006 4 -1
00001 2 18
00002 1 20
00004 1 15
00002 4 18
00001 3 4
00001 4 2
00005 2 -1
00004 2 0

Sample Output:
1 00002 63 20 25 - 18
2 00005 42 20 0 22 -
2 00007 42 - 25 - 17
2 00001 42 18 18 4 2
5 00004 40 15 0 25 -

思路:
一开始整合数据,将某人的AK题目数量,某人的总得分,还有这个人的各个题目的具体得分情况数组*score全部塞到一个机构体里面,将数据按照题目中的规则要求完成输入。随后用STL的sort排序(需要自己写cmp函数),最后输出。

需要注意的是:
1.sort()的第三个参数–cmp函数的写法,cmp是个bool函数,需要排在前面的元素就返回一个true。

2.注意结构体的写法那里,为了表示 指向结构体指针的数组,只需要 结构体 数组名[大小]* 就可以了,不需要在那搞 双重指针(虽然其实是一个意思,但是写起来晕乎乎的,有时候搞着搞着搞出把双重指针装数组变成三重指针了 = = )。

3.遍历所有人那里,flag的值要写在if的外面。这其实是一种常规的写法,因为if很可能不执行,这样如果有一个不进if里面的用例,就会导致这个用例的flag用的是前面那个进if的值,会导致程序出错。 总之,这种flag监控值的每轮初始化,不要进if,最好每次循环立刻做。

4.要表示某种,第一次执行的时候成立,后来的(相等情况)就不成立的情况(比如重复提交满分成绩)。可以参考本程序最后那个Tmp的设置,先设置一个超越常规的数字,然后第一次执行的时候必然会成立,然后之后的等于情况就不进if了。有点哨兵的感觉?

#include <iostream>
#include <vector>
#include <string>
#include <stdio.h>
#include <algorithm>

using namespace std;

const int MaxUsersNumber = 10e4;//最大人数 1~10e4
const int MaxProblems = 5;//最多12345,物种题型

int Point[MaxProblems] = { 0 };//12345个问题

struct UserNode {
	int *Score;//各题得分情况
	int KilledNumbers;//解决过的问题数量
	int TotalScore;//总分
};
UserNode* ranklist[MaxUsersNumber];//人头数组,里面直接装结构体指针

bool cmp(int a , int b)
{
	if (ranklist[a]->TotalScore > ranklist[b]->TotalScore)
		return true;
	else if (ranklist[a]->TotalScore == ranklist[b]->TotalScore)
	{
		if (ranklist[a]->KilledNumbers > ranklist[b]->KilledNumbers)
			return true;
		else if (ranklist[a]->KilledNumbers == ranklist[b]->KilledNumbers)
			if (a < b)	return true;
	}
	return false;
}

int main()
{
	int N, K, M;
	cin >> N >> K >> M;
	for (int i = 0; i < K; i++)
		cin >> Point[i];	//记录下各个题目的得分

	for (int i = 1; i <= N; i++)
		ranklist[i] = NULL;

	vector<int> vec;//供打印的容器装入有过得分的
	int flag;//是否提交过答案
	int ID;
	int SubID, SubProblem, SubScore;
	for (int i = 0; i < M; i++)//提交次数循环
	{
		cin >> SubID >> SubProblem >> SubScore;

		if (!ranklist[SubID])//若是某人的首次提交
		{
			ranklist[SubID] = new UserNode;
			ranklist[SubID]->Score = new int[K]; //开辟每题的得分数组

			for (int j = 0; j < K; j++)//初始化得分数组
				ranklist[SubID]->Score[j] = -2;//没提交标记为-2

			ranklist[SubID]->TotalScore = 0;
			ranklist[SubID]->KilledNumbers = 0;//完成初始化
		}

		if (ranklist[SubID]->Score[SubProblem - 1] < SubScore)//若非首次提交,且新提交的分数更大
			ranklist[SubID]->Score[SubProblem - 1] = SubScore; //更新该题得分(如果没通过则变成-1,对应最后的0分)
	}

	for (ID = 1; ID <= N; ID++)//遍历所有人
	{
		flag = 0;//初始化成所有题目都没通过
		if (ranklist[ID])//如果此人提交过
		{
			
			for (int j = 0; j < K; j++)
			{
				if (ranklist[ID]->Score[j] >= 0) //若此题提交过
				{
					flag = 1;//这个人有题目提交了
					ranklist[ID]->TotalScore += ranklist[ID]->Score[j];
				}
				if (ranklist[ID]->Score[j] == Point[j])//这题拿了满分
					ranklist[ID]->KilledNumbers++;
			}//计算完这个人的各题得分和总分了

			//if (flag == 1)	//有可能有人压根就没提交过,所以要放在if内层,不然没提交的人会用前一个ID的flag
				//vec.push_back(ID);//若有过得分,则可以加入排序了
		}
		if (flag == 1)
			vec.push_back(ID);//若有过得分,则可以加入排序了
	}


	sort(vec.begin(), vec.end(), cmp);

	/*cout << "***********\n";
	cout << "1: " << ranklist[1]->KilledNumbers << " 5: " <<
		ranklist[5]->KilledNumbers << " 7: " << ranklist[7]->KilledNumbers;
*/

	string IDstr;
	int FinalRank;//最后的rank排名
	int Tmp = 1000;//用来重复提交,后面的提交成绩没有前面好的情况
					//等于1000(大于100就行)是让第一轮判断可以跑起来
	for (int i = 0; i < vec.size(); i++)
	{
		ID = vec[i];//此时,vec里面装的就是排序后的ID顺序

		if (ranklist[ID]->TotalScore < Tmp) {
			FinalRank = i + 1;//vec是从0开始的,排序是从1开始的
			Tmp = ranklist[ID]->TotalScore;//记录最好排名(下次跑if判断就是等于了,rank不会动)
		}
		//FinalRank = i + 1;

		IDstr = to_string(ID);
		while (IDstr.size() < 5)
			IDstr = "0" + IDstr;

		cout << FinalRank<<" " << IDstr<<" " 
			<< ranklist[ID]->TotalScore;//输出rank,id和总得分
		for (int j = 0; j < K; j++)
		{
			if (ranklist[ID]->Score[j] == -2)//若这个人的某题没有提交过
				cout << " -";
			else if (ranklist[ID]->Score[j] == -1)//若这个人的某题提交过但是没通过
				cout << " 0";
			else
				cout <<" "<<ranklist[ID]->Score[j];		
		}
		cout << "\n";
	}
	return 0;
}

其中tmp那一块看的不是太懂,主要是逻辑上我认为输入的时候不可能产生重复的ID,也就不会有一个ID对应多个rank的情况。但事实上,如果不写会有如下报错:
在这里插入图片描述

加上rank保证ID的唯一性后就AC了。
在这里插入图片描述

### 关于重庆科技大学PTA平台上的基数排序题目与教程 在处理涉及 **PTA 平台** 和 **基数排序** 的相关内容时,可以参考以下信息: #### 基数排序简介 基数排序是一种非比较型整数排序算法,其原理是通过将整数按位数切割成不同的数字,然后按照每位数字分别进行排序。这种方法通常适用于固定长度的数值数据。对于 PTA 上的相关题目,可能涉及到实现一种基于桶的方式完成排序的任务。 根据引用中的描述[^2],桶式排序属于基数排序的一种具体形式。它利用二维数组作为存储结构来模拟多个“桶”,从而达到对一组正整数从小到大排列的目的。 --- #### C语言实现简单基数排序示例代码 以下是针对类似需求设计的一段C语言程序代码片段用于演示如何运用桶的概念来进行基本的数据整理工作: ```c #include <stdio.h> #define MAX_NUMBERS 15 // 定义最大数量为15个数 void bucketSort(int numbers[], int array_size){ const int BUCKETS = 10; int buckets[BUCKETS][MAX_NUMBERS]; int counts[BUCKETS]={0}; for (int pass=0;pass<4;pass++){//假设最多四位数 for (int i=0;i<array_size;i++){ int digit=(numbers[i]/(int)(pow(10,pass)))%10; buckets[digit][counts[digit]]=numbers[i]; counts[digit]++; } int index=0; for (int b=0;b<Buckets;b++){ for (int c=0;c<counts[b];c++) numbers[index++]=buckets[b][c]; counts[b]=0;//重置计数器以便下次循环使用 } } } int main(){ int nums[MAX_NUMBERS]; printf("请输入%d个正整数:\n",MAX_NUMBERS); for(int i=0;i<MAX_NUMBERS;i++)scanf("%d",&nums[i]); bucketSort(nums,MAX_NUMBERS); printf("排序后的结果:"); for(int i=0;i<MAX_NUMBERS;i++)printf("%d ",nums[i]); } ``` 此代码实现了基础版本的桶排序逻辑,并能够满足大多数情况下关于小规模数据集合的操作需求。 --- #### 注意事项 当参与像重庆科技学院所使用的 `PTA` 测试环境提交作业时,请特别留意某些特殊规则。例如,在动态分配内存方面可能存在特定限制条件[^1]。因此建议严格按照官方文档说明调整自己的解决方案以确保所有测试案例均能顺利通过验证。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值