/*
* szlTournamentSort.h
*/
#ifndef SZL_TOURNAMENT_H
#define SZL_TOURNAMENT_H
/*
* 锦标赛排序
* 时间复杂度:O(nlog(n))
* 空间复杂度:O(n)
*/
void tournament(int a[], int n);
#endif
/*
* szlTournamentSort.c
*/
#include "szlTournamentSort.h"
#include <stdlib.h>
#include <assert.h>
#include <limits.h>
/*
* 锦标赛排序
* 时间复杂度:O(lgN)
* 空间复杂度:O(n)
* 1.首先以n个元素为叶子节点建立一颗完全二叉树;(有技巧:叶子节点数量确定的完全
* 二叉树是并不一定是唯一确定的,但是如果给定叶子节点数量必须比非叶子节点数量
* 多1个而且只多1个,那么这棵二叉树就是完全确定的);
* 1.1将当前叶子节点两两比较,将其父节点设置为较大的孩子节点,以此类推;
* 1.2得到的这棵树中,树根节点就是所有n个节点中的最大值;
* 1.3将这个树根节点对应的叶子节点的值设置为最小值,沿着该节点往树根方向逐个比较;
* 并将节点之较大的拷贝给父节点;
* 1.4最后得到的树根节点即为当前叶子节点中的最大节点;
* ...
* 直到最后只剩下一个其值未被修改的叶子节点;删除节点的顺序就是已排好序的一列元素;
* 算法分析:
* 第1次建立二叉树时需要开辟大小为2*n-1的节点空间,并执行n-1次比较;
* 第2次执行了log(2n-1)次比较;
* 第3次执行了log(2n-1)次比较;
* ...
* 第n次执行了log(2n-1)次比较;
* 所以时间复杂度为O(2*n-1+(n-1)*log(2*n-1)) = O(n*log(n));
*/
void tournamentSort(int a[], int n){
/*
* 使用数组来存储一棵完全二叉树;
* 如果使用索引为0的元素(以下称为节点0)表示n个节点的完全二叉树的树根,那么;
* 1.节点i的孩子为节点2*i+1及2*i+2;
* 2.节点i的父节点为(i-1)/2;
* 3.左孩子为奇数索引的节点,右孩子和树根为偶数索引的节点;
* 4.第一个叶子节点是节点n/2;
* 5.最后一个内部节点为n/2-1;
* 6.叶子节点的数量(n-n/2)要么和内部节点数量(n/2)相等,要么比内部节点数量多1;我们
* 这里使用的是叶子数量较内部节点数量多1个完全二叉树;
*/
int f,i,j,k;
/*
* 定义结构体保存树中叶子的索引,用于稍后修改叶子的值;
*/
typedef struct _treeNode{
int value; // 树节点的值
int index; // 保存value的叶子节点在实现这棵树的数组中的索引
}TreeNode,*ptrTreeNode;
ptrTreeNode tree;
tree = (ptrTreeNode)malloc(sizeof(TreeNode)*(2*n-1));
assert(NULL!=tree);
/*
* 将数组元素拷贝到叶子中去
*/
for(i=0,j=n-1;i<n;i++,j++){
tree[j].value = a[i]; // 保存数组的值
tree[j].index = j; // 记住当前叶子节点的在数组中的索引
}
/*
* 第1轮:由最底层的最后一个叶子开始,自底向上,构造一颗树;
* 相当于是从后往前扫描元素然后作比较的;
* 亦即从这棵树的最后一个内部节点开始,内部节点逐个被赋值;
*/
for(i=2*n-2;i>=1;i=i-2){
/*
* 父节点设置为较大的元素
*/
if(tree[i].value < tree[i-1].value){
tree[(i-1)/2].value = tree[i-1].value;
tree[(i-1)/2].index = tree[i-1].index;
}
else{ // tree[i-1]<=tree[i]
tree[(i-1)/2].value = tree[i].value;
tree[(i-1)/2].index = tree[i].index;
}
} // 树根节点已经是最大值
/*
* 然后从该叶子节点开始往根节点方向回溯n-1轮;每回溯一次产生一个最大值
*/
for(i=0;i<n-1;i++){
/*
* 取出第树根元素,然后放到数组a的末尾
*/
a[n-1-i] = tree[0].value;
/*
* 记住取出的元素在树的叶子节点的索引
*/
j = tree[0].index;
/*
* 并将取出的节点对应的叶子设置为最小值
*/
tree[j].value = INT_MIN;
do{
/*
* 该节点的兄弟节点
*/
k = (j&1)?(j+1):(j-1);
/*
* 父节点
*/
f = (k-1)/2;
/*
* 设置父节点的值和索引
*/
if(tree[j].value < tree[k].value){
tree[f].value = tree[k].value;
tree[f].index = tree[k].index;
}
else{ //tree[j].value > tree[k].value
tree[f].value = tree[j].value;
tree[f].index = tree[j].index;
}
j=f; //往上一层
}while(f);
}
/*
* 最后一个元素
*/
a[0] = tree[0].value;
/*
* 释放空间
*/
if(tree){
free(tree);
}
}
关于那棵二叉树:
和其他O(n*log(n))的算法运行比较还是不错的。
另一个版本:
/*
* tournamentsort.c
* space: O(n)
* time : O(logn)
*/
#include <stdio.h>
#include <limits.h>
#define N 30
void tournamentsort (int a[], int n){
int j;
int me;
int father;
int sibiling;
int max_child;
int value[2*N-1];
int index[2*N-1];
for (j = n - 1; j < (2 * n - 1); j++){
value[j] = a[j-n+1];
index[j] = j;
}
for (j = 2 * n - 2; j >= 2; j -= 2){
max_child = value[j] > value[j-1] ? j : (j-1);
father = (j - 1) / 2;
index[father] = index[max_child]; /* bugs occurs here */
value[father] = value[max_child];
}
for (j = n-1; j > 0; j--){
a[j] = value[0];
me = index[0];
value[me] = INT_MIN;
do{
sibiling = (1 & me) ? (me + 1) : (me - 1);
father = (me - 1) / 2;
index[father] = value[sibiling] > value[me] ? index[sibiling] : index[me]; /* bugs occurs here */
value[father] = value[index[father]];
me = father;
} while (me);
}
a[0] = value[0];
}
int main (int argc, char ** argv){
int n;
int i;
int a[N];
#ifdef DEBUG1
freopen ("in.txt", "r", stdin);
#endif
scanf ("%d", &n);
for (i=0; i<n; i++)
scanf ("%d", &a[i]);
tournamentsort (a, n);
for (i=0; i<n; i++)
printf ("%d ", a[i]);
#ifdef DEBUG1
fclose (stdin);
#endif
return 0;
}