初级排序算法

本文深入讲解排序算法原理,包括选择排序、冒泡排序、插入排序、希尔排序等,探讨其时间复杂度、空间复杂度及应用场景,是算法学习者的宝贵资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序简介

排序就是将一组对象通过某种逻辑顺序重新排序的过程。排序在生活是随处可见的,那么如何在某种场景下选择出最适合且高效的排序需要掌握各种排序的优劣势。尽管现在第三方类库、工具都集成了排序,不需要程序员自己实现,但是排序的思想所提供的一种程序化的算法思路是无论什么时代都应该去学习、去实践的。

大O表示法

在介绍大O表示法时应该先明白两个概念:时间复杂度和空间复杂度。
时间复杂度:指一个算法在执行的过程中所需要的计算工作量(执行时间)
空间复杂度:指一个算法在执行的过程中所需要的内存空间。
大O表示法表示程序的执行时间或占用空间随数据规模的增长趋势,它一般是与被操作数有关,它的展示形式如n²,log(n)。同时在描述一个算法的时间复杂度时,大家会想明明选择排序的时间复杂度为n²/常数,为什么最后被描述成了n²呢?其实这正是大O表示法的规定,常数是忽略不计的。因为在被操作数很大情况下常数计算与否确实不会对算法的时间复杂度造成偏差。而常见的复杂度如n²和log(n)也不会因为常数的忽略不计而造成大小反转的情况。
在下文中我在使用大O表示法时会把常数带上,这会帮助大家更易于通过时间复杂度来反推算法的执行过程。

实现

排序基类

/**
 * @Description:排序练习基类
 * @Auther: guopengfei
 * @Date: 2019/7/8
 */
public abstract class BaseSort implements Sortable{
	//排序过程中的比较次数及交换次数
	protected int comparisonTimes = 0, exchangeTimes = 0;
	/**
	 * 比较两数大小
	 */
	protected boolean less(Comparable c1, Comparable c2) {
		return c1.compareTo(c2) < 0;
	}
	/**
	 * 交换数组两下标的值
	 */
	protected void swap(Comparable[] a, int i, int j) {
		Comparable temp = a[i];
		a[i] = a[j];
		a[j] = temp;
	}
	/**
	 * 打印排序好的数组
	 */
	protected void show(Comparable[] arr) {
		System.out.println(Arrays.toString(arr));
		System.out.println("comparisonTimes=" + comparisonTimes);
		System.out.println("exchangeTimes=" + exchangeTimes);
	}
	/**
	 * 判断数组是否有序
	 */
	protected boolean isSorted(Comparable[] arr) {
		for (int i = 0; i < arr.length - 1; i++) {
			if (!less(arr[i], arr[i + 1])) {
				return false;
			}
		}
		return true;
	}
	/**
	 * 排序抽象方法
	 */
	protected abstract void sort(Comparable[] arr);
}
选择排序

选择排序是排序算法的入门算法。它的优点是易理解、好实现;缺点是面对被操作数庞大的情况效率极低。
排序过程:对于一个无序数组{4,9,5,3}。当前索引为0,首先找到数组中最小的值3,然后将这个最小值与索引0元素交换位置即4与3交换位置{3,4,9,5},因为这时数组索引0的元素已经是整个数组最小的了,所以它不需要再参加排序操作了。其次从索引1开始,在{4,9,5}中寻找最小值与4交换位置(如果这个元素为数组最小值时则它自己与自己交换),如此反复,直到索引到达数组最后一个元素则排序也完成了。

/**
 * @Description:选择排序实现
 * @Auther: guopengfei
 * @Date: 2019/7/8
 */
public class SelectSort extends BaseSort implements Sortable{

	@Override
	public void sort(Comparable[] arr) {
		for (int i = 0; i < arr.length; i++) {
			int min = i;
			for (int j = i; j < arr.length; j++) {
				comparisonTimes++;
				if (less(arr[j], arr[min])) {
					min = j;
				}
			}
			swap(arr, i, min);
			exchangeTimes++;
		}
		show(arr);
	}
}

选择排序示例图

