本篇将简单介绍几种常见的排序算法,首先先来看下简单的分类吧。
一、插入排序:直接插入排序、希尔排序
二、选择排序:选择排序、堆排序
三、交换排序:冒泡排序、快速排序
四、归并排序:归并排序
文字太多,看着太烦,直接聊聊每一种排序的实现逻辑以及代码实现(均以升序为例)。
一、插入排序
-
直接插入排序
假设现在有一个[0-end]区间的有序数组如下:
如果现在我们需要将新元素5放到这个有序数组中去,很明显应该放到4与6的中间,才能使这个数组在添加新元素之后依旧保证有序, 所以插入排序的前提是将元素往一个已经有序的数组中通过比较之后将新元素插入到属于它的位置上,那么问题就转变成如何让一个数组变的有序呢?先看下面这样一个无序数组:
对于上面这样一个无序数组,我们就可以这样去看,将7看成一个有序数组(只有一个元素),将7之后的元素依次往这个有序数组中插入,通过比较的方式,找到待插入元素应该在这个有序数组的位置,再将元素进行插入。可以参考如下图的分析:
根据面的分析,我们可以写如下逻辑进行实现:
//直接插入排序
void InsertSort(int* a, const int n)
{
//从第一个元素开始,将第一个元素之后的元素依次进行插入
//n-1作为边界,如果以n作为边界,那么end+1就会数组越界
for (int i = 0; i < n - 1; ++i)
{
int end = i;
int tmp = a[end + 1]; //暂存待插入的值
//单趟排序,找到待插入元素应该存在的位置
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + 1] = a[end];
--end;
}
else
{
break;
}
}
//end跳出循环的条件是a[end] < tmp ,因此待插入位置应该是a[end+1]
a[end+1] = tmp;
}
}
那么直接插入排序的时间复杂度是多少呢?
分析整个排序过程,假设整个数组有n个元素,那么外层的for循环每进来一次,有序区间元素增加一个,因此交换次数也就会逐渐增加,其时间复杂度就为1*1+1*2+1*3+……+ 1*(n-1) = (n*n-n)/2;
也就是O(N*N),这是最坏的情况(逆序的情况下,每一次插入都要从最后交换到最前面),根据上面的分析,那么最好的情况下便是每次的插入都只需要交换一次数组便有序了,因此最好的情况下的时间复杂度为O(N).
-
希尔排序
简单理解一下希尔排序,希尔排序实际上是对直接插入排序的优化,上面讲到,直接插入排序在最坏的情况下时间复杂度为O(N*N);因此希尔排序实际上是让直接插入排序的时间复杂度更加的均衡一点,不至于太极端。那么希尔排序的逻辑是怎样的呢?
上面提到,直接插入排序的前提是数组已经有序,那么如果数组已经有序或者已经接近有序,比较次数就会减少,时间上的消耗自然也就减少了,而希尔排序的实际就是让数组接近有序.
如何实现呢?将数组按照gap的间距进行分组,对每一组进行直接插入排序,这样就可以让较大的数更快的到后面,较小的数更快的到前面。需要注意的是,gap越大分的组就越多,数组就越不接近有序,gap越小,分的组越少数组越接近有序。如图:
如图所示,我们将数组按照gap进行分组,然后通过不断改变gap的值 ,这样可以让数组更快的接近有序,当gap=1时,实际就是直接插入排序。实现逻辑:
//直接插入排序
void ShellSort(int* a, const int n)
{
int gap = n ;
while ( gap > 1)
{
gap = gap / 3 + 1; //保证最后一次的gap等于1,直接插入排序
//这里用了多组并排的思想,将数组分成了gap组,
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tmp = a[end + gap]; //暂存待插入的值
//单趟排序,找到待插入元素应该存在的位置
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end+gap] = tmp;
}
}
}
二、选择排序
-
选择排序
所谓的选择排序就是,每一次遍历数组选出较小的值,放到对应的位置上,如图分析:
根据上面分析,实现逻辑如下;
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
void SelectSort(int* a, const int n)
{
int begin = 0, end = n - 1;
while (begin < end)
{
//一次性找到最大与最小,并放到相应的位置上,依次类推
int mini = begin, maxi = begin;
for (int i = begin + 1; i <= end; ++i)
{
if (a[mini] > a[i])
mini = i;
if (a[maxi] < a[i])
maxi = i;
}
Swap(&a[mini], &a[begin]);
//maxi与begin重合时,上面交换,就会使a[maxi]成为最小,而a[mini]成为最大,因此需要调整
if (maxi == begin)
{
maxi = mini;
}
Swap(&a[maxi], &a[end]);
++begin;
--end;
}
}
-
堆排序
堆排序明天单独写一篇,剩下的下次再写把。