插入排序
插入排序的思想
简单来说,插入排序就时将一个数插入一个数插入一个有序的数组使之仍然有序,我们可以用一张动图来展示其基本原理
从上图不难发现,我们可以将第一个数看成一个有序的数,因为只有一个数所以不存在有序与无序,所以我们可以从第二个数开始插入,使前两个数有序,然后接着开始从第三个数开始插入,直到最后一个数。
代码实现
代码语言:javascript复制//插入排序
//时间复杂度:O(N^^2)
void InsertSort(int* a, int n)
{
//
//[0,end] end 1
//循环n-1次
//时间复杂度:O(N^2)
//最坏情况是逆序 最好情况是顺序或者接近有序
for (int i = 0;i < n-1;i )//最后一个数是用来插入进去的,所以只能遍历到n-2,最后一个数直接插入进去
{
int end = i;
int tmp = a[end 1];
//1 2 3 4 5 .....n-1(n^2)
while (end >= 0)
{
if (tmp < a[end])
{
a[end 1] = a[end];
end--;
}
else
{
break;
}
}
a[end 1] = tmp;
}
}
==注意:不管是进入if还是进入else,最后都要将tmp
插入到end 1
中,所以,我们可以把这个语句放在外面
冒泡排序
冒泡排序的思想
假设排升序 冒泡排序的基本思想大致是:先将 数组中前两个数进行比较,然后如果前一个数大于后一个数,则交换,然后再将后一个数与其后一个再进行比较,然后如果前一个数大于后一个数则前后两个数交换,则经过一轮排序之后,最大的数则会被冒到最后一个位置,以此类推,我们不难发现,如果是十个数的话我们只需要冒泡九轮,所以最后冒泡的次数是数组的元素个数减一。 动图展示:
代码实现
根据上面的描述:我们可以很容易得写出相应的代码
代码语言:javascript复制void Swap(int* ps, int* pa)
{
int tmp = *ps;
*ps = *pa;
*pa = tmp;
}
//冒泡排序
void BubbleSort(int* a, int n)
{
for (int i = 0;i < n;i )
{
for (int j = 0;j < n - 1 - i;j )
{
if (a[j] > a[j 1])
{
Swap(&a[j], &a[j 1]);
}
}
}
}
注意:冒泡排序的时间复杂度是O(N^2)
为此我们还可以对冒泡排序进行优化,我们知道,冒泡排序不管这个数组有没有序都会将数组遍历一遍,所以我们可以定义一个标记是否进行交换的变量exchange
,如果在一轮循环中,exchange没有发生变化,则证明在这次循环中根本没有交换,所以我们可以直接终止这次循环:
改进后的代码如下:
void NewBubbleSort(int* a, int n)
{
for (int i = 0;i < n - 1;i )
{
int exchange = 0;//标志---一个轮回都没有交换
for (int j = 1;j < n - j;j )
{
if (a[j - 1] > a[j])
{
Swap(&a[j], a[j 1]);
exchange = 1;//交换了,就令exchange为1
}
}
//一轮之后判断exchange是否改变
if (exchange == 0)
{
break;
}
}
}
堆排序
堆排序的基本思想
堆排序是一种基于二叉堆数据结构的排序算法。其基本思想可以总结如下:
- 建立堆: 将待排序的数据构建成一个最大堆(或最小堆),即满足堆的性质:父节点的值总是大于等于(最大堆)或小于等于(最小堆)其子节点的值。建立堆的过程一般从最后一个非叶子节点开始,依次向前调整,使得每个节点都满足堆的性质。
- 堆调整: 将堆顶元素(最大值或最小值)与堆的最后一个元素交换,并将堆的大小减一,然后对堆顶元素进行调整,使得剩余元素重新构成一个最大堆或最小堆。
- 重复步骤2: 不断重复进行堆调整的过程,直到堆的大小减为1,即所有元素都被排序完成。 堆排序的关键在于建立堆和堆调整的过程。通过构建最大堆,堆顶元素就是整个序列中的最大值,在每一轮的堆调整中将最大值移动到序列的末尾,从而完成排序。同样,如果需要按照从小到大的顺序排序,可以构建最小堆,并将堆顶元素移动到序列的末尾。 动图展示:
注意:假设我们要排升序,则应该建大堆,这里解释一下为什么,如果我们建小堆的话,我们只能取到堆顶元素是最小的,取完堆顶元素之后,又要对剩下的元素重新建堆这样的消耗是很大的,但是如果我们建大堆的话,我们可以知道最大的元素时堆顶然后将堆顶的元素交换到最后一层的最后一个元素,然后再对剩下的元素进行一次向下调整,再选出次大的,这样根本不用重新建堆,不管是对空间还是对时间的消耗都是最小,所以我们排升序选择建大堆。 堆排序 第一步:建堆 第二部:交换堆顶和堆底的元素,然后再向下调整。
代码实现
代码语言:javascript复制//堆排序
void AdjustDown(int* a, int n, int root)
{
int parent = root;
int child = root * 2 1;
while (child < n)
{
if (a[child] < a[child] && child 1 < n)
{
child ;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
//建堆
for (int i = (n - 1 - 1) / 2;i >= 0;i--)
{
AdjustDown(a, n, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
end--;
}
}
希尔排序
希尔排序基本思想
希尔排序(Shell Sort)是一种改进的插入排序算法,也被称为缩小增量排序。其基本思想是通过比较相距一定间隔(称为增量)的元素,使得每一轮比较的元素距离越来越近,最终使得整个序列基本有序,然后再进行最后一轮插入排序。
希尔排序的具体步骤如下:
- 选择增量序列: 选择一个增量序列,用于指定待排序序列中相邻元素之间的间隔。通常的增量序列选择包括Hibbard序列、Sedgewick序列等。增量序列的选择会影响希尔排序的性能。
- 根据增量序列进行分组: 将待排序序列分成若干个子序列,每个子序列包含间隔为增量的元素。对每个子序列分别进行插入排序。
- 逐步缩小增量: 不断减小增量,重复步骤2,直到增量为1。这样,最后一轮排序将是一次插入排序,但是由于之前的步骤已经使得序列基本有序,因此插入排序的时间开销会大大减少。
希尔排序之所以有效,是因为它在一开始通过较大的增量将元素分成较少的子序列,对这些子序列进行插入排序可以使得元素移动的距离减少,从而减少了插入排序的时间复杂度。随着增量的逐步减小,最终一轮的插入排序对于基本有序的序列而言时间开销较小。
希尔排序的时间复杂度与增量序列的选择有关,最好的情况下可以达到O(n log n),最坏情况下为O(n^2)。希尔排序是一种不稳定的排序算法,但由于其简单且在某些情况下表现良好,仍然被广泛使用。
注意:我们接下来展示的是将希尔排序的间隔定义为2的情况下展开的 先用动图展示:
代码实现
代码语言:javascript复制//gap越大数据跳到越快
void ShellSort(int* a, int n)
{
//分组预排插入,目标:大的数更快的跳到后面,小的数更快的跳到前面
int gap = n;
while (gap > 1)
{
gap /= 2;
for (int i = 0;i < n - gap;i = gap)
{
int end = i;
int tmp = a[end gap];
while (end > 0)
{
if (tmp < a[end])
{
a[end gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end gap] = tmp;
}
}
}
注意:上面代码随着gap的减小逐渐接近有序,直到最后gap等于1的时候,就是我们前面讲到的插入排序。
选择排序
选择排序基本思想
讨论升序的情况 选择排序的基本思想:遍历整个数组选出最大的元素,然后插入到最后一个位置,然后从剩下的数组中选出次大的,然后插入到倒数第二个位置,以此类推 动图展示:
代码展示
代码语言:javascript复制void SelectSort(int* a, int n)
{
int begin = 0, end = n - 1;
//选出最小值和最大值的位置
int mini = begin, maxi = begin;
while (begin < end)
{
for (int i = begin;i <= end;i )
{
if (a[i] < a[mini])
{
mini = i;
}
if (a[i] > a[maxi])
{
maxi = i;
}
}
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[begin], &a[mini]);
Swap(&a[end], &a[maxi]);
begin ;
end--;
}
}
注意:这里我们我们可以利用两个指针,一个指向头,一个指向尾,然后将最小的元素插入到数列的首位置,将最大的元素插入到数组尾的位置,然后依次向中间遍历,这样优化可以将以前的执行次数减少一半,因为我同时选出了最大和最小的元素。
总结
当然可以。以下是一个关于排序算法的总结:
总的来说,排序算法是计算机科学中一个重要而基础的主题,它们在日常生活和各种应用中都扮演着重要的角色。通过对各种排序算法的了解和研究,我们可以更好地理解数据的组织和处理方式,从而提高算法的效率和性能。
在本文中,我们介绍了几种常见的排序算法,包括冒泡排序、选择排序、插入排序、归并排序、快速排序、堆排序和希尔排序。每种算法都有其独特的思想和实现方式,并且在不同的应用场景下具有不同的优缺点。
冒泡排序和选择排序是最简单的排序算法之一,虽然它们的时间复杂度较高,但对于小规模数据集合仍然是一种有效的选择。插入排序通过构建已排序部分来不断插入新元素,具有较好的性能表现,特别适用于部分有序的数据。归并排序和快速排序是两种基于分治思想的高效排序算法,它们具有较好的平均时间复杂度,并且可以在大规模数据集合下快速排序。堆排序利用堆数据结构实现了一种高效的排序算法,尤其适用于需要原地排序的情况。希尔排序则是一种改进的插入排序算法,通过逐步缩小增量实现了对基本有序序列的高效排序。
除了这些算法外,还有许多其他排序算法,每种都有其特定的应用场景和优劣势。在选择排序算法时,需要考虑数据规模、数据特征、时间复杂度和空间复杂度等因素,并根据实际情况选择最合适的算法。
总的来说,排序算法是计算机科学中一个基础而重要的研究领域,通过不断地学习和研究排序算法,我们可以更好地理解和应用算法,提高程序的效率和性能,为解决实际问题提供更好的解决方案。