选择排序 总结
前言
1、选择排序(Selection Sort)的基本思想
选择排序的基本思想:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。
常用的选择排序方法有直接选择排序和堆排序。
一、直接选择排序
1、直接选择排序的基本思想
n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果。
第1趟从R[0]~R[n-1]中选取最小值,与R[0]交换;
第2趟从R[1]~R[n-1]中选取最小值,与R[1]交换;
第i趟从R[i-1]~R[n-1]中选取最小值,与R[i-1]交换;
直接选择排序和直接插入排序类似,都将数据分为有序区和无序区,所不同的是直接插入排序是将无序区的第一个元素直接插入到有序区以形成一个更大的有序区,而直接选择排序是从无序区选择一个最小的元素直接放到有序区的最后。
2、直接选择排序算法的核心
- 确定待排序的数据R[0…n]
- 确定需比较的数据R[i…n]
- 每次找出最小值,进行交换
3、直接选择排序算法的示例
给定一组数据[8 3 2 5]
第一次交换 8<->2
2 3 8 5
第二次交换 3<->3
2 3 8 5
第三次交换 8<->5
2 3 5 8
4、直接选择排序算法实现
void SelectSort(int *arr, int len)
{
int i,j; // 为循环做准
int iMin; // 存储每次最小值
int temp; // 作为临时存储值
for (i=0; i<len-1; i++) // 进行len-1趟比较即可
{
iMin = i; // 存储每次最小值
for (j=i+1; j<len; j++) // 第i次需要与之比较的数据
{
if (arr[iMin]>arr[j])
{
iMin = j; // 记录最小值的位置
}
}
temp = arr[i]; // 交换
arr[i] = arr[iMin];
arr[iMin] = temp;
}
}
5、直接选择排序算法的性能分析
直接选择排序是一个就地排序,但是直接选择排序不是稳定的。直接选择排序的平均时间复杂度是,而它的最坏情况时间复杂度也是。
参考:http://blog.csdn.net/morewindows/article/details/6671824
二、堆排序
1、堆的概念
堆可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示,每一个结点对应数组中的一个元素。
数组与堆之间的关系
二叉堆一般分为两种:最大堆和最小堆。
堆中每个父结点的元素值都大于等于其孩子结点(如果存在),这样的堆就是一个最大堆。
结点与数组索引关系
对于给定的某个结点的下标i,可以很容易的计算出这个结点的父结点、孩子结点的下标,而且计算公式很漂亮很简约:
2、用大根堆排序的基本思想
1)先将初始文件R[1…n]建成一个大根堆,此堆为初始的无序区
2)再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1…n-1].keys<=R[n].key
3)由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1…n-1]调整为堆。
然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1…n-2].keys<=R[n-1…n].keys,同样要将R[1..n-2]调整为堆。
………
直到无序区只有一个元素为止。
2、堆排序的核心
- 建堆
- 调整堆
- 堆排序
3、堆排序算法的示例
既然是堆排序,自然需要先建立一个堆,而建堆的核心内容是调整堆,使二叉树满足堆的定义(每个结点的值都不大于其父结点的值)。调整堆的过程应该从最后一个非叶子结点开始,假设有数组A={1,3,4,5,7,2,6,8,0}。那么调整堆的过程如下图,数组下标从0开始,A[3]=5开始。分别与左孩子和右孩子比较大小,如果A[3]最大,则不用调整,否则和孩子中的值最大的那一个交换位置,在图1中是A[7]>A[3]>A[8],所以A[3]与A[7]对换,从图1.1转到图1.2。
注意:使用最小堆排序后是递减数组,要得到递增数组,可以使用最大堆。
1、堆化数组
如何对一个数据进行堆化操作。要一个个的从数组中取出数据来建立堆吗?不用,先看一个数组,如下图:
很明显,对叶子结点来说,可以认为它已经是一个合法的堆,即20,60,65,4,49都 分别是一个合法的堆。只要从A[4]=50开始向下调整堆就可以了。然后再取A[3]=30,A[2]=17,A[1]=12,A[0]=9分别做一次向下调整操作就可以了。
由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最大的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。
4、堆排序算法的实现
/**
功能:调整为最大堆
思路:
1、找到左右孩子中最大的值
2、判断是否赋给父结点
*/
void AdjustMaxHeap(int a[], int i, int n)
{
int j, temp;
temp = a[i]; // 保存第i个结点值
j = 2 * i + 1; // 第i个结点的左孩子
while (j < n)
{
if (j + 1 < n && a[j + 1] > a[j]) // 在左右孩子中找最大的
j++; // 如果右孩子大,j就变成右孩子序号
if (a[j] <= temp) // 如果左右孩子的值都不大于父结点,就终止
break;
a[i] = a[j]; // 把较大的子结点往上移动,替换它的父结点
i = j; // 为下次循环做准备
j = 2 * i + 1;
}
a[i] = temp; // 保存父结点的值,此时i是j的值,表示孩子的值
}
/**
功能:建立初始堆(最大堆)
思路:
1、要想将初始文件R[1..n]调整为一个大根堆,就必须将它
所对应的完全二叉树中以每一结点为根的子树都调整为堆。
2、显然只有一个结点的树是堆,而在完全二叉树中,所有序号i>[n/2]([]向下取整)的
结点都是叶子,因此以这些结点为根的子树均已是堆。
3、我们只需依次将以序号为[n/2],[n/2]-1,...1的结点作为根的子树都调整为堆即可。
4、由于c语言中数组下标从0开始,因此,第3点的序号改为[n/2]-1,[n/2]-2,...0。
*/
void MakeMaxHeap(int a[], int n)
{
for (int i = n / 2 - 1; i >= 0; i--) // 根结点序号
AdjustMaxHeap(a, i, n); // 为每个根结点调整,保存最大堆性质
}
/**
1、建立初始堆
2、每一趟最后一个数都与a[0]交换,并重新调整堆,
使其保持堆的特性。
*/
void MaxHeapSort(int a[], int n)
{
MakeMaxHeap(a,n); // 建立初始堆
for (int i = n - 1; i >= 1; i--) // 对当前无序区a[1..i]进行堆排序,共做n-1趟
{
int temp = a[i]; // 将堆顶和堆中最后一个记录交换
a[i] = a[0];
a[0] = temp;
AdjustMaxHeap(a, 0, i); // 将a[0..i]重新调整为最大堆,仅有a[0]可能违反堆性质
}
}
5、堆排序算法的性能分析
堆排序是不稳定,但是它的平均时间复杂度和最差情况时间复杂度都是O(nlogn)。