Sort student grades in C language (merge sort and cardinality sort)

I. Introduction

Our internal sorting has learned insertion sorting (direct insertion sorting, half insertion sorting, Hill sorting), exchange sorting (bubble sorting, quick sorting), selection sorting (simple selection sorting, heap sorting), these are all internal sorting, and then we learn the remaining merge sorting and radix sorting in internal sorting.

2. Merge sort

1. Algorithm idea

Merge sort is different from the above-mentioned sorting ideas based on exchange, selection, etc. "merge" means to merge two or more ordered lists into a new ordered list. Assuming that the table to be sorted contains n records, it can be regarded as n ordered sub-tables, each of which has a length of 1, and then merged two by two to obtain [n/2] ordered tables with a length of 2 or 1; continue to merge two-by-two...and repeat until it is merged into an ordered list of length n. This sorting method is called 2-way merge sort.
Decomposition: Divide the list to be sorted containing n elements into sub-tables each containing n/2 elements, and use the 2-way merge sort algorithm to recursively sort the two sub-tables
.
Merge: Merge two sorted sublists to get sorted results.

2. Example

insert image description here

3. Performance analysis

  • Space efficiency: In the Merge () operation, the auxiliary space is exactly n blocks, so the space complexity of the algorithm is O(n).
  • Time efficiency: The time complexity of each merge is O(n), and a total of [log 2 n] merges are required, so the time complexity of the algorithm is O(nlog 2 n).
  • Stability: Since the Merge () operation does not change the relative order of records with the same keyword, the 2-way merge sort algorithm is a stable sorting method.

3. Radix sorting

1. Algorithm idea


In order to realize multi-keyword sorting, there are usually two methods: the first one is the most significant bit first (MSD) method , which is divided into several smaller subsequences layer by layer according to the weight of key bits in descending order, and finally connects all subsequences into an ordered sequence. The second is the least bit first (LSD) method, which sorts according to the increasing weight of the key bits, and finally forms an ordered sequence.

For i=0, d-1, do a "distribution" and "collection" in turn (actually it is a stable sorting process).

  • Assignment: At the beginning, set the queues of Qo, .,., Q-1 as empty queues, and then examine each node a; (j=0,1,.,n-1) in the linear table in turn. If the keyword k;=k of a;, put a; into the Qk queue.
  • Collection: connect the nodes in each queue of Qo, 0.,-, Qr1 end-to-end in order to obtain a new node sequence, thereby forming a new linear list.

2. Example

Chained cardinality sorting is usually used, assuming that the following 10 records are sorted:

insert image description here

Take the ones, tens, and hundreds in turn for distribution and collection:

insert image description here
Each keyword is a positive integer below 1000, and the base r= 10. In the sorting process, 10 chain queues are needed. Each keyword consists of 3 subkeywords K'K2K', which represent hundreds, tens and ones respectively. A total of three "allocation" and "collection" operations are required. The first allocation is carried out with the lowest position key K3, and all the records with the same lowest position key (units) are allocated to the same queue, as shown in figure (a), and then the collection operation is performed. The result after the first collection is shown in figure (b).

insert image description here
The second allocation is carried out with the second-lowest sub-key K2, and all records with equal second-lowest sub-keys (ten digits) are allocated to the same
queue, as shown in figure (a), and the result after the second round of collection is shown in figure (b).

insert image description here
The third round of allocation is carried out with the highest position key K', and all records with equal highest position key (hundreds) are allocated to the same
queue, as shown in figure (a), and the result after the third round of collection is shown in figure (b), so far the whole sorting is over.

3. Performance analysis

  • Space efficiency: The auxiliary storage space required for one sorting is r (r queues: r queue head pointers and r queue tail pointers), but these queues will be reused in subsequent sorting, so the space complexity of radix sorting is 0®.
  • Time efficiency: Radix sorting requires d allocation and collection, one allocation requires O(n), and one collection requires 0®, so the time complexity of radix sorting is O(d(n + r)), which has nothing to do with the initial state of the sequence.
  • Stability: For the radix sorting algorithm, it is very important that the bitwise sorting must be stable. Therefore, this also guarantees the stability of the radix sort.

4. Sorting algorithm code

1. Merge sort

The function of Merge() is to merge two adjacent ordered lists into one ordered list. Assume that two ordered tables A[low.mid] and A[mid1...high] are stored in adjacent positions in the same-sequence table, and they are first copied to the auxiliary array
B. Each time a record is taken from two segments corresponding to B to compare the keywords, and the smaller one is put into A. When the subscript of a segment in array B exceeds its corresponding table length (that is, all elements of this segment have been copied to A), the remaining part of the other segment is directly copied to A. The algorithm is as follows:

