1. 排序的概念和应用
1.1、排序的概念
- 排序:什么是排序?排序就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作
- 内部排序:数据元素全部放在内存中的排序
- 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序
- 稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的
1.2、排序的应用
- 比如:京东上面的综合排序和价格排序
- 比如:高校之间热度的排序
- 还有我常见的"点外卖",我们一般都会点热度最高的店铺,还有就是我们在学校中考试成绩的排序等等…
1.3、常见的排序算法
2. 插入排序
2.1、直接插入排序
- 直接插入排序其基本思想是:
把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列
- 实际中我们玩扑克牌时,就用了插入排序的思想…
直接插入排序(单趟)过程分析:
- 当插入第(i>=1)个元素时,前面的end[0],end[1],end[2]…end[i-1]已经排好序
- 这时,我们将保存end[i]的值,跟end[i-1],end[i-2]…end[0]进行比较
- 当end[i]的值比待比较的值小时,将end[i-1]的值覆盖到end[i],然后–end
- 当比待比较的值小时,跳出循环,然后直接插入到end[i]位置.
代码实现:
void InsertSort(int* p, int n)
{
for (int i = 0; i < n - 1; ++i)
{
int end = i;
//保存end下标后面的数据
int tmp = p[end + 1];
//当end为
while (end >= 0)
{
//升序,判断tmp是否小于p[end]
if (tmp < p[end])
{
//小于则覆盖end后面的数据
p[end + 1] = p[end];
--end; //end往前移
}
else
{
break;
}
}
//当end走到-1时,放在循环里面处理会导致数组越界访问
p[end + 1] = tmp;
}
}
直接插入排序的特性总结:
- 元素集合越接近有序,直接插入排序算法的时间效率越高
- 时间复杂度:O(N^2)
- 空间复杂度:O(1),它是一种稳定的排序算法
- 稳定性:稳定
2.2、希尔排序
希尔排序:希尔排序又称"缩小增量法"。
希尔排序的基本思想是:
- 先选定一个整数的间隔gap,把待排序文件中所有的数据分成一个个组,所有距离为gap的数据分在同一组内,并对每一组内的数据进行排序。
- 随后,我们将重复上述的分组和排序。
- 当gap为1时,就是直接插入排序了,最后,所有数据都在统一组内排好序了。
- 希尔排序其实是插入排序的变形,希尔每次走gap步(预排序),而插入排序走1步。
预排序:每次间隔为gap,直到i < 0时,才进行下一轮的预排序。每次进行间隔gap / 3 + 1的预排。当gap为1时,说明数据已经接近有序,直接进行插入排序
代码实现:
void ShellSort(int* p, int n)
{
int gap = n;
while (gap > 1)
{
//比如有1000个数,gap之间的间隔为"334",第二次间隔为112次,第三次间隔为"38",第四次为"13",第五次为"5", 第六次为"2"(前面为"预排序"),最后为"1"进行(插入排序)
//当gap > 1时,进行预排序-----当gap等于1时,说明已经接近有序,进行"插入排序"
//一开始进行排序时,i必须小于n - gap,如果i < n时,会导致tmp赋值时,会导致越界
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tmp = p[end + gap];
while (end >= 0)
{
if (tmp < p[end])
{
p[end + gap] = p[end];
end -= gap;
}
else
{
break;
}
}
p[end + gap] = tmp;
}
}
}
注意:gap每次走几步是没有规定的,可以每次走gap/2步,甚至是每次走gap/5+1步,控制好最后gap为1就行
希尔排序的特性总结:
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
- 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定
- 时间复杂度:因为代码中的gap是按照"Knuth"提出的方式取值的,而且Knuth进行了大量的试验统计,所以时间复杂度就暂时按照:O(N1.25)到 O(1.6*N1.25)来算
- 空间复杂度:O(1)
- 稳定性:不稳定
3. 选择排序
3.1 选择排序
基本思想:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完
选择排序过程解析:
- 在元素集合array[i]-----array[n-1]中选择最大(小)的数据元素
- 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
- 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
代码实现:
void SelectSort(int* p, int n)
{
//找数组中最大值和最小值,然后交换到最左边和最右边里面
int left = 0;
int right = n - 1;
while (left < right)
{
int Max = right, Min = left;
for (int i = left; i <= right; ++i)
{
//在左闭右闭[left, right]区间中找最大值和最小值
if (p[i] > p[Max])
Max = i;
if (p[i] < p[Min])
Min = i;
}
//交换数据
Swap(&p[left], &p[Min]);
//如果left和Max重叠,需修正Max
if (left == Max)
Max = Min;
Swap(&p[right], &p[Max])