算法笔记学习Part3:入门篇 2 排序(待补充)

4.1 排序

4.1.0 冒泡排序

冒泡排序是排序算法中最基础的一种,其本质在于交换:每次通过交换将剩余元素的最大(小)值移动到一端,而当剩余元素减少为0时,排序结束。

1.下面是冒泡排序的常规实现:

for (int i = 0; i < list.length-1; i++) {
	for (int j = 0; j < list.length-1-i; j++) {//也可以是 int j = i+1; j < list.length; j++ ,将元素归位到前端 
		if (list[j] < list[j+1]) {
			temp = list[j+1];
			list[j+1] = list[j];
			list[j] = temp;
		}
	}
}

在如上的排序中,每次大循环都会将最大或最小(根据交换中设置的比较规则)的元素归位,每次小循环从头开始直到被归为元素之前每两个元素进行比较后完成交换

2.冒泡排序的优化(方法来自大话数据结构)
冒泡排序的常规实现,不论过程中是否已经完成排序,接下来的循环都会进行,因此存在效率上的损失。如下方法对冒泡排序进行了改进:

bool flag = true;
for (int i = 0; i < list.length-1 && !flag; i++) {
	flag = true;
	for (int j = 0; j < list.length-1-i; j++) {
		if (list[j] < list[j+1]) {
			temp = list[j+1];
			list[j+1] = list[j];
			list[j] = temp;
			flag = false;
		}
	}
}

该方法中定义了布尔变量flag用于标识排序是否已经完成。若在一轮循环中没有发生任何交换,则flag的值为true,会在下一轮的准入判断中脱出循环。而一旦有任一例交换发生,则下一轮循环继续进行。

