简单选择排序
- 基本思想:在待排序的数据中选出最大(小)的元素放在其最终的位置。
- 基本操作:
1.首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将其与第一个记录交换
2.再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将其与第二个记录交换
3.重复上述操作,共进行n-1趟排序后,排序结束。
- 算法描述
void SelectSort(SqList &K)
{
for (i = 1; i < L.length; ++i)
{
k = i;
for (j = i + 1; j <= L.length; j++)
if (L.r[j].key < L.r[k].key)
k = j; //记录最小元素的位置
if (k != i)
{
L.r[0] = L.r[i]; //交换
L.r[i] = L.r[k];
L.r[k] = L.r[0];
}
}
}
- 性能分析
- 时间复杂度
- 记录移动次数
- 最好情况:0
- 最坏情况:3(n-1)
- 比较次数:无论待排序序列处于什么状态,选择排序所需进行的“比较次数”都相同, ∑ i = 1 n − 1 ( n − i ) = n 2 ( n − 1 ) \sum\limits_{i = 1}^{n - 1} {(n - i){\rm{ = }}\frac{n}{2}(n - 1)} i=1∑n−1(n−i)=2n(n−1)
- 记录移动次数
- 稳定性:简单选择排序是不稳定排序 ,但可以改进,将其变成稳定的。
- 空间复杂度:需要一个辅助空间,空间复杂度为 O ( 1 ) O(1) O(1)。
- 时间复杂度
堆排序
- 堆:
-
定义: 若n个元素的序列 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,⋯,an}满足
{ a i ≤ a 2 i a i ≤ a 2 i + 1          或          { a i ≥ a 2 i a i ≥ a 2 i + 1 \left\{ {\begin{array}{l} {{a_i} \le {a_{2i}}}\\ {{a_i} \le {a_{2i + 1}}} \end{array}} \right.\;\;\;\;或\;\;\;\;\left\{ {\begin{array}{l} {{a_i} \ge {a_{2i}}}\\ {{a_i} \ge {a_{2i + 1}}} \end{array}} \right. {ai≤a2iai≤a2i+1或{ai≥a2iai≥a2i+1
则分别称该序列 { a 1 , a 2 , ⋯   , a n } \{a_1,a_2,\cdots,a_n\} {a1,a2,⋯,an}为小根堆和大根堆。
从堆的定义可以看出,堆的实质是满足如下性质的完全二叉树:二叉树中任一非叶子结点均小于(大于)它的孩子结点。 -
例子
-
堆排序:若在输出堆顶的最小值(最大值)后,使得剩余n-1个元素序列重新建成一个堆,则得到n个元素的次小值(次大值)…如此反复,便能得到一个有序序列,这个过程称之为堆排序。(每次从堆顶取一个元素)
-
需解决的问题
- 如何从一个无序序列建成一个堆?
- 如何在输出堆顶元素后,调整剩余元素为一个新的堆?
-
下面主要从这两个问题出发,先介绍第二个问题的解决方法,再介绍第一个问题的解决方法。
- 堆调整
- 以小根堆为例
- 输出堆顶元素后,以堆中最后一个元素替代之;
- 然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;
- 重复上述过程,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。
- 例子说明 :小根堆如下,
1.输出堆顶元素13,用最后一个元素97替换堆顶元素,
2.将此时的根结点(值为97的元素)与左右孩子进行比较,并与其中的较小者发生交换,
3.重复步骤2,直到元素97为叶子结点,此时调整完成,输出堆顶元素27, - 算法描述
- 以小根堆为例
void HeapAdjust(elem R[], int s, int m)
{
rc = R[s];
for (j = 2 * s; j <= m; j *= 2)
{
if (j < m&&R[j] < R[j + 1])
++j;
if (rc >= R[j])
break;
R[s] = R[j];
s = j;
}
R[s]=rc
}
可以看出,对一个无序序列反复“筛选”就可以得到一个堆;即,从一个无序序列建立堆的过程就是一个反复“筛选”的过程。
- 建立堆
-
由于堆实质上是一个线性表,那么我们可以顺序存储一个堆。
-
例子:有关键字{49,38,65,97,76,13,27,49}的一组记录,将其按关键字调整为一个小根堆。
具体步骤:
1.按顺序存储,建立线性表,
2.所有叶子结点都是一个堆,所以不用调整,只需要调整非叶子结点,即上图中的97,38,65,49。从最后一个非叶子结点开始,依次向前调整。最后一个非叶子结点为第 n / 2 n/2 n/2个元素,将97与其左右孩子比较,发现97>49,则要交换97和49。
3.再以序号为 n / 2 − 1 n/2-1 n/2−1的结点为根的二叉树调整为堆,即上面所说的堆调整方法,此时,是65元素与左右孩子比较,交换65和13,
4.再以序号为 n / 2 − 2 n/2-2 n/2−2的结点为根的二叉树调整为堆,即上面所说的堆调整方法,此时,是38元素与左右孩子比较,不用交换。
5.再以序号为 n / 2 − 3 n/2-3 n/2−3的结点为根的二叉树调整为堆,即上面所说的堆调整方法,此时,是49元素与左右孩子比较,交换49和13。
6.此时还没结束,因为要调整到叶子结点才算结束,此时的49还要与其左右孩子比较,交换49和27。至此,整个无序的树被调整为一个小根堆,
-
对于堆的建立,用以下语句可以实现
-
for (i = n / 2; i >= 1; i--)
HeapAdjust(R, i, n);
- 堆排序算法描述
void HeapSort(elem R[]) //堆R[1]到R[n]进行堆排序
{
int i;
for (i = n / 2; i >= 1; i--)
HeapAdjust(R, i, n); //建初始堆
for (i = n; i > 1; i--) //进行n-1趟排序
{
Swap(R[1], R[i]); //根与最后一个元素交换
HeapAdjust(R, 1, i-1); //对R[1]到R[i-1]重新建堆
}
}
- 性能分析
- 初始化堆所需时间不超过 O ( n ) O(n) O(n);
- 排序阶段
- 依次重新建堆所需时间不超过 O ( l o g n ) O(logn) O(logn)
- n-1次循环所需时间不超过 O ( n l o g n ) O(nlogn) O(nlogn)
- 时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
- 堆排序在最好和最坏的情况下都为 O ( n l o g n ) O(nlogn) O(nlogn),这是堆排序的最大优点。无论待排序序列的记录是正序还是逆序,都不会使堆排序处于“最好”或“最坏”状态。
- 空间复杂度为 O ( 1 ) O(1) O(1),只需要一个用于记录交换的辅助存储空间。
- 堆排序是一种不稳定的排序方法,它不适用于待排序记录个数n较少的情况,但对于n较大的文件还是很有效的。