接下来我们对简单选择排序和堆排序做详细额介绍,排序过程中使用的数据仍和前两节排序过程中使用的数据相同:int a[10] = {45,12,36,76,45,9,33,19,87,23};
1、简单选择排序
1.1 简单选择排序的基本思想
简单选择排序的基本思想就体现在“选择”这两个字上,每一趟排序都是从没有排序的数据中选出最小的元素与第一个数据进行交换。给定一个有n个数据的数组,算法的执行步骤如下:
(1)从左到右遍历整个数据,从数组中选出最小的元素;
(2)如果选出的最小元素不在第一个位置,将整个最小元素与第一个位置数据进行交换;
(3)对剩余的n-1个数执行(1),(2)的操作;
1.2 实现代码
#include<iostream>
using namespace std;
void searchSort(int *a, int n)
{
for (int i = 0; i < n - 1; ++i)
{
int indexSmallestEle = i;
for (int j = i; j < n; ++j)//寻找后面的最小的数据
{
if (a[j] < a[indexSmallestEle])
{
indexSmallestEle = j;
}
}
if (indexSmallestEle != i)
{
int temp = a[i];
a[i] = a[indexSmallestEle];
a[indexSmallestEle] = temp;
}
}
}
int main()
{
int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };
searchSort(a, 10);
return 0;
}
1.3 排序结果1.4 算法分析
由于简单选择排序每一次都是从待排序数据中选择出一个最小的元素与第一个位置发生交换,每次确定一个元素的最终位置。有n个元素的话,经过n-1趟排序就能达到最终排序效果。
第一趟排序要比较n个数据,筛选出最小的;第二趟排序要比较n-1个数据,筛选最小的;,....,第n-1次要比较2个数据。这样简单选择排序的时间为n + n-1 + n-2 + ... + 2,不会出现最好的最坏情况,因此时间复杂度为O(n^2)。
1.5 稳定性
不稳定。
例如{ 2, 2, 1}, 第一次选的时候变成 { 1, 2, 2 }, 两个2的次序就变了。
2、堆排序
2.1 堆排序的基本思想
首先我们要知道堆的含义。这里做一个简单的回顾,我们所说的对一般是指二叉堆(当然还有其他的堆,如斐波那契堆),本质是一颗完全二叉树,要求树中每个非叶节点的值要大于等于(或者小于等于)他的孩子节点的值,这样的堆叫作大顶堆(小顶堆)。堆排序就是给定一组数,我们将这组数按从左到右的顺序构造一颗完全二叉树,然后调整树中元素的位置使其满足大顶堆(小顶堆)的性质,就完成了对排序。
本文中我们以大顶堆为例来对数组int a[10] = {45,12,36,76,45,9,33,19,87,23} 进行排序。将该数据建立完全二叉树后,下标为i的节点的左孩子下标是2i + 1,右孩子下标为 2i + 2。而下标为i的节点的父节点为(int)((i-1)/2)。数组a对应的初始完全二叉树如下图(其中二叉树中每个节点的旁边数字是该节点在数组中的下标):
2.2 实现代码
#include<iostream>
using namespace std;
void fixHeap(int *a, int p, int n)//对数组中下标为p的数据进行调整
{
if (p <= (n - 1) / 2)//下标为p点不是一个叶子节点
{
int largestIndex = p;
if (2 * p + 1 <= n - 1)
{
if (a[2 * p + 1] > a[p])
{
largestIndex = 2 * p + 1;
}
}
if (2 * p + 2 <= n - 1)
{
if (a[2 * p + 2] > a[largestIndex])
{
largestIndex = 2 * p + 2;
}
}
if (largestIndex != p)
{
int temp = a[p];
a[p] = a[largestIndex];
a[largestIndex] = temp;
fixHeap(a, largestIndex, n);
}
}
else
{
return;
}
}
void heapSort(int *a, int n)//从倒数第一个非叶节点开始,从下到上进行调整
{
for (int i = (n - 1) / 2; i >= 0; --i)
{
fixHeap(a, i, n);
}
}
void print(int *a, int n)
{
for (int i = 0; i < n; ++i)
{
cout << a[i] << " ";
}
cout << endl;
}
int main()
{
int a[10] = { 45, 12, 36, 76, 45, 9, 33, 19, 87, 23 };
cout << "排序前:";
print(a, 10);
heapSort(a, 10);
cout << "排序后:";
print(a, 10);
return 0;
}
2.3 排序结果
排序的最终结果为:
排序过程:整个堆排序是一个从下往上的调整过程,对每一个调整又是从当前位置开始往下调整的过程。
2.4 算法分析
由算法产生的结果可知,堆排序并不能将数组中的数据按照完全的降序,而只是满足了大顶堆的性质(每个节点值都大于孩子节点的值)。由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件,平均时间复杂度为O(n*logn).
2.5 稳定性
不稳定。
2.6 堆排序用途
输入M个数,从中找出前K个最小的数并保持有序。
这里使用堆排序是个不错的选择,使用输入的前K个数建立一个大顶堆。对于后面的数,每输入一个数据就与堆顶元素进行比较,如果大于堆顶元素,继续输入;如果小于堆顶元素,则将该输入数据设为堆顶数据,然后通过调整构成新的大顶堆。这样遍历一遍M个数据就可以找到K个最小数。