//归并函数
Elemtype *A=(Elemtype *)malloc(MaxSize * sizeof(Elemtype));      //辅助数组
void Merge(SqList &L,int low,int mid,int high){
    
         //这里三个指针不是为了快排,而是为了方便指示序列长度
	int i,j,k;
	for(k=low;k<=high;k++)
		A[k]=L.data[k];                                        //将L.data[]的所有元素复制到A中
	for(i=low,j=mid+1,k=i;i<=mid && j<=high;k++){
    
    
		if(A[i].grade<=A[j].grade)
			L.data[k]=A[i++];                       //比较左右两端元素的大小
		else
			L.data[k]=A[j++];
	}
	while(i<=mid)    L.data[k++]=A[i++];         //没检测完的直接复制
	while(j<=high)   L.data[k++]=A[j++];
}

//归并排序函数
void Mergesort(SqList &L,int low,int high){
    
    
	if(low<high){
    
    
		int mid=(low+high)/2;            //从中间划分两个子序列
		Mergesort(L,low,mid);           //对左侧子树进行递归排序
		Mergesort(L,mid+1,high);         //对右侧子树进行递归排序
		Merge(L,low,mid,high);          //归并
	}
}

2. Radix sort

//基数排序
void Basesort(LinkNode &L, Saquene queue[]) {
    
    
	for (int radix = 1; radix <= 100; radix *= 10) {
    
     // 对每个位数进行排序
		// 入队列
		LinkNode p = L->next;
		while (p != NULL) {
    
    
			int i = (p->data.grade / radix) % 10;
			enQueue(queue[i], &(p->data));
			p = p->next;
		}
		// 出队列
		LinkNode tail = L;
		for (int i = 0; i < 10; i++) {
    
    
			while (!QueueEmpty(queue[i])) {
    
    
				Elemtype e;
				deQueue(queue[i], &e);
				LinkNode newNode = (LinkNode)malloc(sizeof(LNode));
				newNode->data = e;             //不断的“收集”
				newNode->next = NULL;
				tail->next = newNode;        //tail相当于一个头结点
				tail = newNode;
			}
		}
	}
}

5. Complete C language test code

1. Test merge sort

/*我们今天的主角是归并排序,所以我们还是利用线性表来进行模拟*/

/*为了便于我们后面演示希尔排序,所以我们采用顺序存储结构*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define MaxSize 50                //这里只是演示,我们假设这里最多存五十个学生信息

//定义学生结构
typedef struct {
    
    
	char name[200];              //姓名
	int  grade;               //分数,这个是排序关键字
} Elemtype;

//声明使用顺序表
typedef struct {
    
    
	/*这里给数据分配内存,可以有静态和动态两种方式,这里采用动态分配*/
	Elemtype  *data;            //存放线性表中的元素是Elemtype所指代的学生结构体
	int length;                 //存放线性表的长度
} SqList;						//给这个顺序表起个名字,接下来给这个结构体定义方法

//初始化线性表
void InitList(SqList &L){
    
    
	/*动态分配内存的初始化*/
	L.data = (Elemtype*)malloc(MaxSize * sizeof(Elemtype));  //为顺序表分配空间
	L.length = 0;                                            //初始化长度为0
}

//求表长函数
int Length(SqList &L){
    
    
	return L.length;
}

//求某个数据元素值
bool GetElem(SqList &L, int i, Elemtype &e) {
    
    
	if (i < 1 || i > L.length)
		return false;         //参数i错误时,返回false
	e = L.data[i - 1];      //取元素值
	return true;
}

//输出线性表
void DispList(SqList &L) {
    
    
	if (L.length == 0)
		printf("线性表为空");
	//扫描顺序表,输出各元素
	for (int i = 0; i < L.length; i++) {
    
    
		printf("%s        %d", L.data[i].name,  L.data[i].grade);
		printf("\n");
	}
	printf("\n");
}

//插入数据元素
bool ListInsert(SqList &L, int i, Elemtype e) {
    
    
	/*在顺序表L的第i个位置上插入新元素e*/
	int j;
	//参数i不正确时,返回false
	if (i < 1 || i > L.length + 1 || L.length == MaxSize)
		return false;
	i--;                //将顺序表逻辑序号转化为物理序号
	//参数i正确时,将data[i]及后面的元素后移一个位置
	for (j = L.length; j > i; j--) {
    
    
		L.data[j] = L.data[j - 1];
	}
	L.data[i] = e;      //插入元素e
	L.length++;         //顺序表长度加1
	return true;
	/*平均时间复杂度为O(n)*/
}