i591376284
0195376284
1125376984
2123576984
3123476985
4123456987
5123456789
6123456789
7123456789
8123456789

红色元素是选择排序中需要交换的次数,对角线左下方的所有元素为已经有序的元素,对角线右上角的元素是需要比较的元素(未排序)。

效率

  1. 时间复杂度为n²/2,n*(n-1)*(n-2)… = n²/2 即对角线右上角的元素
  2. 比较次数为n²/2,n*(n-1)*(n-2)… = n²/2 即对角线右上角的元素都进行比较
  3. 交换次数为n,也就是对角线上的元素是交换的地方(在诸多排序算法中是很少的交换次数)
冒泡排序

排序过程:对于一个无序数组,从数组左侧开始,将第一个与第二个元素相比较,如果当前元素较大,则交换两者位置,再用第二个元素与第三个元素相比较,依次类推,这样遍历一次数组就会有一个最大值被交换到了数组的最右端。接着再从数组左侧开始比较、交换,每次交换到数组右端的最大值不用参加下次排序操作。

/**
 * @Description:冒泡排序实现
 * @Auther: guopengfei
 * @Date: 2019/7/29
 */
public class BubbleSort extends BaseSort implements Sortable{
	@Override
	protected void sort(Comparable[] arr) {
		for (int i = 0; i < arr.length; i++) {
			for (int j = 0; j < arr.length-i-1; j++) {
				comparisonTimes++;
				if(!less(arr[j],arr[j+1])){
					swap(arr,j,j+1);
					exchangeTimes++;
				}
			}
		}
		show(arr);
	}
}

冒泡排序示例图

i591376284
0513762849
1135627489
2135264789
3132546789
4123456789
5123456789
6123456789
7123456789
8123456789

效率

  1. 时间复杂度也是平方级别的n²/2,n*(n-1)*(n-2)… = n²/2 即红色对角线左上角的元素
  2. 比较次数为n²/2,n*(n-1)*(n-2)… = n²/2 即对角线左上角的元素都进行比较
  3. 交换次数为n²/4,因为在要交换元素中有可能这个元素是数组的开头,要走n、n-1才能到合适的位置,亦或者这个元素已经在合适的位置上,则一次也不用交换。所以平均交换次数为n²/2/2即n²/4
插入排序

排序过程:从数组左侧第一个数开始,依次跟它前面的数比较大小,若它小于前一个元素,则交换它与前一个元素的位置,接着再与上一个数比大小,直到放到合适的位置或走到数组的最左侧为止。当最后一个元素也放到了适合的位置上,排序也就完成了。

倒置:插入排序引入倒置的概念,倒置即数组中顺序颠倒的两个元素,即对于数组{4,3,2,1}中有两对倒置,分别是4-1,3-2

/**
 * @Description:插入排序实现
 * @Auther: guopengfei
 * @Date: 2019/7/10
 */
public class InsertSort extends BaseSort implements Sortable {
	@Override
	protected void sort(Comparable[] arr) {
		for (int i = 0; i < arr.length; i++) {
			for (int j = i; j>0 && less(arr[j],arr[j-1]); j--) {
				swap(arr,j,j-1);
				comparisonTimes++;
				exchangeTimes++;
			}
			comparisonTimes++;
		}
		show(arr);
	}
}

效率

  1. 时间复杂度为N²/4
  2. 交换次数与数组中倒置的数量相同。
  3. 每次交换都意味着一次比较,但每次比较都会比交换多一次(即当前元素不小于前一个元素且未到数组左端),则1到N-1之间都有可能有这一次额外的比较所以比较的次数大于等于倒置的数量,小于倒置的数量+N-1。
希尔排序

希尔排序是插入排序的一种变体,又称“缩小增量排序”。插入排序对基本有序的数组排序效率是很高的,固希尔排序先将数组变为基本有序的数组,再对其进行插入排序。当n很大时,希尔排序的效率要远远高于插入排序。
排序过程:先将数组变为h有序数组,h为递增序列(增量),什么是h有序数组?对于数组任意间隔h的元素都是有序的。h = 3 *h +1(1,4,13,40…)这个公式是总结出来当h取这些值可以有效的提高算法的性能,但其实目前并无法证明h这样取值是最好的。其思路与插入排序是一致的,只是增量不同,插入排序的增量为1,而希尔排序的增量是h。当h=1时,即为插入排序。
h有序数组示例

