2026/08/05 数据结构9(常用排序算法)

排序的概念

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。可以将数据按某字段规律排列,所谓的字段就是数据节点的其中一个属性。比如一个班级的学生,其字段就包含学号、姓名、班级、分数等等,我们既可以针对学号排序,也可以针对分数排序。

稳定性说明
排序算法的稳定性是指在待排序的记录序列中如果存在多个具有相同的关键字记录,排序前和排序后这些关键字的相对位置如果没有发生变化,则说明算法是稳定的,否则就说明算法是不稳定的。

比如原序列中存在r[i] = r[j],且r[i]的位置在r[j]之前,而在排序后的序列中,r[i]的位置仍在r[j]之前,则称这种排序算法是稳定的。

注意:如果一个序列中的关键字都是不重复的,则排序的结果就是唯一的,那么排序算法的稳定性就无关紧要,但是如果一个序列中的关键字可以重复的话,则需要根据具体需求来考虑选择稳定的或者不稳定的算法。

排序算法的种类
根据不同的设计思想可以把算法分为很多种,常见的有插入类、交换类、以及选择类三种。

(1)插入类排序

插入类排序是指在一个已经排好序的序列中插入一个新元素,也就是从一个未排序的无序序列中取出一个元素,然后插入到已经排好序的序列中,直到所有的元素都插入完成,就得到了一个新的有序序列,常见的插入类排序算法有:直接插入排序。
插入排序是一种简单快速的排序算法,就是把一组无序的数据,以第一个数据为准,假设第一个数据是经过排序过的,把第二个数据和排好的数据进行比较,注意插入排序是从后向前进行比较,如果此时有一个新元素,则从已经排序的序列的尾部向前进行比较,如果排序过的序列的元素值大于新元素,则把新元素插入到该元素的前面。
然而,如果取出的数字比左边已归位的数字都要小,就必须不停地比较大小,交换数字,直到它到达整个序列的最左边为止。具体来说,就是第 k 轮需要比较 k - 1 次。因此,在最糟糕的情况下,第 2 轮需要操作 1 次,第 3 轮操作 2 次……第 n 轮操作 n - 1 次,所以插入排序的最坏时间复杂度为 O(n^2 ),也就是当序列的元素是按照从大到小输入的情况,所以直接插入排序算法适合序列基本有序的情景。
编写以下C语言代码实现并验证

#include <stdio.h>

/********************************************************************************************************
 * @Function Name		:InsertSort
 * @Functionality		:插入排序算法 是把无序序列中元素依次插入到有序序列中,一般是从有序序列的尾部开始比较
 * @Function Parameters	:
 *                          @buf[]:需要排序的数组的地址
 *                          @bufsize:需要排序的数组中元素的个数
 * @Return result		:None
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
void InsertSort(int buf[],int bufsize)
{
	int temp = 0; 				//用于备份当前待插入元素的值
	int current_prev = 0; 	//备份待插入元素的下标

	//1.可以假设把数组中的第一个元素作为有序序列的元素,剩下的元素作为无序序列
	for (int i = 1; i < bufsize; ++i)
	{
		//2.先备份当前待插入元素的值
		temp = buf[i]; 

		//3.把当前待插入元素和有序序列中的元素依次进行比较,从有序序列尾部开始
		for (int j = i-1; j >= 0; j--)
		{
			//当前待插入元素的值 小于 待插入元素的直接前驱的元素的值
			if (temp < buf[j])
			{
				current_prev = j; 	//备份当前待插入元素的直接前驱的下标
				buf[j+1] = buf[j]; 	//后移	
			}
			else
			{
				current_prev = j+1;
				break;
			}
		}

		//4.把待插入元素插入到指定位置
		buf[current_prev] = temp;
	}
}


int main(int argc, char const *argv[])
{
	
	//定义一个需要排序的整型数组
	int buf[] = {14,23,12,5,8,3,9,10,33,24,35,53,745,2,345,22,32};

	InsertSort(buf,sizeof(buf)/sizeof(buf[0]));

    //遍历输出数组元素
	for (int i=0;i<sizeof(buf)/sizeof(buf[0]);i++)
	{
		printf ("buf[%d] = %d\n",i,buf[i]);
	}

	return 0;
}

运行结果:
image

(2)交换类排序

交换类排序算法的核心是“交换”,也就是每一轮排序都是通过一系列交换动作实现的,最终让元素排列到合适的位置上。常见的交换类排序算法有:冒泡排序、快速排序。
冒泡排序也被称为起泡排序,该排序算法的原理就是经过一系列的交换实现的,也就是用第一个元素和第二个元素进行比较,如果第一个元素的值大于第二个元素则两者位置互换,否则不交换。然后第二个元素和第三个元素比较.......最后序列中最大的元素被交换到了序列的尾部,这样就完成了一轮交换,经过n轮交换之后,就可以得到一个有序序列。

当然,除了从左向右交换的方案外,另外一种冒泡排序就是重复“从序列右边开始比较相邻两个数字的大小,再根据结果交换两个数字的位置”这一操作的算法,也就是从右往左交换。在这个过程中,数字会像泡泡一样,慢慢从右往左“浮”到序列的顶端,所以这个算法才被称为“冒泡排序”。

在冒泡排序中,第 1 轮需要比较 n - 1 次,第 2 轮需要比较 n - 2 次……第 n - 1 轮需要比较 1 次。因此总的比较次数为 (n - 1) +(n - 2) +…+1 ≈ (n^2) /2。这个比较次数恒定为该数值,和输入数据的排列顺序无关。

不过,交换数字的次数和输入数据的排列顺序有关。假设出现某种极端情况,如输入数据正好以从小到大的顺序排列,那么便不需要任何交换操作;反过来,输入数据要是以从大到小的顺序排列,那么每次比较数字后都要进行交换。因此,冒泡排序的时间复杂度为 O(n^2)。

注意:设计冒泡排序算法的时候排序结束的条件应该是一轮排序过程中没有发生元素交换。
编写以下C语言代码实现并验证

#include <stdio.h>

/********************************************************************************************************
 * @Function Name		:bubbleSort
 * @Functionality		:冒泡排序算法 指的是元素两两之间进行比较交换,需要比较n轮,每轮需要比较m次,从左向右升序
 * @Function Parameters	:
 *                          @buf[]:需要排序的数组的地址
 *                          @bufsize:需要排序的数组中元素的个数
 * @Return result		:None
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
void bubbleSort(int buf[],int bufsize)
{
	
	int temp = 0; //为了临时存储交换值

	//1.循环比较元素,需要比较n轮
	for (int n = 1; n < bufsize; ++n)
	{
		//2.每轮需要比较m次
		for (int m = 0; m < bufsize-n; ++m)
		{
			//3.数组元素两两之间进行比较交换 buf[0] buf[1]   buf[1] buf[2]
			if (buf[m] > buf[m+1])
			{
				temp  	= buf[m];  //备份前一个
				buf[m]	= buf[m+1];//把后面交换到前面
				buf[m+1]= temp;	   //把前面交换到后面
			}
		}
	}
}


int main(int argc, char const *argv[])
{
	
	//定义一个需要排序的整型数组
	int buf[] = {14,23,12,5,8,3,9,10,33,32,88,45,6,56};

	bubbleSort(buf,sizeof(buf)/sizeof(buf[0]));

    //遍历输出数组元素
	for (int i=0;i<sizeof(buf)/sizeof(buf[0]);i++)
	{
		printf ("buf[%d] = %d\n",i,buf[i]);
	}

	return 0;
}

运行结果:
image

(3)选择类排序
选择排序的主要动作就是“选择”,排序原理其实就是从未排序的数列找到最小的元素,放在已排序数列的开始位置,然后再从未排序的数列找到最小的元素,然后再放置已排序数列的末尾。

选择排序就是重复“从待排序的数据中寻找最小值,将其与序列最左边的数字进行交换”这一操作的算法。在序列中寻找最小值时使用的是线性查找。

线性查找是一种在数组中查找数据的算法,线性查找的操作很简单,只要在数组中从头开始依次往下查找即可。
编写以下C语言代码实现并验证

/********************************************************************************************************
 * @Function Name		:SelectSort
 * @Functionality		:选择排序  从序列中找到一个最小值元素,把最小值元素放在整个序列的首部,重复n轮,直到整个序列有序
 * @Function Parameters	:
 *                          @buf[]:需要排序的数组的地址
 *                          @bufsize:需要排序的数组中元素的个数
 * @Return result		:None
 * @Notes				:None
 * @Author				:bbzhu04@gmail.com
 * @Revision History		:None
 * @Function version		:V1.0
 * *****************************************************************************************************/
