文章目录
前言
我们常见的排序算法有下面这几种:
在这几种算法中,有比较简单的,有也比较复杂的,所以这里我打算把简单的排序整合成一篇文章,比较难的我会单独写成一篇
我们在讲排序算法的时候会提到稳定性的概念。
⭐️:这里提一下:
排序稳定性的意义:相同的数据排序后,相对位置是否发生变化。
1. 冒泡排序
1.1 冒泡排序的思想
冒泡排序的基本思想:冒泡排序是交换排序中一种简单的排序方法。是许多人学的第一个排序算法,因此它也是我们的白月光。
它的基本思想是对所有相邻的数值进行比效,如果(a[j]>a[j+1]),则将其交换,最终达到有序化。
1.2 冒泡排序的实现
- 冒泡排序是一种非常理解的排序
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:稳定
我们用代码来实现一下它的逻辑:
void BubbleSort(int* arr, int n)
{
if (arr == NULL)
return; // 防止使用的人传空指针过来
for (int i = 0;i < n - 1;i++)
{
int flag = 1; // 定义一个变量来优化冒泡排序
for (int j = 0;j < n - 1 - i;j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
// 我们在每次交换完数据以后都会将flag赋值为0,
// 如果有一次我们比较完一趟下来都没有发生交换
// 则表明该数组已经有序了
flag = 0;
}
}
if (flag == 1)
break; // 有序就直接退出循环了,不用继续将所有的数据都比较完
}
}
2. 直接插入排序
2.1 直接插入排序的思想
直接插入排序是一种简单的插入排序法,其基本思想是:
把待排序的数按照大小逐个比较,然后插入到一个已经排好序的有序序列中,直到所有的数插入完为止,得到一个新的有序序列。 当然你也不止可以比较数,也可以比较其他类型的数据,通过你想比较的方式区进行实现,因为这里我们说排序,所以就用整型来实现,会更加通俗易懂。
在实际生活中,我们玩扑克牌时,就用到了插入排序的思想:我们拿到排后,会将牌按一定的顺序调整好,在这个过程中,我们整理一张牌时,眼睛会将手里的牌看一遍,然后在将要插入的牌插入到合适的位置。
2.2 直接插入排序的实现
说了这么多我们来看一下直接插入排序具体是这样排序的吧:
当插入第i(i >= 1)个元素时,前面的array[0], array[1], …, array[i - 1]已经排好序,此时用array[i]的排序码与array[i - 1], array[i - 2], …的排序码顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),他是一种稳定的排序算法
- 稳定性:稳定
我们用代码来实现这一逻辑:
void InsertSort(int* arr, int n)
{
if (arr == NULL)
return; // 防止使用的人传空指针过来
for (int i = 0;i < n - 1;i++)
{
int tmp = arr[i + 1]; // 保存每趟的最后一个数
int j = 0;
for (j = i;j >= 0;j--)
{
if (tmp < arr[j]) // 如果最后一个数小于前一个数就将前一个数往后移
{
arr[j + 1] = arr[j];
}
else // 由于我们是从前面开始排序的,所以能够保证前面是有序的,所以只要我们比较的这个数大于它的前一个,我们就在当前位置插入
break;
}
arr[j + 1] = tmp; // 在arr[j]位置插入tmp
}
}
3. 选择排序
3.1 选择排序的基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。
3.2 选择排序的实现
- 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
- 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
- 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
选择排序的特性总结:
- 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
- 时间复杂度:O(N^2)
- 空间复杂度:O(1)
- 稳定性:不稳定
插入排序的思想如上面所述,我们是先遍历一遍,找到数组中最小的那个数,然后将它放到最左边。但其实我们可以再给这个排序进行一些优化:我们每次遍历可以分别找到它的最小值和最大值,然后再将最小值和最大值分别放到最左边和最右边。具体代码实现如下:
// 我们需要一个交换值的函数
void swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void SelectSort(int* arr,int n)
{
if (arr == NULL)
return; // 防止使用的人传空指针过来
int left = 0;
int right = n - 1;
while (left < right)
{
// 这里选择用下标来记录数组中的最大值最小值的位置,
// 这样下面做交换的时候会比较方便,
// 如果我们直接用max/min来记录最大值/最小值后续交换的时候就会很困难
int maxi = left, mini = left; // 从左边开始找最大值和最小值
for (int i = left;i <= right;i++)
{
// 每趟找到最大的数和最小的数
if (arr[i] > arr[maxi])
maxi = i;
if (arr[i] < arr[mini])
mini = i;
}
swap(&arr[left], &arr[mini]);
if (left == maxi)
{
// 这步是防止如果maxi是left,
// 如果maxi是left,left和mini交换以后,maxi指向的数就被换了位置了
maxi = mini;
}
swap(&arr[right], &arr[maxi]);
//把最小的和最大的分别放到了最左边和最右边,left和right就要分别往中间靠
left++;
right--;
}
}
4. 计数排序
4.1 计数排序的思想
计数排序它是一个性能比较高,但使用范围很局限(仅适用于整型且范围较集中的数进行比较)的排序,这里学习计数排序主要还是学习它的思想,它的思想也可以解决一些其他的问题。
计数排序的思想:创建一个数组(range(范围)是要比较的数中最大值和最小值的差值,也就是这组数的极差)用来记录我们要排序的数组中的各个数据出现的次数,然后再将我们记录的数据(所对应的数出现的次数)依次赋值回我们要排序的数组中。在这里就可以看出来,为什么适用于范围较集中的数据用来排序了。
4.2 计数排序的实现
- 只适用于整型且范围较集中的数
- 时间复杂度:O(N + range)
- 空间复杂度:O(range)
- 不用讨论它的稳定性
我们先统计一下arr
数组中每个数出现的次数,对应到下标存入到counst
中
然后又在counst
中下标对应的数每次赋值到arr
数组中。
但到了这里,我们发现,如果是排序 101 101 101到之间的数 109 109 109,我们显然开一块大小为 110 110 110的数组显然是不现实的,因为前面的空间我们也用不到,这样就造成了浪费。
我们可以用一些手段来调整一下:
先遍历一遍要排序的数组,找到它的最大值和最小值,然后计算出他们之间的范围(range),知道了他们之间的范围,就可以开辟这块空间了。然后再将我们要排序的数组中的数,以一定的方式映射到counst
中。
这样一来我们就可以只开辟大小为9的空间。(range = max-min+1
,+1的原因是最大值和最小值都是左闭右闭的区间,相减之后得到的只是它们相隔的距离,+1才能得到它们之间的距离)
我们用代码实现一下这个逻辑:
void CountSort(int* arr, int n)
{
if (arr == NULL)
return; // 防止使用的人传空指针过来
int max = arr[0], min = arr[0];
for (int i = 0;i < n;i++)
{
if (arr[i] < min)
min = arr[i];
if (arr[i] > max)
max = arr[i];
}
// 找到最大的数和最小的数来确定范围
int range = max - min + 1;
// 开辟一个数组用来计数a数组中每个数据出现了多少次
int* count = (int*)malloc(sizeof(int) * range);
if (count == NULL)// 处理一下开辟空间失败的结果
{
perror("malloc fail");
exit(-1);
}
// 将我们新创建的数组初始化为0
memset(count, 0, sizeof(int) * range);
// 遍历一遍a数组,将它的值出现的次数存入到count中
for (int i = 0;i < n;i++)
{
count[arr[i] - min]++;
}
// 排序
int j = 0;
for (int i = 0;i < range;i++)
{
while (count[i]--)
{
arr[j++] = i + min;
}
}
// 用完开辟的空间要记得释放,否则容易造成内存泄漏
free(count);
count = NULL;
}