GUOPENGFEI
下标0123456789
h=4EIGFENOPGU
h=1EEFGGINOPU

其中h=4时,下标0,4,8三个组成一个h有序数组,它们的值EGU是有序的。同理下标1,5,9也为子h有序数组,它们的值也是有序的,以此类推。这就是h有序数组。

/**
 * @Description:希尔排序实现
 * @Auther: guopengfei
 * @Date: 2019/7/11
 */
public class ShellSort extends BaseSort implements Sortable {
	@Override
	protected void sort(Comparable[] arr) {
		int h = 1, n = arr.length;
		while (h < n / 3) {
			h = h * 3 + 1;
		}
		while (h >= 1) {
			for (int i = h; i < n; i++) {
				for (int j = i; j >= h && less(arr[j], arr[j - h]); j -= h) {
					swap(arr, j, j - h);
					comparisonTimes++;
					exchangeTimes++;
				}
				comparisonTimes++;
			}
			h = h / 3;
		}
		show(arr);
	}
}

效率
Shell算法的性能与所选取的分组长度序列有很大关系。只对特定的待排序记录序列,可以准确地估算关键词的比较次数和对象移动次数。想要弄清关键词比较次数和记录移动次数与增量选择之间的关系,并给出完整的数学分析,今仍然是数学难题。——引用百度百科

### 初级 CSP 信奥赛排序算法题目 #### 题目描述: 在一个初级的信息学奥林匹克竞赛 (CSP) 中,有一道常见的题型就是编写简单的排序程序。例如: **【题目】** 给定一组整数序列,你需要将这组数据按照从小到大的顺序排列,并输出排好序的结果。 输入示例:`5\n4 8 2 9 6` 输出示例:`2 4 6 8 9` 这里给出了一种基础的解决思路——冒泡排序法(Bubble Sort),它是一种简单直观但是效率较低的经典排序算法之一。 --- ### 冒泡排序原理简介 1. **基本思想** 比较相邻元素大小并交换位置;一轮遍历结束后最大值会“浮”到最后一位; 2. **步骤说明** - 设数组长度为 n,则需要进行 n−1 轮比较操作。 - 第 i 趟从前往后依次两两比较第 i 和第 i+1 的数值, 如果前者大于后者则互换它们的位置。 3. **代码实现(C语言)** ```c #include <stdio.h> void bubbleSort(int arr[], int length){ for (int step = 0; step < length-1; ++step){ // 控制轮次 bool flag=false; for (int i=0;i<length-step-1;++i){ // 相邻两个数字逐一比较 if(arr[i]>arr[i+1]){ int temp=arr[i]; arr[i]=arr[i+1]; // 交换位置 arr[i+1]=temp; flag=true; } } if(!flag) break; // 若某趟未发生任何变化直接跳出循环提高性能 } } int main(){ int N; scanf("%d", &N); int nums[N]; for(int i = 0 ; i<N ;++i) scanf("%d",&nums[i]); bubbleSort(nums,N); printf("Sorted array: "); for (size_t i = 0; i < N; i++) printf("%d ", nums[i]); return 0; } ``` 这个例子演示了如何通过 C 程序读取用户输入的一串数字作为待处理的数据集,然后应用上面提到的基本冒泡排序逻辑对其进行升序整理后再打印出来。 --- 为了帮助初学者更好地理解和掌握这类问题,建议练习以下几个方面的知识点和技术点: - 学习其他常见排序方法如选择排序、插入排序等及其应用场景的区别分析。 - 掌握时间复杂度的概念以及计算公式用于评估各种排序方案之间的优劣对比。 - 尝试优化现有的解决方案,在保证正确性的前提下尽可能减少不必要的运算量达到更高效的执行效果。 - 练习更多的样例测试用例验证自己的程序是否能够稳定可靠地工作于所有合理的边界条件之下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值