结构定义
#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)
时间复杂度:
冒泡插入 | 最好情况 | 最坏情况 | 平均情况 |
---|---|---|---|
- | n | n2 | n2 |
冒泡排序
/* 冒泡排序 */
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)
时间复杂度:
快速排序 | 最好情况 | 最坏情况 | 平均情况 |
---|---|---|---|
- | nlogn | n2 | nlogn |
快速排序
看不懂的可以看看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);
}