排序-交换排序-冒泡排序,快速排序,改进快速排序(C语言)

文章介绍了冒泡排序和快速排序两种经典排序算法的原理、性能分析及改进方法。冒泡排序通过相邻元素比较交换,最佳情况下时间复杂度为O(n^2)。快速排序采用分治策略,平均时间复杂度为O(nlogn),文中提出了优化选取枢轴、避免不必要的交换、处理小数组以及尾递归优化等方法。
摘要由CSDN通过智能技术生成

结构定义

#include<stdio.h>
#include<stdbool.h>  //布尔类型
#define MAXSIZE 100  //排序数组个数最大值

typedef struct {
	int a[MAXSIZE + 1];   //a[0]用作哨兵或临时变量
	int length;           //顺序表长度
}SqList;

/* 交换数组L中下标为 i 和 j 的值 */
void swap(SqList* L, int i, int j)
{
	int t = L->a[i];
	L->a[i] = L->a[j];
	L->a[j] = t;
}

冒泡排序

动画演示:从前往后(一次外部循环排出最大值)
我的代码是从后往前(一次外部循环排出最小值)
在这里插入图片描述

思路

两两比较相邻记录的关键字,如果返序则交换,直到无返序记录

性能分析

稳定排序
最好情况:顺序有序
最坏情况:逆序有序
空间复杂度:O(1)
时间复杂度:

冒泡插入最好情况最坏情况平均情况
-nn2n2

冒泡排序

/* 冒泡排序 */
void BubbleSort(SqList* L)
{
	for (int i = 1; i < L->length; i++)
	{
		for (int j = L->length; j > i; j--)
		{
			if (L->a[j] < L->a[j - 1])
				swap(L, j, j - 1);
		}
	}
}

改进冒泡排序

如果冒泡排序在一次外部中无交换,则已经是顺序有序了,就不需要再操作了,我们可以用一个标记变量来改进

/* 改进冒泡排序 */
void BubbleSort2(SqList* L)
{
	_Bool flag = true;   //标记
	for (int i = 1; i < L->length && flag; i++)  //flag 为 true 则有记录交换,否则退出循环
	{
		flag = false;   //初始化为 false
		for (int j = L->length; j > i; j--)
		{
			if (L->a[j] < L->a[j - 1])
			{
				swap(L, j, j - 1);
				flag = true;    //有记录交换,为true
			}
		}
	}
}

快速排序

在这里插入图片描述

思路

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于分界值,而右边部分中各元素都大于或等于分界值.
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。

枢轴(pivot): 该关键字位于一个位置,它之前的记录小于它,它之后的记录大于它。

性能分析

不稳定排序
枢轴每次划分的越均匀越好(枢轴左边和右边的个数相差越小越好)
最坏情况:有序或逆序
空间复杂度:O(logn)~O(n)
时间复杂度:

快速排序最好情况最坏情况平均情况
-nlognn2nlogn

快速排序

看不懂的可以看看b站天勤数据结构, 快速排序讲的通俗易懂

/* 快速排序 */
void QuickSort(SqList* L)  //封装函数,便于调用
{
	QSort(L, 1, L->length);
}

/* 对子序列L->a[low......high]作快速排序 */
void QSort(SqList* L, int low, int high)
{
	int pivot;
	if (low < high)
	{
		pivot = Partition(L, low, high);//将子序列L->a[low......high]一分为二,算出枢轴值
		QSort(L, low, pivot - 1);          //低子表递归调用
		QSort(L, pivot + 1, high);           //高子表递归调用
	}
}

/* 枢轴值到位 */
/* 交换L子表记录,返回枢轴值,并且它之前的记录小于它,它之后的记录大于它*/
int Partition(SqList* L, int low, int high)
{
	int pivotkey;
	pivotkey = L->a[low];    //用子表第一个记录作枢轴记录
	while (low < high)      //从表的两端交替的向中间比较交换
	{
		while (low < high && L->a[high] >= pivotkey) //从右端查找小于pivotkey的记录
			high--;

		swap(L, low, high);  //将比枢轴记录小的记录(L->a[high])交换到低端

		while (low < high && L->a[low] <= pivotkey) //从左端查找大于pivotkey的记录
			low++;

		swap(L, low, high);  //将比枢轴记录大的记录(L->a[high])交换到高端
	}
	return low;
}

改进快速排序

1. 优化选取枢轴

枢轴每次划分的越均匀越好(枢轴左边和右边的个数相差越小越好)
如果用子表第一个记录作枢轴记录,碰上最坏情况,有序或逆序,那每次将子序列L->a[low…high]一分为二,有一方无记录,这怎么行,
时间复杂度为O(n2),
所以我们可以选稍微不大不小的记录值作枢轴。
这里我只三选一。