void SelectSort(int buf[10],int size)
{
	int min  = 0; //记录最小值元素的下标
	int temp = 0; //备份最小值元素的值

	//需要比较n轮,每轮找到序列中的最小值元素
	for (int n = 0; n < size-1; ++n)  
	{
		 min  = n; //假设每轮序列中的第一个元素是最小值元素

		//找到序列中的最小值元素的下标
		for (int i = n+1; i < size; ++i)
		{
			//每轮需要通过比较的方式才可以确定最小值
			if ( buf[min] > buf[i] )                  
			{
				min = i;//更新当前的最小值元素的下标 
			}
		}

		//找到最小值元素,需要把最小值元素和序列的首部元素进行交换
		temp   = buf[min];
		buf[min] = buf[n];
		buf[n] = temp;
	}
}


int main(int argc, char const *argv[])
{
	
	//定义一个需要排序的整型数组
	int buf[] = {14,23,12,5,8,3,9,10,33,32,88,45,6,56};

	SelectSort(buf,sizeof(buf)/sizeof(buf[0]));

    //遍历输出数组元素
	for (int i=0;i<sizeof(buf)/sizeof(buf[0]);i++)
	{
		printf ("buf[%d] = %d\n",i,buf[i]);
	}

	return 0;
}

运行结果:
image

(4)快速排序
快速排序也属于交换类的排序算法,特点是通过多次划分操作实现排序。快速排序算法首先会在序列中随机选择一个基准值(通常是序列中的第一个元素),然后将除了基准值以外的数分为“比基准值小的数”和“比基准值大的数”这两个类别,再将其排列成以下形式。

[ 比基准值小的数 ] 基准值 [ 比基准值大的数 ]

接着对两个“[ ]”中的数据进行排序之后,整体的排序便完成了。对“[ ]”里面的数据进行排序时同样也会使用快速排序。
快速排序是一种“分治法”。它将原本的问题分成两个子问题(比基准值小的数和比基准值大的数),然后再分别解决这两个问题。子问题就是子序列完成排序后,再像一开始说明的那样,把他们合并成一个序列,那么对原始序列的排序也就完成了。

不过,解决子问题的时候会再次使用快速排序,甚至在这个快速排序里仍然要使用快速排序。只有在子问题里只剩一个数字的时候,排序才算完成。快速排序的算法中可以体现出“递归”。

当要排序的序列越无序,快速排序算法的效率越高,当要排序的序列越有序,该算法的效率越低。

posted @ 2026-08-05 23:41  !城北徐公.!  阅读(6)  评论(0)    收藏  举报