数据结构快速教程4—排序


排序

在这里插入图片描述

在这里插入图片描述

qsort库函数排序

qsort函数包含四个参数,分别是:

1、数组名

2、元素个数(从前往后计算)

3、数组元素所占字节(int,double,char等所占字节),直接sizeof即可

4、排序原则,这里需要构造一个cmp函数,这是最关键的部分。

我们首先看一个简单的例子:从小到大排序。

#include <stdlib.h>

int cmp1(const void* a, const void* b){
    //从小到大排序
    return *(int*)a - *(int*)b;
}

int cmp2(const void* a, const void* b){
    //从大到小排序
    return *(int*)b - *(int*)a;
}

//qsort(数组名, 数组元素个数, sizeof(数组元素类型), cmp);

如果要从大到小排序,就要反过来,就像上面的cmp2。记住,小的在前,大的在后,如果是从小到大排序,就是return前面的减后面的。

注意:非常易错!cmp函数的返回值类型一定是int,千万不是void!非常易错!这是qsort中最关键的部分!

下面我们看一个复杂一点的例子。数组的元素是结构体时,要按照一定的指标进行稳定排序。

int cmp1(const void* a, const void* b) {
	Student s1 = *(Student*)a;
	Student s2 = *(Student*)b;
	if (s1.seat == s2.seat) {
		return s1.num - s2.num;
	}
	return s1.seat - s2.seat;
}

假如要字符串排序,字符串相减肯定是不行的。正确的是返回strcmp。永远记住,cmp函数的返回类型永远是Int.

插入排序

原理

在这里插入图片描述

完整代码

void insertSort(keytype k[], int n)
{
       int i, j;
       keytype temp;
       for(i = 1; i < n; i++){
           temp = k[i];
           for(j = i - 1; j >= 0 && temp < k[j]; j--){
               k[j+1] = k[j];
           }                  
           k[j+1] = temp;
       }
}

折半插入排序法

void insertBSort(keytype k[], int n)
{      
    int i, j, low, high, mid;
    keytype temp;
    for(i = 1; i < n; i++){
        temp = k[i];            
        low = 0;
        high = i - 1;
        while(low <= high){
            mid = (low + high) / 2;
            if(temp < k[mid]){
                high = mid - 1;
            }else{
                low = mid + 1;
            } 
        }
        for(j = i - 1; j >= low; j--){
            k[j+1] = k[j];
        }      
        k[low] = temp;
    }
}

采用插入排序的方法确定插入的位置。

选择排序

在这里插入图片描述

代码太简单了。略。

冒泡排序

在这里插入图片描述

完整代码

void  bubbleSort(keytype k[],int n)
{     
    int i, j, flag = 1;
    keytype temp;
    for(i = n - 1; i > 0 && flag == 1; i--){
        flag = 0; //每趟排序前标志flag置0 
        for(j = 0; j < i; j++){
            if(k[j]>k[j+1]){
                temp=k[j];
                k[j]=k[j+1];
                k[j+1]=temp;	//交换两个元素的位置     	
                flag=1; 		//标志flag置1 
        	}
        }
     }
 }

谢尔(Shell)排序

原理

在这里插入图片描述

完整代码

void shellSort(keytype k[],int n)
{     
    int i, j, flag, gap=n;
    keytype  temp; 
    while(gap>1){
        gap=gap/2;
        do{
            flag=0;	//每趟排序前,标志flag置0
            for(i=0;i<n–gap;i++){
                j=i+gap;
                if(k[i]>k[j]){
                    temp=k[i];
                    k[i]=k[j];
                    k[j]=temp;
                    flag=1;  
                }
            }                            
        }while(flag!=0);      
    }
}

堆排序

在这里插入图片描述

完整代码