3.冒泡循环的再优化(方法来自https://www.cnblogs.com/jyroy/p/11248691.html)
上述改进的冒泡循环中,内层循环仍存在多余的交换。为了避免这种情况,可以利用一个标志位,记录一下当前第 i 趟所交换的最后一个位置的下标,在进行第 i+1 趟的时候,只需要内循环到这个下标的位置就可以了,因为后面位置上的元素在上一趟中没有换位,这一次也不可能会换位置了。基于这个原因,我们可以进一步优化我们的代码。

int len = list.length - 1;
bool flag = true;
int tempPosition = 0;//记录交换发生的位置
for (int i = 0; i < list.length-1 && !flag; i++) {
	flag = true;
	for (int j = 0; j < len; j++) {
		if (list[j] < list[j+1]) {
			temp = list[j+1];
			list[j+1] = list[j];
			list[j] = temp;
			flag = false;
			tempPosition = j;//记录交换发生的位置
		}
	}
	len = tempPosition;//将最后一次发生交换的位置给len,缩减内循环次数
}

4.1.1 选择排序

选择排序是最简单的排序算法之一,在书中介绍了最常用的简单选择排序算法
简单排序算法是指:对于一个序列A中的元素A[1]~A[n],令i从1到n枚举,进行n趟操作。每一趟从待排序的部分[i,n]中选择最小的元素,令其与待排序部分的第一个元素A[i]进行交换,这样A[i]就会与当前有序区间[1,i-1]形成新的有序区间[1,i]。于是在n趟排序后,所有元素就会是有序的。
下面是简单排序算法的编程实现:

for (int i = 1; i < n; i++) {
	int k = i;				//k用于标记待排序部分中最小的元素
	for (int j = i; j < n; j++) {
		if (A[j] < A[k]) {
			k = j;
		}
	}
	//将k标记的最小元素和待排序部分第一个元素交换,之后已排序部分扩张
	int temp = A[i];
	A[i] = A[k];
	A[k] = temp;
}

4.1.2 插入排序

插入排序也是最简单的一类排序算法。 插入排序的基本方法是:每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止。在书中介绍了最直观的直接插入排序方法。
直接插入排序是指:对于序列A的n个元素A[1] ~ A[n],令i从2到n枚举,进行n-1趟操作。假设某一趟时,序列A的前i-1个元素A[1] ~ A[i-1]已经有序,而范围i到n还未有序,那么该趟从[1,i-1]中寻找某个位置j,使得将A[i]插入位置j后,范围[1,i]的元素有序。
下面是直接插入排序算法的编程实现:

int A[maxn],n;								//n为元素个数,数组下标为1到n 
void insertSort() {							
	for (int i = 2; i <= n; i++) {			//进行n-1趟排序 
		int temp = A[i],j = i;				//temp临时存放A[i],j从i开始往前枚举 
		while( j > 1 && temp < A[j-1] ) {	//只要temp小于前一个元素A[j-1]
			A[j] = A[j-1];					//将A[j-1]后移一位至A[j] 
			j--;
		}
		A[j] = temp;						//插入位置为j 
	}
}

4.1.3 排序题和sort函数的应用(待更新)

一般在试题中都不需要自己构建排序函数,只需要的到排序的最终结果即可,因此可以直接使用qsort/sort函数进行排序

sort排序函数的使用(6.9.6节内容)

sort是C++中提供的排序函数,根据具体情形使用不用的排序方法,效率较高,且在实现中规避了经典快速排序可能出现的导致时间复杂度退化到O(n^2)的极端情况
1.如何使用sort排序
基本格式:

sort(首元素地址,尾元素地址的下一个地址,比较函数);//前两个部分必填,比较函数根据需要添加,没有时默认递增排序

使用案例:

#include
#include

using namespace std; 

int main() {
	int a[6] = {9,4,2,5,6,-1};
	//将a[0]~a[3]从大到小排序
	sort(a,a+4);
	for (int i = 0; i < 6; i++) {
		printf("%d ",a[i]);
	}
	printf("\n");
	//将a[0]~a[5]从小到大排序
	sort(a,a+6);
	for (int i = 0; i < 6; i++) {
		printf("%d ",a[i]);
	}
	printf("\n");

	//对char类型排序时默认为字典序
	
	return 0;
}

2.比较函数cmp
在使用sort函数进行排序时,若比较对象本身缺少可比性时,则需要制定规则来建立可比性。一般会使用sort函数的第三个参数比较函数cmp来实现自定义的比较规则
cmp函数的基本形式如下:

bool cmp(参数A,参数B) {
	return A>B(A<B);//<为升序排列,>为降序排列
}

也可以根据结构体进行一定的变体并添加一些判断规则:

bool cmp(node a,node b) {
	if (a.x != b.x) return a.x > b.x;
	else return a.y < b.y;
}

3.C++容器的排序(待补充)

排序题的常用解题步骤

1.构建相关结构体

排序题中的排序队对象常常涉及到多个属性,因此可以构建结构体来解题,方便代码的编写

2.cmp函数的编写

根据题目的排序规则来编写适当的比较函数

3.排名的实现

部分排序题中,需要计算出个体排名,一般需要在构建结构体时将排名这项属性加入到结构体中。一般的规则是:分数不同排名不同,分数相同排名相同但占用一个排位。在数组排序完成后,可用以下方法实现排名的计算:

  1. 从数组第一个个体开始顺次遍历整个数组,若当前个体和前一个个体分数相同,则排名等同于前一个个体;否则,当前个体排名等于数组下标加一(从下标0开始)
  2. 而有时题目中不一定需要真的把排名记录下来,而是直接输出即可,那么也可以用这样的办法:令int型变量r初值为1,然后遍历所有个体:如果当前个体不是第一个个体且当前个体的分数不等于上一个个体的分数,那么令r等于数组下标加1,这时r就是当前个体的排名,直接输出即可。这样的做法适用于需要输出的信息过多,导致第一种方法代码冗长的情况
例题 - 【PAT A1025】PAT Ranking(待补充)

你可能感兴趣的:(算法学习)