选择排序(简单选择排序,树形选择排序,堆排序)
简单选择排序
基本思想:第i趟排序经过n-i次关键字的比较在n-i+1(i=1,2,3,......n-1)个记录中选取关键字最小的记录作为序列的第i个记录
直接看代码!没啥好说的这个!
void SelectSort(SqList *L)
{
for(int i=0;i<L->length-1;i++){//总共进行n-1趟排序,便可使整个序列有序
{
int index=i;//假设第i个记录就是最小值
for(int j=i+1;j<L->length;j++)
if(L->r[j]<L->r[index])//找到比index指向记录有更小关键字的记录
index=j;//index指向带新最小关键字的记录
if(index!=i)
{
int key=L->r[index];
L->r[index]=L->r[i];
L->r[i]=key;//交换记录
}
}
return;
}
简单选择排序:
记录移动次数最小为0次,最大值为3*(n-1)次
记录比较次数:第1趟排序比较n-1次,第i趟排序比较n-i次,共有n-1趟,故共比较n(n-1)/2次
故:简单选择排序的时间复杂度为O(n^2)
树形选择排序(锦标赛排序)
树形选择排序是对简单选择排序的改进。简单选择排序的主要操作是进行关键字间的比较,因此我们通过减少比较次数而改进简单选择排序。
改进思想:在n个关键字中选出最小值,需要进行n-1次比较,然而,继续在剩余的n-1个关键字中选择次小值并非一定要进行n-2次比较,若能利用前n-1次比较所得信息,则可减少以后各趟选择排序中比较次数。
主要步骤:
- 对n个记录的叶子节点进行两两比较,从根结点得到最小关键字的记录
- 找到与根结点关键字相同的叶子节点记录,更改关键字为最大值
- 从该叶子节点开始,和其左兄弟或者右兄弟进行比较,修改从叶子结点到根路径上各结点的关键字,得到次小关键字
- 重复上述第2~3步依次选出从小到大的所有关键字
#include <limit.h>
void TreeSelectSort(SqList *L)
{
int n;//根据叶子节点个数计算整棵树的结点数
if(L->length%2==0)
n=L->length*2-1;//此时为叶子节点为L->length的满二叉树,叶子节点个数为偶数个
else
n=L->length*2;//此时为叶子节点为L->length的完全二叉树,叶子节点个数为奇数个
int *Tree=(int*)malloc((n+1)*sizeof(int);//分配大小为n+1的数组,使用Tree[1]到Tree[n]
if(!Tree) exit(-1);
for(int i=n,int j=L->length-1;i>=0;i--,j--){
if(j>=0) Tree[i]=L->r[j];
else Tree[i]=0;
}//将顺序表记录依次从Tree[n-L->length+1]到Tree[n]进行赋值,其他赋值为0
for(int k=n/2;k>0;k--){
if(2*k+1<=n)
Tree[k]=Tree[2*k]<Tree[2*k+1]?Tree[2*k]:Tree[2*k+1];
else
Tree[k]=Tree[2*k];
}//比较得到最小关键值
L->r[0]=Tree[1];
for(int i=1;i<L->length;i++){//进行L->length-1趟剩余最小关键字的选择
int j=1;
while(2*j<=n){//寻找次小关键字所在叶子节点
if(Tree[j]==Tree[2*j])
j=2*j;
else
j=2*j+1;
}
Tree[j]=MAX_INT;//最小关键字所在的叶子结点赋值为MAX_INT
while(j!=1){//选出次小关键字
j=j/2;
if(2*j+1>n||Tree[2*j]<Tree[2*j+1])//如果j没有右孩子结点或者左结点<右结点
Tree[j]=Tree[2*j];//将左结点关键值赋值Tree[j]
else
Tree[j]=Tree[2*j+1];//将右结点关键值赋值Tree[j]
}
L->r[i]=Tree[1];//得到次小关键值
}
return;
}
时间复杂度:
含有n个叶子结点的完全二叉树的深度为
则除了最小关键字的那一趟选择,每选择一个次小关键字仅需要比较次,因此时间复杂度为O(nlogn)
缺点:
- 需要的辅助存储空间比较多
- 需要和变成最大值的叶子结点进行比较
堆排序
堆的含义:完全二叉树的所有非终端结点的值均不大于(或不小于)其左、右孩子结点的值。若系列是堆,则堆顶元素必定为序列中n个元素的最小值(或最大值)。
实现堆排序面临两问题:
- 如何由一个无序序列建成一个堆
- 在输出堆顶元素之后,如何调整剩余元素成为一个新的堆
先解决第二个问题:以一维数组作为此序列的存储结构,假设为初始堆为大顶堆
- 将堆顶元素和最后一个元素互换,此时根结点的左右子树均为大顶堆
- 若根结点存在左右子树,则比较根结点和左右子树根结点的值,否则调整结束,跳到步骤1
- 若根结点值依旧均大于左右子树的根结点的值,则跳转到第1步执行
- 否则将根结点和左右子树根结点较大值互换,跳转到第2步,以被交换的子树的根结点作为第2步根结点执行
解决第一个问题:
从无序序列建堆的过程就是反复调整筛选的过程,设序列大小为n,将其看做是是一个完全二叉树,则第一个非终端结点为第个元素,从第个元素开始到第一个元素结束,分别以其作为根结点进行筛选调整。
typedef SqList HeapType;//给SqList定义别名为HeapType
//做非递减堆排序
void HeapAdjust(HeapType *H,int first,int end)//堆顶关键字小于左右子树根结点,需调整堆
{
int i=first;//i指向堆顶,从堆顶开始进行堆调整
while(2*i<=end){//当i不是叶子结点时,进入循环
int key=H->r[i];//用key临时保存堆顶关键字
int j=2*i;//用j指向最大子树根结点,假设此时左子树根结点为最大,用j指向它
if(j+1<=end&&H->r[j]<H->r[j+1]) j++;//如果右子树根结点存在,大于左子树根结点,则j++
if(Key>H->r[j]) break;//如果i大于其最大子树根结点,说明已经成堆,无需再进行调整,break
H->r[i]=H->r[j];//否则,将最大子树根结点关键字赋值堆顶元素
i=j;//最大子树结点成为子堆堆顶
H->r[i]=key;//原有堆顶关键字赋值子堆堆顶
}//以子堆堆顶i重新进行堆调整
return;
}
//SqList序列表长度为H->length+1,但实际记录为H->length个,i=0不使用,从i=1到H->length
void HeapSort(HeapType *H)
{
int n=H->length;
for(int i=H->length/2;i>0;--i)//对无序堆进行调整,从i=H->length/2开始到i=1;
HeapAdjust(H,i,H->length);
for(int i=H->length;i>1;i--)//只需进行H->length-1趟排序就OK
{
int key=H->r[1];
H->r[1]=H->r[i];
H->r[i]=key;//将堆顶的最大值记录和尾部记录交换
HeapAdjust(H,1,i-1);//从1到i-1进行堆调整
}
return;
}
时间复杂度:堆排序的时间复杂堆为O(nlogn)
空间复杂度:只需要1个记录大小供交换用的辅助空间 O(1)