直接选择排序
代码展示
//最简单的
//遍历一遍选出最大放到最后
//再遍历一遍选出剩下的最大的放到后面,以此类推
//这样很慢!O(N*N)
//我们尝试用一次选一个最大的和一个最小的,这样就把效率提升一倍,但总体来说还是很慢
void Swap(int *px, int *py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
void SeletSort(int *a, int n)
{
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[min]) //如果a[i]最小
{
//记录下标
min = i;
}
if (a[i] > a[max])
{
max = i;
}
//交换
//Swap(&a[begin], &a[min]);
//Swap(&a[end], &a[max]);
//这里会出问题,如果max就是他的begin,begin和max重叠了,明明a[min]和a[begin]已经换过了,但是因为max下标没动,导致初始位置又和end发生交换了。
Swap(&a[begin], &a[min]);
if (begin == max)
{
max = min;
}
Swap(&a[end], &a[max]);
}
begin++;
end--;
}
}
时间复杂度
虽然经过了优化,但是时间复杂度还是应为:O(N2)
- 最好的情况就是虽然全部有序,但是程序拿到数组之后都要经过遍历才能判断出最大值最小值。所以仍旧是O(N2)
所以选择排序是最烂、最慢排序。(虽然代码很简单
堆排序
编写思路
先建堆,然后进行堆排序。
排升序,按理来说需要找出最小值,然后找出次小的值,以此类推,这样思考的话我们需要建立小堆,但是!!,
试想如果建立小堆,无论是向上调整,还是向下调整,如果删掉堆顶数据,从堆顶的下一个位置视作堆顶。那么之前建立好的堆的关系就全部乱了,必须重新排列重新建堆,那么时间复杂度会飙升->O(N*N)。(这个时间复杂度还不如遍历选数呢)
所以我们需要建大堆:
因为大堆的向下调整必须要求根的左右子树都为大堆,所以我们需要从倒数第一个叶子节点的父节点开始向下调整。随后减减父亲节点的下标,就可以逐个找到所有的父亲节点,并且正好是整棵树都向下调整完毕。
这样子我们就选出了根节点(最大值),随后让其与数组中的最后一个元素交换位置,然后屏蔽掉最后一个元素(最大值已经找到了,就可以把它屏蔽掉了)
随后向下调整,以此类推,就可以找到最大值,次大值,…,最后选出最小值。
代码展示
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);
}
}
时间复杂度
时间复杂度为:O(N*log2N),类似于ShellSort
冒泡排序
冒泡排序和快速排序都是属于交换排序。
编写思路
冒泡排序的思想就是前一个如果比后一个大的话就交换位置(排升序)。这样一次可以找出来一个最大值,所以我们需要再嵌套循环n次。
首先我们还是写一趟的循环。
for (int i = 1; i < n;i++)
{
if(a[i-1]>a[i])
{
Swap(a[i - 1], a[i]);
}
i++;
}
嵌套循环:
//最终代码
void BubbleSort(int *a, int n)
{
//冒泡排序就是前一个比后一个大就交换
for (int j = 0; j < n;j++)
{
for (int i = 1; i < n-j; i++)
{
if (a[i - 1] > a[i])
{
Swap(a[i - 1], a[i]);
}
i++;
}
}
}
代码优化
冒泡排序还是可以进行一定的优化的:
可以考虑下面这种情况:
相邻的两个数 前数都小于后一个数,这就代表数组以及就有序。我们可以加一个变量来判断是否发生过交换,如果没发生过交换,那么就已经有序了。
void BubbleSort(int *a, int n)
{
//冒泡排序就是前一个比后一个大就交换
for (int j = 0; j < n;j++)
{
int exchange = 0;
for (int i = 1; i < n-j; i++)
{
if (a[i - 1] > a[i])
{
Swap(a[i - 1], a[i]);
}
i++;
if(exchange)
{
break;
}
}
}
}
同样的,它也适用于接近有序的情况,考虑
交换完一次后,第二轮的循环开始,发现exchange为0,说明并为交换,那么循环中止,完成排序。
时间复杂度
冒泡排序是典型的O(N2)排序。
- 最坏情况是N2
- 最好情况是N
选择排序、直接插入排序、冒泡排序的比较
这三个排序都是时间复杂度O(N2)级别的排序,所以可以拿出来比较一下。
选择排序无疑是最差的,因为它无论最好情况还是最坏情况都是O(N2)。
而直接插入排序和冒泡排序,最好情况是O(N)。但是虽然最好情况相同,直接插入排序更优。因为:
- 对于已经有序的数组排序,一样好。
- 对于接近有序的数组,直接插入排序更好。
- 综合来说,直接插入排序对局部有序的数组的适应性更优。
所以,直接插入排序更好。
考虑如下数组:
-
冒泡排序比了:n-1 + n-2 次
-
直接插入比了:n次