一.冒泡排序
1.1冒泡排序引入
对于任何编程语言,当我们学到循环和数组的时候,都会介绍一种排序算法:冒泡排序;深入学习更多排序算法后和在实际使用情况中,冒泡排序的使用还是极少的。它适合数据规模很小的时候,而且它的效率也比较低,但是作为入门的排序算法,还是值得学习的。
1.2冒泡排序的核心思想与算法分析
核心思想:相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。 因为较大的数会下沉,所以我们也可以将冒泡排序称作沉石排序。
算法分析:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个;
- 每趟从第一对相邻元素开始,对每一对相邻元素作同样的工作,直到最后一对;
- 针对所有的元素重复以上的步骤,除了已排序过的元素(每趟排序后的最后一个元素),直到没有任何一对数字需要比较。
1.3实例说明
以3,2,5,6,1,11为例子,排序过程:
- 底部画线的为已确定的数据
- 有n个数据,需要跑n-1趟即可
1.4优化
上面的排序过程在第四趟处理完时,就已经完全有序了(肉眼观察的),理应直接退出循环即可,第五趟顺序完全可以省略,那么我们就会有一个问题:
怎样判断数据完全有序? 从先到后遍历一遍,发现数据两两比较都是前面小于后面(没有交换操作),这个时候,就可以判定数据已经完全有序。
1.5代码实现
void BubbleSort(int* arr, int len)
{
int count = 0;
bool tag = true;
for (int i = 0; i < len - 1; i++)
{
tag = true;
for (int j = 0; j + 1 < len - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
tag = false;
}
}
count++;
if (tag)
{
break;
}
}
printf("%d\n", count);
}
1.6性能分析
- 时间复杂度:o(n^2);
- 空间复杂度:o(1);
- 稳定性:稳定
二.堆排序
2.1堆的基础知识
2.1.1堆是什么
堆是一种数据结构,一种叫做完全二叉树的数据结构。
2.1.2堆的性质
这里我们用到两种堆,其实也算是一种。
大顶堆:每个节点的值都大于或者等于它的左右子节点的值。
小顶堆:每个节点的值都小于或者等于它的左右子节点的值。
查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么
1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数)
2.左孩子索引:2*i+1
3.右孩子索引:2*i+2
如下图所示: 两种堆就是如上图所示。
如果我们把这种逻辑结构映射到数组中,如下所示: 大顶堆: 小顶堆: 从这里我们可以得出以下性质:
对于大顶堆:arr[i] >= arr[2i + 1] && arr[i] >= arr[2i + 2]
对于小顶堆:arr[i] <= arr[2i + 1] && arr[i] <= arr[2i + 2]
2.2堆排序的核心思想与基本步骤
核心思想:将无序数组构造成一个大顶堆,固定一个最大值,将剩余的数重新构造成一个大顶堆,重复这样的过程构造堆。
基本步骤:
- 将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;
- 将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;
- 重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部;
- 最后,就得到一个有序的序列了。
2.3实例说明与分析
以4,5,8,2,3,9,7,1为例,按照上面的基本步骤进行排序,过程如下: 1.将无序序列构造为一个大顶堆 2.现在我们需要找到最后一个非叶子节点的位置,也就是索引值。
对于一个完全二叉树,在填满的情况下,每一层的元素个数是上一层的二倍,根节点数量是1,所以最后一层的节点数量,一定是之前所有层节点总数+1,所以,我们能找到最后一层的第一个节点的索引,即节点总数/2(根节点索引为0),这也就是第一个叶子节点,所以第一个非叶子节点的索引就是第一个叶子结点的索引-1。
那么对于填不满的二叉树呢?这个计算方式仍然适用,当我们从上往下,从左往右填充二叉树的过程中,第一个叶子节点,一定是序列长度/2,所以第一个非叶子节点的索引就是(数组长度/ 2 -1)。
现在找到了最后一个非叶子节点,即元素值为2的节点,比较它的左右节点的值,是否比他大,如果大就换位置。这里因为1<2,所以,不需要任何操作,继续比较下一个,即元素值为8的节点,它的左节点值为9比它本身大,所以交换。 3.因为元素8没有子节点,所以继续比较下一个非叶子节点,元素值为5的节点,它的两个子节点值都比本身小,不需要调整;然后是元素值为4的节点,也就是根节点,因为9>4,所以需要调整位置 4.原来元素值为9的节点值变成4了,而且它本身有两个子节点,所以,这时需要再次调整该节点 5.排序序列,将堆顶的元素值和尾部的元素交换 6.将剩余的元素重新构建大顶堆,其实就是调整根节点以及其调整后影响的子节点,因为其他节点之前已经满足大顶堆性质 7.继续交换,堆顶节点元素值为8与当前尾部节点元素值为1的进行交换 8.重新构建大顶堆 9.重复交换 10.重新构建大顶堆
11.重复交换
12.重新构建大顶堆 13.重复交换
14.重新构建大顶堆
15.重复交换
16.重新构建大顶堆 17.重复交换
18.重新构建大顶堆 19.继续交换 这里我么就交换结束了 得到了最后的结果。
2.4代码实现
void HeapAdjust(int arr[], int start, int end)
{
int tmp = arr[start];
for (int i = start * 2 + 1; i <= end; i = start * 2 + 1)
{
if (i<end && arr[i + 1] > arr[i])
{
i++;
}
if (arr[i] > tmp)
{
arr[start] = arr[i];
start = i;
}
else
{
break;
}
}
arr[start] = tmp;
}
void HeapSort(int* arr, int len)
{
for (int i = (len - 1 - 1) / 2; i >= 0; i--)
{
HeapAdjust(arr, i, len - 1);
}
for (int i = 0; i < len - 1; i++)
{
int tmp = arr[0];
arr[0] = arr[len - 1 - i];
arr[len - 1 - i] = tmp;
HeapAdjust(arr, 0, (len - 1 - i) - 1);
}
}
2.5性能分析
- 时间复杂度:最好和最坏的情况时间复杂度都是(n*logn) 。
- 空间复杂度:O(1)。
- 稳定性:不稳定。
|