void adjust(keytype k[],int i,int n)
{
    int j;
    keytype temp;
    temp=k[i];
    j=2*i+1;
    while(j<n){
    	if(j+1<n && k[j]<k[j+1]){
            j++;
        }
        if(temp<k[j]){
            k[(j-1)/2]=k[j];
            j=2*j+1;
        }else{
            break;
        }              
    }
    k[(j-1)/2]=temp;
}

void heapSort(keytype k[], int n){
	int i;
	keytype temp;

	for (i = n / 2 - 1; i >= 0; i--)
		adjust(k, i, n);
	for (i = n - 1; i >= 1; i--) {
		temp = k[i];
		k[i] = k[0];
		k[0] = temp;
		adjust(k, 0, i);
	}
}

adjust函数

功能:向下调整结点i的位置,使得其祖先结点值都比其大。如果一棵树仅根结点i不满足堆条件,通过该函数可将其调整为一个堆。

K :序列

i:被调整的二叉树的根的序号

n 被调整的二叉树的结点数目

稳定性:不稳定;时间复杂度:O(nlog2n);空间代价:O(1)

二路归并(Merge)排序法

在这里插入图片描述

特点

第一趟每两个元素是有序的,第二趟每四个元素是有序的…

排序趟数是log2n

完整代码

mergeSort是主函数

int merge(keytype x[], keytype tmp[], int left, int leftend, int rightend)
{
	int i = left, j = leftend + 1, q = left;
	while (i <= leftend && j <= rightend) {
		if (x[i] <= x[j]) {			
			tmp[q++] = x[i++];
		}			
		else {
			tmp[q++] = x[j++];
		}			
	}		
	while (i <= leftend) {
		tmp[q++] = x[i++];
	}		
	while (j <= rightend) {
		tmp[q++] = x[j++];
	}		
	for (i = left; i <= rightend; i++)
		x[i] = tmp[i];
}

void mSort(keytype k[], keytype tmp[], int left, int right)
{
    int center;
    if(left < right){
        center = (left+right)/2;
        mSort(k, tmp, left, center);
        mSort(k, tmp, center+1, right);
        merge(k, tmp, left,center, right);
    }
}

void mergeSort(keytype k[],int n)
{
    //主函数
    keytype *tmp;
    tmp = (keytype *)malloc(sizeof(keytype) * n);
    if(tmp != NULL) {
        mSort(k, tmp, 0, n-1);
        free(tmp);
    } 
    else
        printf(“No space for tmp array!!!\n”);
}

稳定性:稳定;时间:O(nlog2n);空间:O(n)

快速排序

特点

在这里插入图片描述

虽然我们不要求手搓代码,但是这里经常会出一些有趣的题目,我们要知道每一步的规律。

非常重要:快排的阶段性排序结果的特点是,第i趟完成时,会有i个及以上的数出现在它最终将要出现的位置,即它左边的数都比它小,它右边的数都比它大。

根据这个重要特点,我们来看下面的题:

在这里插入图片描述

从小到大。题目问第二趟排序的结果,第二趟结束时,必有两个或两个以上的元素“已就位”,也就是说这个元素左边的所有元素都比它小,右边的所有元素都比它大。A:2、3、6、7、9均符合,所以A排除;B:2、9符合;C:只有一个元素9符合。所以C不可能,选C。D:5、9符合。

完整代码

void swap(keytype* a, keytype* b)
{
	keytype tmp = *a;
	*a = *b;
	*b = tmp;
}

void qSort(keytype v[], int left, int right)
{
	int i, last;
	if (left < right) {
		last = left;
		for (i = left + 1; i <= right; i++) {cnt1++;
			if (v[i] < v[left]) {				
				swap(&v[++last], &v[i]);
			}				
		}			
		swap(&v[left], &v[last]);
		qSort(v, left, last - 1);
		qSort(v, last + 1, right);
	}
}

void quickSort(keytype k[], int n){
    //主函数
    qSort(k, 0, n-1);
}

稳定性、时间复杂度、空间复杂度

在这里插入图片描述

应用1-排座位(简)a

【问题描述】