//归并函数
Elemtype *A=(Elemtype *)malloc(MaxSize * sizeof(Elemtype));      //辅助数组
void Merge(SqList &L,int low,int mid,int high){
    
         //这里三个指针不是为了快排,而是为了方便指示序列长度
	int i,j,k;
	for(k=low;k<=high;k++)
		A[k]=L.data[k];                                        //将L.data[]的所有元素复制到A中
	for(i=low,j=mid+1,k=i;i<=mid && j<=high;k++){
    
    
		if(A[i].grade<=A[j].grade)
			L.data[k]=A[i++];                       //比较左右两端元素的大小
		else
			L.data[k]=A[j++];
	}
	while(i<=mid)    L.data[k++]=A[i++];         //没检测完的直接复制
	while(j<=high)   L.data[k++]=A[j++];
}

//归并排序函数
void Mergesort(SqList &L,int low,int high){
    
    
	if(low<high){
    
    
		int mid=(low+high)/2;            //从中间划分两个子序列
		Mergesort(L,low,mid);           //对左侧子树进行递归排序
		Mergesort(L,mid+1,high);         //对右侧子树进行递归排序
		Merge(L,low,mid,high);          //归并
	}
}

int main(){
    
    
	SqList L;
	Elemtype stuents[10]={
    
    {
    
    "张三",649},{
    
    "李四",638},{
    
    "王五",665},{
    
    "赵六",697},{
    
    "冯七",676},
		{
    
    "读者",713},{
    
    "阿强",627},{
    
    "杨曦",649},{
    
    "老六",655},{
    
    "阿黄",604}};
	//这一部分忘了的请回顾我的相关博客
	printf("初始化顺序表并插入开始元素:\n");
	InitList(L);         //这时是一个空表,接下来通过插入元素函数完成初始化
	for (int i = 0; i < 10; i++)
		ListInsert(L, i + 1, stuents[i]);
	DispList(L);
	printf("根据分数进行归并排序后结果为:\n");
	int low=0,high=L.length-1;
	Mergesort(L,low,high);
	DispList(L);
}

2. Test radix sort

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MaxSize 50       //定义队列元素的最大个数
//定义学生结构
typedef struct {
    
    
	char name[20];              //姓名
	int  grade;               //分数
} Elemtype;

//声明链表
typedef struct LNode
{
    
    
	Elemtype data;
	struct LNode *next;
}LNode,*LinkNode;

//和上次实验的不同,上次实验初始化是建立一个头结点把next置为空
//这里用LinkNode L表示一个链表,用LNode *表示一个结点(该思路来源于王道考研)
//不带头结点链表的初始化
bool InitList1(LinkNode &L)
{
    
    
	L=NULL;
	return true;
}

//带头结点链表的初始化
bool InitList2(LinkNode &L){
    
    
	L=(LNode *)malloc(sizeof(LNode));
	if(L==NULL)
		return false;
	L->next=NULL;
	return true;
}

//尾插法建立单链表
void CreateListR(LinkNode &L,Elemtype a[],int n)
{
    
    
	LNode *s,*r;                      //r位始终指向尾结点的指针,而s为指向要插入结点的过度指针
	//头节点已存在,不再在这里建立了
	r=L;                       //r始终指向尾节点,但初始时指向头节点(初始时头节点即为尾节点)
	for(int i=0;i<n;i++)
	{
    
    
		s=(LNode * )malloc(sizeof(LNode));  //创建数据新节点
		s->data=a[i];                            //将数组元素赋值给新节点s的数据域
		r->next=s;                               //将s放在原来尾指针r的后面
		r=s;
	}
	r->next=NULL;                              //插入完成后,尾节点的next域为空
}

//头插法建立单链表
void CreateListF(LinkNode &L,Elemtype a[],int n)
{
    
    
	LNode *s;
	//头节点已存在,不再在这里建立了
	for(int i=0;i<n;i++)
	{
    
    
		s=(LNode * )malloc(sizeof(LNode));   //创建数据新节点
		s->data=a[i];                              //将数组元素赋值给s的数据域
		s->next=L->next;                          //将s放在原来L节点之后
		L->next=s;
	}
}
/*头插法和尾插法一定要画图弄清思路*/

//按序号查找结点
LNode *GetElem(LinkNode &L,int i){
    
    
	if(i<1)
		return NULL;
	int j=1;
	LNode *p=L->next;
	while(p!=NULL && j<i){
    
    
		p=p->next;
		j++;
	}
	return p;
}

