目录
[1. 前言](#1. 前言)
[2. 冒泡排序](#2. 冒泡排序)
[2.1 动态演示](#2.1 动态演示)
[2.2 代码实现](#2.2 代码实现)
[3. 插入排序](#3. 插入排序)
[3.1 直接插入排序](#3.1 直接插入排序)
[3.1.1 动态演示](#3.1.1 动态演示)
[3.1.2 代码实现](#3.1.2 代码实现)
[3.2 希尔排序](#3.2 希尔排序)
[3.2.1 代码实现](#3.2.1 代码实现)
[4. 选择排序](#4. 选择排序)
[4.1 动态演示](#4.1 动态演示)
[4.2 代码实现](#4.2 代码实现)
[5. 堆排序](#5. 堆排序)
[5.1 代码实现](#5.1 代码实现)
1. 前言
本篇主要讲解基础的排序算法,教学意义较大,实践意义较小。
在排序代码中会频繁使用自定义函数Swap,因此在前言中进行声明,后文不再过多赘述。
cpp
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
2. 冒泡排序
- 时间复杂度 O( N )
- **思想:**重复遍历数组,依次比较相邻的两个元素,如果左边比右边大,就交换它们的位置。每一轮遍历,都会把当前未排序部分中最大的那个元素"冒泡"到最右边。
2.1 动态演示
冒泡排序
2.2 代码实现
cpp
//冒泡排序
void BubbleSort(int* a, int n)
{
for(int i=0; i<n-1; i++)
{
int flag = 0;
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
flag = 1;
Swap(&a[j], &a[j+1]);
}
}
if(flag == 0)
{
return;
}
}
}
使用flag可以保证在后续元素有序的情况下,直接终止循环,省略多余判断。
3. 插入排序
3.1 直接插入排序
- 时间复杂度 O( N^2 ),最好情况 O( N )
- **思想:**将数组分为"已排序"和"未排序"两部分,每次从未排序部分取出第一个元素,插入到已排序部分的正确位置。
3.1.1 动态演示
插入排序
3.1.2 代码实现
cpp
//插入排序
void InsertSort(int* a, int n)
{
for(int i=0; i<n-1; i++) //小于n-1是因为后面涉及end+1,如果小于n会越界
{
int end = i;
int obj = a[end + 1];
while(end >= 0)
{
if(a[end] > obj)
{
a[end + 1] = a[end]; //挪动比Swap更快
end--;
}
else
{
break;
}
}
a[end + 1] = obj;
}
}
3.2 希尔排序
- 时间复杂度 **O( N^1.3 )**左右
- **思想:**希尔排序是插入排序的改进版,通过"分组跳跃式插入"让数组先变得"大致有序",最后再用一次插入排序完成精细调整。
3.2.1 代码实现
cpp
//希尔排序(预排序+插入排序,插入排序已被预排序包含)
//法一:逐组进(三层循环)
void ShellSort1(int* a, int n)
{
int gap = n;
while(gap > 1) //gap为1时为插入排序(最后一次循环先判断再变为1,因此不用加=)
{
gap = gap / 3 + 1;
for(int j=0; j<gap; j++) //一共有gap数量的组
{
for(int i=j; i<n-gap; i+=gap) //改为n-gap,否则会越界;i要从j开始!(每组的第一个数)
{
int end = i;
int obj = a[end + gap];
while(end >= 0)
{
if(a[end] > a[end+gap])
{
Swap(&a[end], &a[end+gap]);
end-=gap;
}
else
{
break;
}
}
a[end + gap] = obj;
}
}
}
}
cpp
//法二:多组并行
void ShellSort2(int* a, int n)
{
int gap = n;
while(gap > 1)
{
gap = gap / 3 + 1;
for(int i=0; i<n-gap; i++)
{
int end = i;
int obj = a[end + gap];
while(end >= 0)
{
if(a[end] > obj)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = obj;
}
}
}
4. 选择排序
- 时间复杂度 O( N^2 )
- **思想:**每次从待排序部分选出最小(或最大)的元素,放到已排序部分的末尾。
4.1 动态演示
选择排序
// 这是最直接的选择排序,但在代码中采用了首尾双指针的方法进行选择排序:
同时找出区间内的最大和最小元素,放在区间首尾
4.2 代码实现
cpp
//选择排序(双指针)
void SelectSort(int* a, int n)
{
int begin = 0;
int end = n - 1;
while(begin < end)
{
int mini = begin, maxi = begin;
for(int i=begin+1; i<=end; i++)
{
if(a[i] > a[maxi])
{
maxi = i;
}
if(a[i] < a[mini])
{
mini = i;
}
}
Swap(&a[maxi], &a[end]);
//避免刻舟求剑TvT
if(mini == end) mini = maxi;
Swap(&a[mini], &a[begin]);
begin++;
end--;
}
}
5. 堆排序
- 时间复杂度 O( NlogN )
- 思想: 向上调整算法 + 模拟堆删除操作。顺序建大堆,倒序建小堆
详细介绍可以阅读博主之前写的这篇博客:
5.1 代码实现
cpp
//堆排序(向下调整建大堆->每次交换首尾元素并end--,向下调整)
void HeapSort(int* a, int n)
{
//向下调整
for(int i=(n-1-1)/2; i>=0; i--)
{
int parent = i;
int child = parent * 2 + 1;
while(child < n)
{
//假设法(右孩子存在且右孩子更大)
if(child+1<n && a[child+1] > a[child])
{
child += 1;
}
if(a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//设置end,交换首尾元素
int end = n-1;
while(end)
{
Swap(&a[end], &a[0]);
//向下调整
int parent = 0;
int child = parent * 2 + 1;
while(child < end)
{
if(child+1<end && a[child+1] > a[child])
{
child += 1;
}
if(a[parent] < a[child])
{
Swap(&a[parent], &a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
end--;
}
}
//感谢阅读~(●'◡'●)