某班级要进行期末考试,准备考试时打乱座次,现已按照学号顺序人工为学生随机安排了座位号,但其中可能会出现漏排和重复安排座位的情况。编写程序读入人工安排的考试座位安排表T1,对安排情况进行检查,并对漏排和重复安排座位的情况进行修正,修正后,若学生人数为N,则每位学生考试座位安排应在1~N之间,其中没有缺号和重号。假设T1中学号信息不会出现重复,同一座位号最多有两位同学的座位号相同,并且座位号不会连续漏排;初始考试座位安排表存放在当前目录下的in.txt中,其中包括每位学生的学号、姓名和座位号,要求修正后的考试座位安排表输出到当前目录下的out.txt文件中。程序检查座位号的规则如下:

1、首先对考试座位安排表T1按座位号从小到大的顺序排序(原始考试安排可能有座位号相同情况,座位号相同时则按原始学号顺序排序),得到按座位号排序的安排表T2;

2、对表T2从头开始检查漏排座位号情况:假设当前表中安排的最大座位号为M,取M和N的较小值Q;从1号开始检查,若某个小于等于Q的座位序号没有安排学生,则将表T2的最后学生的座位设置为该座位号;若存在多个漏排座位,则从表T2最后依次向前设置;

3、然后再检查表T2中重排座位号情况:假设当前表中安排的最大座位号为m,将座位号重复的、学号较大的学生的座位号依次设置为m+1、m+2、m+3…;

4、将调整好的表T2按学号由小到大序排序后按输出格式要求输出至指定输出文件中。

【输入形式】
从标准输入中读入学生人数(不超过100的正整数)。

初始考试座位安排表存储在当前目录下的in.txt文件中,已按照学号由小到大的顺序分行存储每位学生座位信息,依次为学生学号(不超过8位的正整数)、姓名(由不超过20位的英文字母组成)和座位号(不超过100的正整数),各数据间以一个空格分隔。最后一个学生座位信息后有回车换行。
【输出形式】
按照学号由小到大的顺序将修正后的考试座位安排表输出到当前目录下的out.txt文件中,每行依次为学号、姓名和座位号,各数据之间以一个空格分隔。
【样例输入】

24

假设当前目录下的in.txt文件内容如下:
18373001 ShiTian 7
18373002 WangLi 15
18373003 LiGuoHong 23
18373005 QianSanQiang 26
18373006 ZhangQiang 8
18373007 SunXiXi 2
18373010 LiXing 12
18373011 TangYing 20
18373012 YangYang 4
18373013 ZhaoGang 27
18373014 ZhouLiang 18
18373015 WuShuo 9
18373016 ZhengSiSi 13
18373017 WangGong 27
18373018 TianTian 21
18373020 LeiLei 16
18373021 ZhangLou 10
18373022 WangLei 17
18373025 SunTian 24
18373026 JinXiang 18
18373028 PangHong 11
18373029 GaoLiang 2
18373030 GaoHang 6
18373031 YangYang 22

【样例输出】
当前目录下的out.txt文件内容应为:

18373001 ShiTian 7
18373002 WangLi 15
18373003 LiGuoHong 19
18373005 QianSanQiang 5
18373006 ZhangQiang 8
18373007 SunXiXi 2
18373010 LiXing 12
18373011 TangYing 20
18373012 YangYang 4
18373013 ZhaoGang 3
18373014 ZhouLiang 18
18373015 WuShuo 9
18373016 ZhengSiSi 13
18373017 WangGong 1
18373018 TianTian 21
18373020 LeiLei 16
18373021 ZhangLou 10
18373022 WangLei 17
18373025 SunTian 14
18373026 JinXiang 24
18373028 PangHong 11
18373029 GaoLiang 23
18373030 GaoHang 6
18373031 YangYang 22

