一、排序
1、排序的概念
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。
2、稳定性的概念
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
二、冒泡排序
1、交换排序的基本思想
所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置。
2、交换排序的特点
将键值较大的记录向序列的尾部移动,键值较小的记录向序列的头部移动。冒泡排序就是交换排序的一种。
3、交换元素数据函数的代码
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
因为此函数在下面的函数中都会用到,就直接写到这里,该函数实现较简单,不做过多的说明
4、冒泡排序的代码
void BubbleSort(int* a, int n)
{
assert(a);
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n - 1 - i; ++j)
{
if (a[j + 1] < a[j])
Swap(&a[j + 1], &a[j]);
}
}
}
5、实现原理
- 冒泡排序比较简单,就是每一趟循环将最大的值移到序列范围的最后一个位置。
- 在每次循环的比较中,后一个数比当前位置的数要小时,将二者进行交换。
- 每一趟循环结束进入下一趟循环时,第二层循环的执行次数将减少一次,因为最大的值的位置已经确定了。
6、冒泡排序优化后的代码
void BubbleSort(int* a, int n)
{
assert(a);
int end = n;
while (end > 0)
{
int flag = 0;
for (int i = 0; i < end; ++i)
{
if (a[i + 1] < a[i])
{
Swap(&a[i + 1], &a[i]);
flag = 1;
}
}
if (flag == 0)
break;
--end;
}
}
7、冒泡排序优化后的代码的实现原理
- 创建一个变量end并初始化为n,充当循环的判断条件。
- while循环相当于未优化时候的第一层循环,循环n次,并在内部对end自减一,使其趋于结束,否则,如果没有对end自减,循环将变为死循环。
- 在循环内部创建一个变量flag并初始化为0,当第二层循环没有元素要进行交换时,flag不会被修改,即表明序列已经有序,不需要再进行循环了,此时flag还是0,在第二层循环结束后对flag进行判断,flag等于0则直接结束循环,否则,flag为1,循环继续。
三、直接选择排序
1、选择排序的基本思想
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置(或末尾位置),直到全部待排序的数据元素排完。直接选择排序就是选择排序的一种。
2、直接选择排序的实现原理
在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素,若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。
3、代码
void SelectSort(int* a, int n)
{
assert(a);
int min = 0;
for (int i = 0; i < n; ++i)
{
min = i;
for (int j = i + 1; j < n; ++j)
{
if (a[j] < a[min])
{
min = j;
}
}
Swap(&a[i], &a[min]);
}
}
4、代码的实现原理
- 创建一个变量min,用来接收最小值所处的位置(下标)。
- 用两层循环实现对序列中的每个元素进行遍历并比较,找出范围内最小值的下标,赋值给min,最后进行交换,将最小值与i位置的值进行交换,即找到范围内的最小值,下次循环便不遍历该位置。
- 所以,第二层循环中的j初始值设为 i + 1 ,使排序完最小值的位置不再被遍历,即找到范围内的最小值后,接下来的遍历都不需要遍历这些元素。
- 判断条件中i的最大值设为 n ,即i最大为 n - 1 ,j最大为 n ,即遍历范围中只有一个元素时,不再遍历。
5、实现原理图(第一趟循环)
6、选择排序优化后的代码
void SelectSort(int* a, int n)
{
assert(a);
int begin = 0, end = n - 1;
while (begin < end)
{
int min = begin, max = begin;
for (int i = begin; i <= end; i++)
{
if (a[i] >= a[max])
max = i;
if (a[i] < a[min])
min = i;
}
//最小值放在begin处
Swap(&a[begin], &a[min]);
if (begin == max)
max = min;
//最大值放在end处
Swap(&a[end], &a[max]);
++begin;
--end;
}
}
7、选择排序优化后的代码的实现原理
- 优化后的选择排序每次遍历循环都能找出范围内的最大值与最小值,排序的效率有所提高。
- while循环与未优化时排序中的第一次循环作用类似,用begin与end作为判断条件,每次循环结束后,已排序部分的位置不再参与遍历。
- 将min与max都初始化为begin,因为在下面会与排序范围内的所有元素进行比较,所以将min与max初始化为begin,这样它们就从排序范围内的第一个元素开始比较,直到范围内的最后一个元素。
- 需要注意的是两个Swap的交换,第一次Swap将找出的最小值与begin位置的值进行交换,但如果找出的最大值的位置在begin位置处,则此次交换会将最大值的位置交换掉,所以,添加一个if语句对max的位置进行判断,如果max的位置与begin的位置相同,则将max改为min的位置,因为已经进行了交换,所以此时min的位置就是找出的最大值的位置。
- 最后对begin自增,对end自减,使下次循环时,已经排好序的位置不再被遍历。
8、选择排序优化后的代码的实现原理图(第一趟循环)
四、堆排序
1、堆排序的实现原理
堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种,是
通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
关于树与二叉树的内容参见树与二叉树
2、代码
void AdjustDwon(int* a, int n, int root)
{
//建大堆
int maxchild = 2 * root + 1;
if (maxchild >= n)
return;
if (maxchild + 1 < n && a[maxchild + 1] > a[maxchild])
++maxchild;
if (a[root] < a[maxchild])
{
int tmp = a[root];
a[root] = a[maxchild];
a[maxchild] = tmp;
AdjustDwon(a, n, maxchild);
}
}
void HeapSort(int* a, int n)
{
assert(a);
for (int i = (n - 2) / 2; i >= 0; --i)
{
AdjustDwon(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDwon(a, end, 0);
--end;
}
}
3、代码的实现原理
- 首先开始排序时,我们不知道序列是否有序,所以需要对序列进行排序,当我们需要将序列变为升序时就建大堆,反之,排降序则建小堆。
- 建堆的方式,此处就采用向下调整的方式来建,函数名设为AdjustDwon。建堆的开始位置是二叉树中的最后一个非叶子节点,它的位置是(n - 2) / 2,然后从这个节点开始,依次往上进行向下调整。
- AdjustDwon,向下调整,顾名思义,就需要将该节点的值与它左右子树的根节点进行比较,而左子树的根节点位置为2 * root + 1,右子树的根节点位置为左子树根节点的位置加一。
- n为排序序列的大小,该序列最后一个元素的下标为n-1。当maxchild >= n时,此时maxchild为左子树的根节点位置,证明该位置已经超出序列的大小了,直接返回,不再进行下面的操作。
- 第二个if语句的作用是当 maxchild + 1 等于n时,不会进行计算与修改maxchild的值,因为此时左子树根节点的位置为n - 1,而数组a只有n-1个元素,maxchild + 1 等于 n,对它进行访问属于越界访问。
- 第三个if语句的作用是对root的值和其左右子树中最大值的节点进行判断,如果root节点比它要小则进行交换,再进行递归。
- 退出AdjustDwon函数后,我们得到一个大堆数据,创建一个变量end并赋值为n - 1,即最后一个元素的位置,将刚刚得到的大堆的堆顶位置的元素与最后一个位置的元素进行交换,再进行向下调整,此时,最大的元素到了最后面,对end自减,以此类推,直到最后一个元素,则升序排序完成。
4、实现原理图(将序列建成大堆后)
五、计数排序
1、 计数排序实现原理
- 统计相同元素出现的次数。
- 根据统计的结果将数据回写到原来的序列中。
2、 代码
void CountSort(int* a, int n)
{
int min = a[0], max = a[0];
for (int i = 1; i < n; ++i)
{
if (a[i] < min)
min = a[i];
if (a[i] > max)
max = a[i];
}
// 统计次数的数组
int range = max - min + 1;
int* count = (int*)malloc(sizeof(int)*range);
if (count == NULL)
{
printf("malloc fail\n");
exit(-1);
}
memset(count, 0, sizeof(int)*range);
// 统计次数
for (int i = 0; i < n; ++i)
{
count[a[i] - min]++;
}
// 回写-排序
int j = 0;
for (int i = 0; i < range; ++i)
{
// 出现几次就会回写几个i+min
while (count[i]--)
{
a[j++] = i + min;
}
}
}
3、代码的实现原理
- 首先找出序列中的最大值与最小值,为下面开辟空间做准备。
- 统计数组的大小就是max - min + 1,开辟空间并将数组中的所有元素都初始化为0。
- 再次遍历序列,序列中的元素的数值减去最小值就是它在数组中的存放位置,遍历序列元素并统计它们出现的次数。
- 最后回写到原序列中,序列中元素的大小就是数组中该元素的值加上最小值,它在数组中所处位置的值就是它在序列中出现的次数。
4、代码的实现原理图
5、注意
- 最后一个循环中,i的判断条件是小于range而不是小于n。
6、计数排序的特性
- 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
- 只适用于整型数据组成的序列,浮点数或字符组成的序列都不适用。
- 如果数据范围很大,空间复杂度就会很高,相对来说不适用。
- 计数排序适合范围集中,重复数据多的序列。
本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得写得不错,请务必一键三连💕💕💕