一、简单选择排序
1.1 基本思想
在待排序数组中选出最小的(或最大)的与第一个位置的数据交换 然后在剩下的待排序数组中找出最小(或最大)的与第二个位置的数据交换,以此类推,直到第 n − 1 n-1 n−1个元素。
简单选择排序可以说是冒泡排序的一种改版,它不再两两比较出较小数就进行交换,而是每次遍历比较当前数的后面所有数,最后再把最小的数和当前数进行交换。
1.2 图示
举例:下面是待排序的元素
黄色为排好序的元素,紫色的表示正在操作的元素
1.3 简单选择排序代码
void SelectSort(ElemType A[], int n) {
for(int i = 0;i < n - 1; i++) { //一共进行n-1趟
int min = i; //记录最小元素位置
for(int j = i + 1; j < n; j++) { //在A[i...n-1]中选择最小的元素
if(A[j] < A[min]) min = j; //更新最小元素位置
}
if(min != i) swap(A[i], A[min]); //封装的swap函数共移动元素三次
}
}
1.4 性能分析
1.4.1 复杂度
- 空间复杂度:仅使用常数个辅助单元, O ( 1 ) O(1) O(1)
- 时间复杂度:为 O ( n 2 ) O(n^2) O(n2)
1.4.2 稳定性
- 在第 i i i趟找到最小元素后,和第 i i i个元素交换,可能会导致第 i i i个元素与其含有相同关键字元素的相对位置发生改变,因此简单选择排序是一种不稳定的排序算法。
二、堆排序
2.1 堆
2.1.1 堆的概念
- 堆是一棵完全二叉树
- 堆中每个节点的值都必须大于等于(大顶堆)或小于等于(小顶堆)其左右节点的值。
2.1.2 堆的实现
用数组存储完全二叉树,下标为
i
i
i的结点左子节点下标为
2
i
2i
2i,右子节点下标为
2
i
+
1
2i+1
2i+1
2.1.3 堆的核心操作
- 堆的插入和删除时间复杂度都为 O ( l o g n ) O(logn) O(logn)
(1) 往堆中插入一个元素
插入元素后可能导致堆不满足堆的特性,需要对其进行调整让其重新满足堆的特性(堆化),即顺着结点所在路径,向上或向下对比,然后变换。
例如:插入元素22,此时破坏了堆的特性,因此需要进行调整。
调整之后,得到的堆如下图所示:
(2) 从堆中删除一个元素:把最后一个结点放到堆顶,然后利用相同的父子结点对比方法,对于不满足父子结点大小关系的,互换两个结点,并且重复进行这个过程,直到满足堆的特性为止。
举例:删除结点33
首先将最后一个结点放到堆顶,替换掉该元素
然后根据堆的特性对结构进行调整:
最终得到的结果:
2.2 堆排序基本思想
首先将存放在 L [ 1... n ] L[1...n] L[1...n]中的 n n n个元素建成初始堆,由于堆本身的特点(以大根堆为例),堆顶元素就是最大值。输出堆顶元素之后,通常将堆底元素送入堆顶,此时根节点已不满足大根堆的性质,堆被破坏,将堆顶元素向下调整使其保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩下一个元素位置。
- 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
- 依次将根节点与待排序序列的最后一个元素交换
- 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列
2.3 堆排序的代码
void BuildMaxHeap(ElemType A[], int len) {
for(int i = len / 2; i > 0; i --) {
HeapAdjust(A, i, len);
}
}
void HeapAdjust(ElemType A[], int k, int len) {
A[0] = A[k]; //暂存根节点
for(int i = 2 * k; i <= len; i *= 2) { //沿key较大的子节点向下筛选
if(i < len && A[i] < A[i + 1]) { //i < len保证有右孩子,A[i] < A[i + 1]比较左右孩子的大小
i++;
}
if(A[0] >= A[i]) break; //根节点比左右孩子更大,筛选结束
else {
A[k] = A[i]; //将A[i]调整到双亲结点上
k = i; //修改k值,以便向下筛选
}
}
A[k] = A[0]; //被筛选结点的值放入最终位置
}
void HeapSort(ElemType A[], int n) {
BuildMaxHeap(A, n);
for(int i = n; i > 1; i--) { //n-1趟的交换和建堆过程
swap(A[i], A[1]); //输出堆顶元素
HeapAdjust(A, 1, i - 1);
}
}
2.4 性能分析
2.4.1 复杂度
- 空间复杂度:仅使用常数个辅助单元, O ( 1 ) O(1) O(1)
- 时间复杂度:建堆时间为 O ( n ) O(n) O(n),之后有 n − 1 n-1 n−1次向下调整操作,每次调整的时间复杂度为为 O ( h ) O(h) O(h),所以在最好、最坏和平均情况下,堆排序的时间复杂度都为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
2.4.2 稳定性
- 进行筛选时,有可能把后面相同关键字的元素调整到前面,因此堆排序是一种不稳定的排序算法。
2.4.3 适用场景
- 适合关键字较多的情况。