【样例说明】
初始考试座位安排表中有24位学生的排位信息,正确情况下这些学生安排的座位号应为1~24。初始人工安排的座位信息有1、3、5、14和19号座位漏排,有2、18和27号座位重复安排学生。先对漏排的座位进行修正:已安排的最大座位号为27,学号为18373017和18373013的学生分别安排了27号座位,按照漏排座位修正规则,先将18373017学生安排在1号座位,再将18373013学生安排在3号座位;同理分别将18373005学生安排在5号座位,将18373025学生安排在14号座位,18373003号学生安排在19号座位。当前安排的最大座位号为22,还有2号和18号座位重复,将2号重复的学号较大的18373029号学生安排在23号座位,将18号重复的学号较大的18373026号学生安排在24号座位。这样修正后按照学号由小到大的顺序输出学生座位信息到out.txt中。

【评分标准】
按照要求修正学生座位信息,提交程序文件名为seat.c。

#define _CRT_SECURE_NO_WARNINGS
#define min(a,b) ((a) < (b) ? (a) : (b))

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

typedef struct _stu {
	int num;	//学号
	char name[20];
	int seat;
}Student;

int cmp1(const void* a, const void* b);
int cmp2(const void* a, const void* b);

int main()
{
	FILE* fp1 = fopen("in.txt", "r");
	FILE* fp2 = fopen("out.txt", "w");
	
	int digit;	//学生人数
	Student stu[100] = { 0 };
	int i, j, k;
	int min1;
	int maxSeat;	//最大座位号

	//input
	scanf("%d", &digit);
	for (i = 0; i < digit; i++) {
		fscanf(fp1, "%d", &stu[i].num);
		getchar();
		fscanf(fp1, "%s", stu[i].name);
		fscanf(fp1, "%d", &stu[i].seat);
	}

	//按座位号从小到大排序
	qsort(stu, digit, sizeof(Student), cmp1);

	//处理漏排
	min1 = min(stu[digit - 1].seat, digit);
	j = digit - 1;
	for (i = 1; i <= min1; i++) {
		for (k = 0; k < digit; k++) {
			if (stu[k].seat == i) {
				break;
			}
			if (stu[k].seat > i) {
				stu[j].seat = i;
				j--;
				break;
			}
		}
	}

	//处理重复
	for (i = 0, maxSeat = stu[0].seat; i < digit; i++) {
		if (stu[i].seat > maxSeat) {
			maxSeat = stu[i].seat;
		}		
	}
	for (i = 1; i < digit; i++) {
		if (stu[i].seat == stu[i - 1].seat) {
			stu[i].seat = (++maxSeat);
		}
	}

	//按照学号排序
	qsort(stu, digit, sizeof(Student), cmp2);

	for (i = 0; i < digit; i++) {
		fprintf(fp2, "%d %s %d\n", stu[i].num, stu[i].name, stu[i].seat);
	}

	fclose(fp1);
	fclose(fp2);

	return 0;
}

int cmp1(const void* a, const void* b) {
	Student s1 = *(Student*)a;
	Student s2 = *(Student*)b;
	if (s1.seat == s2.seat) {
		return s1.num - s2.num;
	}
	return s1.seat - s2.seat;
}

int cmp2(const void* a, const void* b) {
	Student s1 = *(Student*)a;
	Student s2 = *(Student*)b;	
	return s1.num - s2.num;
	
}

本题是具有代表性的基本题。BUAA数据结构期末的编程题第一题就是这种类型。只会情景更复杂,不会更简单。

应用2-整数排序(排序-基本题)

【问题描述】

从标准输入中输入一组互不相同的整数(个数不超过100)及排序方式,按照从小到大排序,输出按某种算法排序的结果及元素的比较次数。

说明:排序方式为一个1~5的整数,分别表示:

1:选择排序,比较次数是指选择未排序部分的最小元素时的比较次数。

2:冒泡排序,比较次数是指相邻元素的比较次数,若某趟排序中没有进行数据交换,就认为排序结束。

3:堆排序,比较次数是指根元素调整过程中根元素与子树根结点的比较次数,即下面算法中红色语句的执行次数:

