选择类排序的基本思想是每一趟在n-i+1(i=1,2,...,n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。本篇在介绍简单选择排序的基础上,给出了其改进算法--树形选择排序和堆排序。
算法思想:第一趟简单选择排序时,从第一个记录开始,通过N-1次关键字的比较,从n个记录中选出关键字最小的记录,并和第一个记录进行交换。第二趟简单选择排序时,从第二个记录开始,通过n-2次关键字的比较,从n-1个记录中选出关键字最小的记录,并和第二个记录表进行交换。......第i趟简单选择排序时,从第i个记录开始,通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录进行交换。如此反复,经过n-1趟简单选择排序,将n-1个记录排到位,剩下一个最小记录直接在最后,所以共需进行n-1趟简单选择排序。
private static void SelectSort(int[] a) {
int k=0,temp;
for (int i = 0; i < a.length-1; i++) {
k=i;
for (int j = i+1; j < a.length; j++) {
if(a[j]
算法分析:i=1时需要进行n-1此比较,i=2时需进行n-2次比较,依次总需要n-1+n-2+..+1=n(n-1)/2,即进行比较操作的时间复杂度为O(n^2),空间复杂度为O(1),不稳定
算法思想:树形选择排序也称作锦标赛排序。基本思想是先把待排序的n个记录的关键字两两比较,取出较小者,然后再在[n/2]个较小者中,采用同样的方法进行比较,选出每两个中的较小者,如此反复,直至选出最小关键字记录为止。这个过程可以用一棵满二叉树来表示,不满时用关键字为∞的节点填满,选择的最小关键字记录就是这棵树的根节点。在输出最小关键字之后,为选出次小关键字,将最小关键字记录所对应的叶子结点的关键字值置为∞,然后从该叶节点开始和其兄弟结点的关键字比较,修改从该叶结点到根结点路径上各结点的值,则根结点的值即为次小关键字。重复进行上述过程,直到所有的记录全部输出为止,如图所示给出了选取最小及次小关键字的过程。
(a)选出最小关键字 (b)选出次小关键字
private static void treeSelectSort(int[] a){
int len = a.length;
int treeSize = 2 * len - 1; //完全二叉树的节点数
int low = 0;
Object[] tree = new Object[treeSize]; //临时的树存储空间
//由后向前填充此树,索引从0开始
for(int i = len-1,j=0 ;i >= 0; --i,j++){
//填充叶子节点
tree[treeSize-1-j] = a[i];
}
for(int i = treeSize-1;i>0;i-=2){
//填充非终端节点
tree[(i-1)/2] = ((Comparable)tree[i-1]).compareTo(tree[i]) < 0 ? tree[i-1]:tree[i];
} //不断移走最小节点
int minIndex;
while(low < len){
Object min = tree[0]; //最小值
a[low++] = (int) min;
minIndex = treeSize-1;
//找到最小值的索引
while(((Comparable)tree[minIndex]).compareTo(min)!=0){
minIndex--;
}
tree[minIndex] = Integer.MAX_VALUE; //设置一个最大值标志
//找到其兄弟节点
while(minIndex > 0){ //如果其还有父节点
if(minIndex % 2 == 0){ //如果是右节点
tree[(minIndex-1)/2] = ((Comparable)tree[minIndex-1]).compareTo(tree[minIndex]) < 0 ? tree[minIndex-1]:tree[minIndex];
minIndex = (minIndex-1)/2;
}else{ //如果是左节点
tree[minIndex/2] = ((Comparable)tree[minIndex]).compareTo(tree[minIndex+1]) < 0 ? tree[minIndex]:tree[minIndex+1];
minIndex = minIndex/2;
}
}
}
}
算法分析:假设排序所用满二叉树的深度为h,在树形选择排序中,除了最小关键字,被选出的其他较小关键字都是走了一条有叶子结点到根结点的比较的过程且均需比较h-1次。可证明含有n个叶子结点的完全二叉树的深度h=log2n+1,因此在树形选择排序中,每选出一个较小关键字需要进行log2n次比较,所以其时间复杂度O(nlog2n) 空间复杂度O(n) 稳定
威洛母斯对树形选择排序进一步的改进方法,堆排序,用以弥补树形选择排序占用空间多的缺憾。采用堆排序时,只需要一个记录大小的辅助空间。堆排序是在排序过程中,将向量中存储的数据看成一棵完全二叉树,利用完全二叉树中双亲结点和孩子结点之间的内在关系来选择关键字最小的记录,即待排序记录仍采用向量数组方式存储,并非采用树的存储结构,而仅仅是采用完全二叉树的顺序结构的特征进行分析而已。
算法思想:在待排序的记录的关键字存放在数组a[1,...,n]之中,将a看成一棵完全二叉树的顺序表示,每个结点表示一个记录,第一个记录a[0]作为二叉树的根,以下各记录a[1]~a[n-1]依次逐层从左到右顺序排列,任意结点a[i]的左孩子是a[2i],右孩子是a[2i+1],双亲是a[i/2]。对这棵完全二叉树进行调整建堆。
堆定义:称各结点的关键字值满足条件:a[i]>=a[2i]切a[i]>a[2i+1]的完全二叉树为大根堆。反之就是根结点小于孩子结点对应的堆称为小根堆。例如
98 14
77 35 48 35
62 55 14 35 62 55 98 35
48 77
(a).大根堆 (b).小根堆
实现原理:
1.首先将序列看成一个树形结构,
构建堆的过程:找到最后一个非终端节点n/2,与它的左右子节点比较,
比较结果使此父节点为这三个节点的最小值。再找n/2-1这个节点,
与其左右子节点比较,得到最小值,以此类推....,最后根节点即为最小值
49 38 65 97 76 13 27 49
初始树为:
49
38 65
97 76 13 27
49
构造堆后的树为
13
38 27
49 76 65 49
97
交换数据的顺序为:97<——>49, 13<--->65,38不用换,49<-->13,13<-->27
2.输出堆顶元素并调整建新堆的过程
输出堆顶最小值后,假设以最后一个值替代之,由于其左右子树的堆结构并没有被破坏
只需要自上而下进行调整。比如把上图的13输出后以97替代,然后可以把97与27交换,
然后97又与49交换,此时最小值为根元素27,输出27后以又用最后一个值替换根元素,
以此类推,则最终得到有序序列
private static void HeapSort(int[] a) {
crt_heap(a);
int temp;
for (int i = a.length-1; i >=1; i--) {
temp=a[0];
a[0]=a[i];
a[i]=temp;
sift(a,0,i-1);//使a[0,..,i-1]变成堆
}
}
//重建堆
private static void sift(int[] a, int k, int m) {
int t,x,i,j;
t=a[k];//暂存根记录a[k]
x=a[k];
i=k;
j=2*i;
boolean finished=false;
while(j<=m&&!finished){//若存在右子树,且右子树根的关键字大,则沿右分支筛选
if(j=a[j]) finished=true;//筛选完毕
else{//继续筛选
a[i]=a[j];
i=j;
j=2*i;
}
}
a[i]=t;//a[k]填入到恰当的位置
}
//建立初堆
private static void crt_heap(int[] a) {
int n=a.length-1;
for(int i=n/2;i>=0;--i){//从第[n/2]个记录开始进行筛选建堆
sift(a,i,n);
}
}
算法分析:在每次循环时,左、右子树先比较一次,然后左、右子树较大者再与被筛元素比较一次,所以对深度为H的堆,筛选算法中的关键比较次数至多为2(h-1)次。时间复杂度O(nlog2n) 空间复杂度O(1) 不稳定