/* 优化操作 1  */
int Partition1(SqList* L, int low, int high)
{
	int pivotkey;
// ---------------------------- 优化操作 1 -------------------------------------

		int m = low + (high - low) / 2;   //计算数组中间元素下标
		if(L->a[low] > L->a[high])
		swap(L, low, high);              //保证低端记录小于高端
		if (L->a[m] > L->a[high])
			swap(L, m, high);            //保证中端记录小于高端
		if (L->a[m] > L->a[low])         //使得L->a[low]为 低端,中端,高端中的中间值
			swap(L, low, m);

//	----------------------------------------------------------------------------- 
	pivotkey = L->a[low];    //用子表第一个记录作枢轴记录
	while (low < high)      //从表的两端交替的向中间比较交换
	{
		while (low < high && L->a[high] >= pivotkey) //从右端查找小于pivotkey的记录
			high--;

		swap(L, low, high);  //将比枢轴记录小的记录(L->a[high])交换到低端

		while (low < high && L->a[low] <= pivotkey) //从左端查找大于pivotkey的记录
			low++;

		swap(L, low, high);  //将比枢轴记录大的记录(L->a[high])交换到高端
	}
	return low;
}

2. 优化不必要交换

枢轴记录每次交换没必要,最后我们只要将枢轴记录插入到所需要的插入位置即可

/* 优化操作 2 */
int Partition2(SqList* L, int low, int high)
{
	int pivotkey;
	
	int m = low + (high - low) / 2;   //计算数组中间元素下标
		if(L->a[low] > L->a[high])
		swap(L, low, high);              //保证低端记录小于高端
		if (L->a[m] > L->a[high])
			swap(L, m, high);            //保证中端记录小于高端
		if (L->a[m] > L->a[low])         //使得L->a[low]为 低端,中端,高端中的中间值
			swap(L, low, m);
			
	pivotkey = L->a[low];    //用子表第一个记录作枢轴记录

// ---------------------------- 优化操作 2 -------------------------------------
	L->a[0] = pivotkey;     //临时存储变量
//-----------------------------------------------------------------------------

	while (low < high)      //从表的两端交替的向中间比较交换
	{
		while (low < high && L->a[high] >= pivotkey) //从右端查找小于pivotkey的记录
			high--;

// ---------------------------- 优化操作 2 -------------------------------------
		L->a[low] = L->a[high];            //采用替换操作而不是交换
//---------------------------------------------------------------------------

		while (low < high && L->a[low] <= pivotkey) //从左端查找大于pivotkey的记录
			low++;

// ---------------------------- 优化操作 2 -------------------------------------
				L->a[high] = L->a[low];       //采用替换操作而不是交换
//---------------------------------------------------------------------------
			}
// ---------------------------- 优化操作 2 -------------------------------------
L->a[low] = L->a[0];         //将L->a[low]替换成枢轴值
//----------------------------------------------------------------------------
	return low;
}

3. 优化小数组的排序方案

我们知道,排序记录个数越小,采用简单算法越合适,因此,我这里high - low <= 7 时用直接插入排序

/* 优化操作3 */
#define MAX_LENGTH_INSERT_SORT 7   //用于快速排序时判断是否选用插入排序的阈值
void QSort3(SqList* L, int low, int high)
{
	int pivot;
	if ((high - low) > MAX_LENGTH_INSERT_SORT)  //high - low > MAX_LENGTH_INSERT_SORT时用快速排序
	{
		pivot = Partition(L, low, high);  //用 Partition1 或Partition2都行
		QSort3(L, low, pivot - 1);          
		QSort3(L, pivot + 1, high);
	}
	else
		InsertSort(L);    //high - low <= MAX_LENGTH_INSERT_SORT 时用直接插入排序
}

4. 优化递归操作

QSort()函数在其尾部有两次递归操作,如果我们能减少递归操作,将提高性能,所以这里对QSort()进行尾递归优化,可以减少堆栈深度

/* 优化操作4 */
#define MAX_LENGTH_INSERT_SORT 7   
void QSort4(SqList* L, int low, int high)
{
	int pivot;
	if ((high - low) > MAX_LENGTH_INSERT_SORT) 
	{
		while(low < high)    //减少递归
		{
			pivot = Partition(L, low, high);
			QSort4(L, low, pivot - 1);
			low = pivot + 1;    //尾递归优化
		}
	}
	else
		InsertSort(L);    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mirror_zz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值