内部排序-选择排序
写在前面
【说明】以下代码实现最终均为递增序列,即从小到大排序。
1.头文件及类型定义
#include<stdio.h>
#define ElemType int
2.函数声明
/*函数声明*/
void swap(int& a, int& b); //1-1.交换
void SelectSort(ElemType A[], int n); //1-2.简单选择排序
void PrintA(ElemType A[], int len); //1-3.输出测试
void HeadAdjust(ElemType A[], int k, int len); //2-1.堆调整(以大根堆为例)
void BuildMaxHeap(ElemType A[], int len); //2-2.建立大根堆
void HeapSort(ElemType A[], int len); //2-3.堆排序
void PrintB(ElemType B[], int len); //2-4.输出测试
3.基本操作
3.1 简单选择排序
3.1.1 交换
//1-1.交换
void swap(int& a, int& b) { //涉及两个元素实际交换,要带引用
int temp = a;
a = b;
b = temp;
}
3.1.2 简单选择主过程
//1-2.简单选择排序
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函数共移动元素3次
}
}
3.1.3 输出测试
//1-3.输出测试
void PrintA(ElemType A[], int len) {
for (int i = 0; i < len; i++)
printf("%d\t", A[i]);
printf("%\n");
}
3.2 堆排序
3.2.1 堆调整
//2-1.堆调整(以大根堆为例)
void HeadAdjust(ElemType A[], int k, int len) {
//函数HeadAdjust将元素k为根的子树进行调整
A[0] = A[k]; //A[0]暂存子树的根结点
for (int i = 2 * k; i <= len; i *= 2) { //沿key较大的子结点向下筛选
if (i < len && A[i] < A[i + 1]) //子树根结点的左右孩子
i++; //取key较大的孩子的下标
if (A[0] >= A[i])
break; //若子树的根结点大于左右孩子,不必调整
else { //否则
A[k] = A[i]; //A[i]提到子树的根结点
k = i; //修改k值,继续向下筛选
}
}
A[k] = A[0]; //被筛选结点的值放入最终位置
}
3.2.2 建立大根堆
//2-2.建立大根堆
void BuildMaxHeap(ElemType A[], int len) {
for (int i = len / 2; i > 0; i--) //从i=[n/2]~1,反复调整堆
HeadAdjust(A, i, len);
}
3.2.3 堆排主过程
//2-3.堆排序
void HeapSort(ElemType A[], int len) {
BuildMaxHeap(A, len); //建立大根堆
for (int i = len; i > 1; i--) { //n-1趟的交换和调整过程
swap(A[i], A[1]); //输出堆顶元素(和堆底元素交换)
HeadAdjust(A, 1, i - 1); //调整,把剩余的i-1个元素整理成堆
}
}
3.2.4 输出测试
//2-4.输出测试
void PrintB(ElemType B[], int len) {
for (int i = 1; i < len + 1; i++)
printf("%d\t", B[i]);
printf("%\n");
}
4.main函数
int main() {
/*1、简单选择排序*/
ElemType A[] = { 49,38,65,97,76,13,27,49 };
SelectSort(A, 8);
printf("<————————————简单选择排序————————————>\n");
PrintA(A, 8);
/*2、堆排序*/
ElemType B[] = { -1,53,17,78,9,45,65,87,32 };
//2-1.建立大根堆
BuildMaxHeap(B, 8);
printf("<————————————建立大根堆————————————>\n");
PrintB(B, 8);
//2-2.堆排序
HeapSort(B, 8);
printf("<————————————堆排序————————————>\n");
PrintB(B, 8);
return 0;
}
5.小结
一、选择排序的概念
- 每一趟在待排元素中选取关键字最小(或最大)的元素加入有序序列,共需要n-1趟。
二、关于两种选择排序的性能分析
- 简单选择排序
空间复杂度:O(1)
时间复杂度:O(n2)
稳定性:不稳定
适用性:适用于顺序存储和链式存储的线性表 - 堆排序
空间复杂度:O(1)
时间复杂度:O(nlog2n)
稳定性:不稳定
适用:适用于顺序存储的线性表,链表也可以,但不方便
三、其他说明
- 简单选择排序较为简单,重点掌握堆排序,并且手写代码。
- 选择排序的重要特点
(1) 对简单选择排序和堆排序来说,每经过一趟选择排序则必有一个元素会放在其最终的位置上。
此特点可作为判断是否进行了简单选择排序或堆排序以及进行了几趟的依据
(2) 对快速排序来说
①基于大根堆的堆排序,得到的是递增序列
①基于小根堆的堆排序,得到的是递减序列 - 堆排序中涉及到的关于二叉树的知识
堆排序一般通过顺序存储的线性表来实现,可以将一维数组视为一棵完全二叉树,对于顺序存储的完全二叉树来说,有下面结论:
(1) n个结点的完全二叉树树高h=⌊log2n⌋+1
(2) 位序为i的结点,左孩子位序为2i,右孩子位序为2i+1,双亲结点为⌊i/2⌋
(3) 若 i ≤ ⌊n/2⌋,则结点i为分支结点,否则为叶子结点
说明:
①树高用来计算时间复杂度
②左右孩子用在堆调整的判断
③堆调整是对分支结点的调整
四、关于堆的插入删除和关键字的比较次数
- 插入
① 先将新元素放到表尾(堆底)
② 根据大小根堆的要求,新元素不断上升,直到无法继续上升为止 - 删除
① 被删除元素用表尾(堆底)元素代替
② 根据大小根堆的要求,替代元素不断下坠,直到无法继续下坠为止 - 关键字的比较次数(常考)
① 上升:只需比较一次关键字
② 下降:若有左右孩子,比较两次;若只有左孩子,比较一次(在完全二叉树中不可能只有右孩子没有左孩子)