//插入数据元素
bool ListInsert(LinkNode &L,int i,Elemtype e)
{
    
    
	/*在链表L的第i个位置上插入新元素e*/
	int j=0;
	LNode *p=L,*s;      //p开始指向头节点,s为存放数据新节点
	if(i<=0)               //位置不对就报错
		return false;
	while(j<i-1 && p!=NULL)       //定位,使p指向第i-1个节点
	{
    
    
		j++;
		p=p->next;
	}
	if(p==NULL)                 //如果没找到第i-1个节点就报错
		return false;
	else                        //成功定位后,执行下面操作
	{
    
    
		s=(LNode * )malloc(sizeof(LNode));          //创建新节点s,其数据域置为e
		s->data=e;
		s->next=p->next;                                //创建的新节点s放在节点p之后
		p->next=s;
		return true;
	}
}

//输出线性表
void DispList(LinkNode &L)
{
    
    
	LinkNode p=L->next;                   //p指向首节点
	while(p!=NULL)                         //p不为空就输出p节点的data域
	{
    
    
		printf("%s        %d\n",p->data.name,p->data.grade);
		p=p->next;	                       //p移向下一位节点
	}
	printf("-------------------------------\n");
}

//定义循环顺序队列结构体
typedef struct {
    
    
	Elemtype data[MaxSize]; //存放队中元素
	int front, rear; //队头和队尾的伪指针
} SqQueue, *Saquene; //顺序队类型

//初始化队列
void InitQueue(Saquene *q) {
    
    
	*q = (SqQueue *) malloc(sizeof(SqQueue)); //申请一个顺序队大小的空队列空间
	(*q)->front = (*q)->rear = 0; //队头和队尾的伪指针均设置伪-1
}

//销毁队列
void DestroyQueue(Saquene q) {
    
    
	free(q); //释放q所占的空间即可
}

//判断空队列
int QueueEmpty(Saquene q) {
    
    
	return (q->front == q->rear);
}

//进队列
int enQueue(Saquene q, Elemtype *e) {
    
    
	if ((q->rear + 1) % MaxSize == q->front) //队满上溢出报错
		return 0;
	q->rear = (q->rear + 1) % MaxSize; //队尾增1
	q->data[q->rear] = *e; //rear位置插入元素e
	return 1;
}

//出队列
int deQueue(Saquene q, Elemtype *e) {
    
    
	if (q->rear == q->front) //队空下溢出报错
		return 0;
	q->front = (q->front + 1) % MaxSize; //队头增1
	*e = q->data[q->front];
	return 1;
}

//基数排序
void Basesort(LinkNode &L, Saquene queue[]) {
    
    
	for (int radix = 1; radix <= 100; radix *= 10) {
    
     // 对每个位数进行排序
		// 入队列
		LinkNode p = L->next;
		while (p != NULL) {
    
    
			int i = (p->data.grade / radix) % 10;
			enQueue(queue[i], &(p->data));
			p = p->next;
		}
		// 出队列
		LinkNode tail = L;
		for (int i = 0; i < 10; i++) {
    
    
			while (!QueueEmpty(queue[i])) {
    
    
				Elemtype e;
				deQueue(queue[i], &e);
				LinkNode newNode = (LinkNode)malloc(sizeof(LNode));
				newNode->data = e;             //不断的“收集”
				newNode->next = NULL;
				tail->next = newNode;        //tail相当于一个头结点
				tail = newNode;
			}
		}
	}
}
/*这里容易产生一个思维误区,由于我们这里是链表,它是一个一个结点构成的,在我们进队操作时,原来的链表相当于被我们打散了
  分成一个个结点插入不同的队列中,在后面收集的出队过程中,我们又把每个结点拿出来重新链接,所以最后结果就相当于在原来的线性表L上修改*/
int main(){
    
    
	LinkNode L;
	Elemtype stuents[10]={
    
    {
    
    "张三",649},{
    
    "李四",638},{
    
    "王五",665},{
    
    "赵六",697},{
    
    "冯七",676},
		{
    
    "读者",713},{
    
    "阿强",627},{
    
    "杨曦",649},{
    
    "老六",655},{
    
    "阿黄",604}};
	printf("初始化顺序表并插入开始元素:\n");
	InitList2(L);         //这时是一个空表,接下来通过插入元素函数完成初始化
	for (int i = 0; i < 10; i++)
		ListInsert(L, i + 1, stuents[i]);
	DispList(L);
	//这一步是验证了插入函数是正确的,我们通过for循环插入
	//同时,我还写了头插法和尾插法建立单链表,可以直接使用这两个算法建立单链表,把students[]作为参数传入即可
	Saquene queue[10];            //定义十个队列
	for(int i=0;i<10;i++){
    
    
		Saquene a;
		InitQueue(&a);
		queue[i]=a;
	}
	
	Basesort(L, queue); // 基数排序
	DispList(L); // 输出排序后的链表
	
	return 0;
	
}

6. Test results

1. Merge sort test results

insert image description here

2. Radix sort test results

insert image description here

Guess you like

Origin blog.csdn.net/weixin_51496226/article/details/131792054