选择排序:
void Selection_Sort( ElementType a[], int n )
{
int i, j;
for( i = 0; i < n - 1; i++ ){ //n个元素共进行n-1趟选择排序
int min = i; //假设当前元素是未排序序列中最小的元素
for( j = i + 1; j < n; j++ ){ //从当前元素的下一个一直到最后一个元素中查找比当前选中元素小的元素中的最小值
if( a[j] < a[min] )
min = j; //更新下标
}
if( min != i ){ //如果后面存在比当前元素小的元素的最小值,就交换当前元素和找到的元素
int tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
}
空间效率:借助常数个辅助单元,空间复杂度为O(n)。
时间效率:
(1)比较过程:选择排序中比较过程与数据序列的初始状态无关,始终是n( n - 1) / 2次;
(2)移动过程:最好情况下,移动0次(顺序),最坏情况下,移动3( n - 1 )次。
(3)平均时间复杂度O(n^2)。
稳定性:不稳定。
堆:完全二叉树中任一非叶节点均大于(或小于)它的孩子结点
(1)大根堆(大顶堆):堆中非叶节点均大于孩子结点。
(2)小根堆(小顶堆):堆中非叶节点均小于孩子结点。
堆排序:(大根堆为例)
(1)第一步:创建堆:
void Bulid_Max_Heap( ElementType a[], int n )
{
for( int i = n / 2 - 1; i >= 0; i-- ) //对每个非叶节点做调整
Heap_Adjust( a, i, n - 1 ); //二叉树中所有的叶子结点都满足堆的定义,所以从i到n - 1中只有i可能不满足堆的要求
}
创建堆也是通过不断的调整来创建堆的,先把所有的元素看成一个完全二叉树,然后从最后一个非叶节点开始,不断进行调整,直到根结点调整完为止,就创建出了一个大根堆。
调整部分:
void Heap_Adjust( ElementType a[], int s, int e )
{
ElementType tmp = a[s]; //把当前可能不满足堆要求的元素保存在临时空间中
for( int i = 2 * s + 1; i <= e; i = 2 * i + 1 ){ //从待调整结点的左孩子结点开始,向下筛选时依然先从左结点开始
if( i < e && a[i] < a[ i + 1 ] ) //如果左孩子结点比右孩子结点小
i++; //就让下标指向右孩子结点(这一步就是挑选左右孩子中的较大者)
if( tmp >= a[i] ) //把挑选出的较大者和待调整结点作比较,如果待调整结点不比孩子结点中的较大者小
break; //说明此时待调整节点满足堆的定义,就跳出循环
a[s] = a[i]; //否则就把较大的值放在待调整结点的位置上,此时可能破坏左右子树的堆的特性
s = i; //所以修改s的值,把较大值的位置赋成s,方便继续向下筛选
}
a[s] = tmp; //筛选完毕后把待筛选结点的值放在最终的位置
}
时间复杂度:把一个无序数组建成一个大顶堆的时间复杂度为O(n)。
(2)堆排序:
void Heap_Sort( ElementType a[], int n )
{
Build_Max_Heap( a, n ); //创建大根堆
for( int i = n - 1; i > 0; i-- ){ //从最后一个结点向上调整
ElementType tmp = a[i]; //交换根结点和当前无序序列的最后一个结点
a[i] = a[0];
a[0] = tmp;
Heap_Adjust( a, 0, i - 1 ); //然后从根结点开始调整无序序列这部分成为一个大顶堆
}
}
空间效率:仅借助常数个辅助单元,空间复杂度为O(1)。
时间效率:建堆时间复杂度为O(n),之后有n - 1次向小调整操作,每一次平均时间复杂度为O(logn),所以堆排序时间复杂度为O(nlogn)。
稳定性:不稳定(关于稳定性的记忆可以用一个三个元素的序列{ 1, 2, 2 }做一个尝试,很容易得到结果)。