选择排序算法就是每一趟从待排序的记录中选出关键字最小(最大)的记录,顺序放在已排好序的子文件的最后(最前),直到全部记录排序完毕。常见的选择排序有直接选择排序(Selection Sort),堆排序(Heap Sort),平滑排序(Smooth Sort),笛卡尔树排序(Cartesian Sort),锦标赛排序(Tournament Sort),循环排序(Cycle)。下面介绍前两种:
(一)直接选择排序
最差时间复杂度:O(n^2)
最优时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
稳定性:不稳定
直接选择排序(Selection Sort),这是一种简单直观的排序算法。它首先在未排序序列中找到最小(大)元素,存放到排序序列的其起始位置,然后再从剩余未排序的序列元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素排序完毕。
算法示意图:
实现代码:
- void SelectSort(int *a, int len)
- {
- for (int i=0; i<len-1; i++)
- {
- int k = i;
- int key = a[i];
- for (int j=i+1; j<len; j++)
- {
- if (a[j]<key)
- {
- k = j;
- key = a[j];
- }
- }
- if (k!=i)
- swap(a[i], a[k]);
- }
- }
(二)堆排序
最差时间复杂度:O(nlogn)
最优时间复杂度:O(nlogn)
平均时间复杂度:O(nlogn)
稳定性:不稳定
堆排序(Heap Sort),是利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
通常堆是通过一维数组来实现的,在起始数组为0的情形中,对于节点i:其左子节点的下标为 (2*i+1);
其右子节点的下标为 (2*i+2);
其父节点的下标为 floor((i-1)/2)。
在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义一下三个操作:
1.最大堆调整(Max Heapify):在假定节点i的左右子节点为根的两颗二叉树都是最大堆的前提下,确保父节点大于子节点,否则下降原父节点,最终使以i为根的子树成为最大堆。
2.创建最大堆(Build Max Heap):将堆所有数据重新排序,对所有非叶子节点调用一次Max Heapify。
3.堆排序(Heap Sort):首先创建最大堆,然后依次将堆的根节点与末节点交换、剔除末节点、对根节点进行最大堆调整,直到堆中的节点数为1,排序结束。
算法示意图:
#include <iostream>
using namespace std;
void AdjustDown(int A[], int i, int len)
{
int temp = A[i]; // 暂存A[i]
for(int largest=2*i+1; largest<len; largest=2*largest+1)
{
if(largest!=len-1 && A[largest+1]>A[largest])
++largest; // 如果右子结点大
if(temp < A[largest])
{
A[i] = A[largest];
i = largest; // 记录交换后的位置
}
else
break;
}
A[i] = temp; // 被筛选结点的值放入最终位置
}
void BuildMaxHeap(int A[], int len)
{
for(int i=len/2-1; i>=0; --i) // 从i=n/2-1到0,反复调整堆
AdjustDown(A, i, len);
}
void HeapSort(int A[], int n)
{
BuildMaxHeap(A, n); // 初始建堆
for(int i=n-1; i>0; --i) // n-1趟的交换和建堆过程
{
// 输出最大的堆顶元素(和堆底元素交换)
A[0] = A[0]^A[i];
A[i] = A[0]^A[i]; //写入排序后的元素值
A[0] = A[0]^A[i];
// 调整,把剩余的n-1个元素整理成堆
AdjustDown(A, 0, i); //得到剩余的最大值
}
}
int main() {
int a[] = {3,2,5,6,1,9,0,7};
HeapSort(a,8);
}