基本思想: 在待排序的数据中心选出最大(小)的元素放在其最终的位置;
基本操作:
1. 首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换;
2. 在通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换;
3. 重复上述步骤,攻进行n-1趟排序后,排序结束;
/*
* 简单选择排序
*/
void SelectSort(SqList &L) {
for (int i = 1; i < L.length; i++) {
int k = i; // 代表最小值
for (int j = i+1; j < L.length; j++) {
if (L.r[j].key < L.r[k].key) k = j; // 如果发现i之后的元素还有更小的j,则记录下来
}
if (k != i) { // 交换
int temp = L.r[i];
L.r[i] = L.r[k];
L.r[k] = temp;
}
}
}
时间复杂度:
记录移动次数:
最好情况: 0;
最坏情况: 3(n-1);
比较次数: 无论待排序列处于什么状态,选择排序所需进行的"比较"次数都相同;
算法稳定性:
简单选择排序是不稳定排序;
堆的定义:
若n个元素的序列{a1, a2, a3, ..., an}满足:
ai <= a2i ai <= a2i+1
ai >= a2i ai >= a2i+1
则分别称该序列{a1, a2, a3, ..., an}为小根堆和大根堆.
从堆的定义可以看出,堆实质是满足如下性质的完全二叉树: 二叉树中任一非叶子结点均小于(大于)它的孩子结点;
堆排序:
若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素的序列中又建成一个堆,则得到n个元素的次小值(次大值) ...如此反复,便能得到一个有序序列,这个过程称之为堆排序.
以小根堆为例:
1. 输出堆顶元素后,以堆中最后一个元素替代之;
2. 然后将根结点值与左右子树的根结点值进行比较,并与其中小者进行交换;
3. 重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为"筛选";
可以看出:
对一个无序序列反复"筛选"就可以得到一个堆;
即:从一个无序序列建堆的过程就是一个反复"筛选"的过程
单节点的二叉树就是一个堆;
在完全二叉树中所有以叶子结点(序列i> n/2) 为根的子树是堆;
这样,我们只需依次将以序号为n/2, n/2-1, … , 1的结点为根的子树均调整为堆即可;
由于堆实质上是一个线性表,那么我们可以顺序存储一个堆.
从最后一个非叶子结点开始,以此向前调整:
1. 调整从第n/2个元素开始,将以该元素为根的二叉树调整为堆;
2. 将以序号为n/2-1的结点为根的二叉树调整为堆;
3. 再将以序号为n/2-2的结点为根的二叉树调整为堆;
4. ...
实质上,堆排序就是利用完全二叉树中父结点与孩子结点之间的内在关系来排序的.
/*
* 堆排序算法
*/
void HeapSort(SqList &L) { // 对L.r[1]到L.r[n]进行堆排序
int i;
int n = L.length - 1;
for (i = n/2; i >= 1; i--)
HeapAdjust(L, i, n); // 建立初始堆
for (i = n; i > 1; i--){ // 进行n-1趟排序
Swap(L.r[1].key, L.r[i].key); // 根与最后一个元素交换(未实现)
HeapAdjust(L, 1, i-1); // 对L.r[1]到L.r[i-1]重新建堆
}
}// HeapSort
/*
* 堆排序中的筛选算法
* 已知L.r[s...m]中记录的关键字除L.r[s]之外均满足堆的定义,本函数调整L.r[s]的关键字,使L.r[s...m]成为一个大根堆
*/
void HeapAdjust(SqList &L, int s, int m) {
rc = L.r[s].key; // 将需要调整的元素赋值给rc
for (int j = 2*s; j <= m; j *= 2) { // 沿着key较大的孩子结点向下筛选
if (j < m && L.r[j].key < L.r[j+1].key) j++; // j为key较大的记录的下标
if (rc >= L.r[j].key) break;
L.r[s].key = L.r[j].key;
s = j; // rc应插入在位置s上
}// for
L.r[s].key = rc; // 插入
}// heapAdjust
堆排序的时间主要耗费在"创建初始堆"和"调整新堆"时进行的反复"筛选"上.堆排序在最坏情况下,其时间复杂度也为O(nlog~2~n),这是堆排序的最大优点.
无论待排序列中的记录是正序还是逆序排列,都不会使堆排序处于"最好"或"最坏"的状态.
另外,堆排序需一个记录大小供交换用的辅助空间.
然而堆排序是一种不稳定的排序方法,他不适用于待排序记录个数n较少的情况,但对于n较大的文件还是很有效的.