void adjust(int k[ ],int i,int n)

{

int j,temp;

temp=k[i];

j=2*i+1;

while(j<n){

​ if(j<n-1 && k[j]<k[j+1])

​ j++;

​ if(temp>=k[j])

​ break;

​ k[(j-1)/2]=k[j];

​ j=2*j+1;

}

k[(j-1)/2]=temp;

}

4:二路归并排序,比较次数是指两组有序数据合并成一组时的比较次数,即下面算法中红色语句的执行次数(注意:调用 merge时,要使用上课讲的递归算法):

void merge(int x[ ],int tmp[ ],int left,int leftend,int rightend)

{

int i=left, j=leftend+1, q=left;

while(i<=leftend && j<=rightend)

{

​ if(x[i]<=x[j])

​ tmp[q++]=x[i++];

​ else

​ tmp[q++]=x[j++];

}

while(i<=leftend)

​ tmp[q++]=x[i++];

while(j<=rightend)

​ tmp[q++]=x[j++];

for(i=left; i<=rightend; i++)

​ x[i]=tmp[i];

}

5:快速排序,比较次数是指分界元素与其它元素的比较次数,即下面算法中红色语句的执行次数:

void quickSort(int k[ ],int left,int right)

{

int i, last;

if(left<right){

​ last=left;

​ for(i=left+1;i<=right;i++)

​ if(k[i]<k[left])

​ swap(&k[++last],&k[i]);

​ swap(&k[left],&k[last]);

​ quickSort(k,left,last-1);

​ quickSort(k,last+1,right);

}

}

【输入形式】

首先在屏幕上输入2个整数,分别表示待排序的整数个数及排序方式,然后在下一行依次输入待排序的整数。各整数之间都以一个空格分隔。

【输出形式】

先在一行上输出排序结果,各整数间以一个空格分隔。然后在下一行上输出排序过程中的元素比较次数。

【样例1输入】

20 1
38 356 98 -102 126 46 65 -9 100 0 21 2 90 8 18 12 78 16 189 23

【样例1输出】

-102 -9 0 2 8 12 16 18 21 23 38 46 65 78 90 98 100 126 189 356
190

【样例1说明】

输入了20个整数数据,要求按照选择排序算法对输入的数据进行从小到大排序,输出排序结果,排序过程中元素的比较次数为190次。

【其它样例说明】

若输入的待排序数据与样例1完全相同,要求的排序算法不同,则输出的排序结果与样例1完全一样,但比较次数不同,为了方便说明,下面左侧为排序方式,右侧为对应的比较次数:

2 162

3 58

4 66

5 75

【评分标准】

该题要求按照指定算法对输入的数据进行排序,提交程序名为sort.c。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

void selectSort(int str[], int cnt);
void bubbleSort(int str[], int cnt);
int adjust(int k[], int i, int n);
void heapSort(int k[], int n);
int merge(int x[], int tmp[], int left, int leftend, int rightend);
int mSort(int k[], int tmp[], int left, int right);
void mergeSort(int k[], int n);
void swap(int* a, int* b);
void qSort(int v[], int left, int right);
int cnt = 0;
int cnt1 = 0;

int main()
{
	int num;	//数的个数
	int digit[100] = { 0 };
	int c;	//排序方式
	int i;

	scanf("%d%d", &num, &c);
	for (i = 0; i < num; i++) {
		scanf("%d", &digit[i]);
	}

	switch (c) {
	case 1:selectSort(digit, num); break;
	case 2:bubbleSort(digit, num); break;
	case 3:heapSort(digit, num); break;
	case 4:mergeSort(digit, num); break;
	case 5:qSort(digit, 0, num - 1); 
		for (i = 0; i < num; i++) {
			printf("%d ", digit[i]);
		}
		printf("\n%d", cnt1); 
		break;
	}
	return 0;
}

