1. 基本思想 : 在待排序的数据里选择最小(大)的元素放在其最终的位置
2. 基本操作 :
2.1 通过 n-1 次关键字比较, 从 n 个记录中选择最小的记录, 与第一个记录交换
2.2 通过 n-2 次关键字比较, 从 n-1 个记录中选择最小的记录, 与第一个记录交换
3.3 重复上述操作, 共进行 n-1 趟排序后, 排序结束。
void SelectSort(Sqlist &L){
for(int i=1; i<L.length; i++){
int pos=i;
for(int j=i+1; j<=n; j++)
if(L.r[j]<L.r[pos]) pos=j; // 记录最小值位置
if(pos!=i) swap(L.r[i], L.r[pos]);
}
}
算法分析 :
1. 简单选择排序是一种不稳定的排序算法
2. 时间复杂度 : 最好 O(n^2), 最坏 O(n^2), 平均 O(n^2)
a[i]<=a[2*i]&&a[i]<=a[2*i+1]
或 a[i]>=a[2*i]&&a[i]>=a[2*i+1]
(
1
≤
i
≤
?
n
/
2
?
1\leq i \leq \lceil n/2 \rceil
1≤i≤?n/2?), 则分别称该序列为 小根堆 ????????????或 大根堆
从堆的定义可以看出 , 堆实质是满足如下性质的完全二叉树 :
二叉树中任一非叶子结点均小于(大于)它的孩子节点
堆排序 : 若在输出堆顶的最小值(最大值)后 , 使得剩余 n-1 个元素的序列重又建成一个堆, 则得到 n 个元素的次小值(次大值), 如此反复, 便能得到一个有序序列, 这个过程称为堆排序
筛选(sift) : 在堆调整的过程中 , 总是将根节点(即被调整结点)与左右孩子进行比较 , 若不满足堆的条件 , 则将根节点与左右节点的最大值交换 , 这个调整过程一直进行到所有子树均为堆或将被调整结点交换到叶子。
算法 : Sift
输入 : 待调整的记录 r[k]~r[m], r[k+1]~r[m]满足堆的条件
输出 : 无
1. 设置 i 和 j, 分别指向当前要筛选的结点和要筛选结点的左孩子
2. 若结点 i 已经是叶子 , 则筛选完毕, 算法结束; 否则, 执行以下操作 :
2.1 将 j 指向结点 i 的左右孩子中的较大者
2.2 如果 R[i]>R[j], 算法结束, 筛选完毕
2.3 如果 R[i]<R[j], 则将 R[i]与R[j]交换; 令 i=j, 转步骤2继续筛选;
void Sift(int r[], int k, int m){ // 大根堆 Sift
int i=k, j=2*i; // i 是被筛选结点 , j 是结点 i 的左孩子
while(j<=m){ // 没有筛选到叶子结点
if(j+1<=m && r[j]<r[j+1]) j++; // 令 j 指向左右孩子中的较大值
if(r[i]>r[j]) break;
Swap(r[i], r[j]); // 交换根结点与结点 j
i=j; j=2*i; // 被筛选结点位于原来结点 j 的位置
}
}
对 r[n]
数组进行堆排序
void HeapSort(int r[], int n){
// 初始建堆, 从最后一个分支结点到根结点
for(int i=n/2; i>=1; i--) Sift(r, i, n);
for(int i=n; i>1; i--){ // 进行 n-1 趟排序
Swap(r[1], r[i]); // 交换 r[1] 与 r[i]
Sift(r, 1, i-1); // 对 r[1] 到 r[i-1] 重新建堆
}
}
堆运行的时间主要消耗在初始建堆和建堆时进行的反复筛选上。初始建堆需要 O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n) , 第 i i i 次取堆顶记录重建堆的时间是 O ( l o g 2 i ) O(log_2i) O(log2?i) , 并且需要取 n ? 1 n-1 n?1 次堆顶记录 , 因此总时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2?n) , 这是堆排序的最好, 最坏和平均时间复杂度
堆排序是一种不稳定的排序算法
对待排序列的初始状态不敏感