void selectSort(int str[], int cnt) {
	int n = 0;
	int i, j;

	for (i = 0; i < cnt - 1; i++) {
		for (j = i + 1; j < cnt; j++) {
			n++;
			if (str[j] < str[i]) {
				int swap = str[i];
				str[i] = str[j];
				str[j] = swap;
			}
		}
	}

	for (i = 0; i < cnt; i++) {
		printf("%d ", str[i]);
	}
	printf("\n%d", n);
}

void bubbleSort(int str[], int cnt) {
	int n = 0;
	int i, j;
	int flag = 0;

	for (i = cnt - 1; i > 0; i--) {
		flag = 0;
		for (j = 0; j <= i - 1; j++) {
			n++;
			if (str[j] > str[j + 1]) {
				int swap = str[j];
				str[j] = str[j + 1];
				str[j + 1] = swap;
				flag = 1;
			}
		}
		if (flag == 0) {
			break;
		}
	}

	for (i = 0; i < cnt; i++) {
		printf("%d ", str[i]);
	}
	printf("\n%d", n);
}

int adjust(int k[], int i, int n){
	int j;
	int temp;
	int cnt = 0;

	temp = k[i];
	j = 2 * i + 1;
	while (j < n) {
		cnt++;
		if (j + 1 < n && k[j] < k[j + 1])
			j++;
		if (temp < k[j]) {
			k[(j - 1) / 2] = k[j];
			j = 2 * j + 1;
		}
		else {
			
			break;
		}
	}
	k[(j - 1) / 2] = temp;
	return cnt;
}

void heapSort(int k[], int n){
	int i;
	int temp;
	int cnt = 0;

	for (i = n / 2 - 1; i >= 0; i--)
		cnt += adjust(k, i, n);
	for (i = n - 1; i >= 1; i--) {
		temp = k[i];
		k[i] = k[0];
		k[0] = temp;
		cnt += adjust(k, 0, i);
	}

	for (i = 0; i < n; i++) {
		printf("%d ", k[i]);
	}
	printf("\n%d", cnt);
}

int merge(int x[], int tmp[], int left, int leftend, int rightend)
{
	int cnt = 0;
	int i = left, j = leftend + 1, q = left;
	while (i <= leftend && j <= rightend) {
		cnt++;
		if (x[i] <= x[j]) {			
			tmp[q++] = x[i++];
		}			
		else {
			tmp[q++] = x[j++];
		}			
	}		
	while (i <= leftend) {
		tmp[q++] = x[i++];
	}		
	while (j <= rightend) {
		tmp[q++] = x[j++];
	}
		
	for (i = left; i <= rightend; i++)
		x[i] = tmp[i];

	return cnt;
}

int mSort(int k[], int tmp[], int left, int right)
{
	int center;

	if (left < right) {
		center = (left + right) / 2;
		mSort(k, tmp, left, center);
		mSort(k, tmp, center + 1, right);
		cnt += merge(k, tmp, left, center, right);
	}

	return cnt;
}

void mergeSort(int k[], int n)
{
	int* tmp;
	int i;
	int cnt;
	tmp = (int*)malloc(sizeof(int) * n);
	if (tmp != NULL) {
		cnt = mSort(k, tmp, 0, n - 1);
		free(tmp);
	}

	for (i = 0; i < n; i++) {
		printf("%d ", k[i]);
	}
	printf("\n%d", cnt);
}

void swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void qSort(int v[], int left, int right)
{
	int i, last;
	if (left < right) {
		last = left;
		for (i = left + 1; i <= right; i++) {cnt1++;
			if (v[i] < v[left]) {
				
				swap(&v[++last], &v[i]);
			}				
		}			
		swap(&v[left], &v[last]);
		qSort(v, left, last - 1);
		qSort(v, last + 1, right);

	}
}

总结一下,排序这边从应用的角度,必须要完全掌握qsort库排序。尽量不要在考试或比赛中手写冒泡排序、选择排序等,防止在这种基本的模块中出现bug,浪费重要的时间。

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值