漫画:什么是 “锦标赛排序” ?

简介: 如图中所示,我们把原本的冠军选手5排除掉,在四分之一决赛和他同一组的选手6就自然获得了直接晋级。接下来的半决赛,选手7打败选手6晋级;在总决赛,选手7打败选手3晋级,成为了新的冠军。因此我们可以判断出,选手7是总体上的亚军。

640.png640.png640.png640.png640.png640.jpg640.png640.png640.png640.png

 

 

————————————

640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png640.png

 

如图中所示,我们把原本的冠军选手5排除掉,在四分之一决赛和他同一组的选手6就自然获得了直接晋级。

接下来的半决赛,选手7打败选手6晋级;在总决赛,选手7打败选手3晋级,成为了新的冠军。

因此我们可以判断出,选手7是总体上的亚军。

640.png640.png640.png640.png640.png640.png640.png 

假如给定如下数组,要求从小到大进行升序排列:

 

640.png

 

第一步,我们根据数组建立一颗满二叉树,用于进行“锦标赛式”的多层次比较。数组元素位于二叉树的叶子结点,元素数量不足时,用空结点补齐。

 

640.png

 

第二步,像锦标赛那样,让相邻结点进行两两比较,把数值较小的结点“晋升“到父结点。640.png

如此一来,树的根结点一定是值最小的结点,把它复制到原数组的最左侧:

 640.png

 

第三步,删除原本的最小结点,也就是值为1的结点。然后针对该结点所在路径,进行重新比较和刷新。

 

如此一来,树的根结点换成了第二小的结点,把它复制到原数组的下一个位置:

640.png 

第四步,删除原本第二小的结点,也就是值为2的结点。然后针对该结点所在路径,进行重新比较和刷新。

如此一来,树的根结点换成了第三小的结点,把它复制到原数组的下一个位置:

 

 640.png

像这样不断删除剩余的最小结点,局部刷新二叉树,最终完成了数组的升序排列:



640.png640.png640.png


public class TournamentSort {
    public static void tournamentSort(int[] array) {
        Node[] tree = buildTree(array);
        for(int i=0; i<array.length; i++){
            array[i] = tree[0].data;
            if(i<array.length-1) {
                //当前最小元素所对应的叶子结点置空
                tree[tree[0].index] = null;
                //重新选举最小元素
                updateTree(tree[0].index, tree);
            }
        }
    }
    //排序前为数组构建二叉树,并选举最小值到树的根结点
    public static Node[] buildTree(int[] array) {
        //计算叶子层的结点数
        int leafSize = nearestPowerOfTwo(array.length);
        //计算二叉树的总结点数
        int treeSize = leafSize * 2 - 1;
        Node[] tree = new Node[treeSize];
        //填充叶子结点
        for(int i=0; i<array.length; i++){
            tree[i+leafSize-1] = new Node(i+leafSize-1, array[i]);
        }
        //自下而上填充非叶子结点
        int levelSize = leafSize;
        int lastIndex = treeSize-1;
        while(levelSize > 1){
            for(int i=0; i<levelSize; i+=2){
                Node right = tree[lastIndex-i];
                Node left = tree[lastIndex-i-1];
                Node parent = left;
                if(left != null && right != null) {
                    parent = left.data<right.data?left:right;
                }else if (left == null){
                    parent = right;
                }
                if(parent != null){
                    int parentIndex = (lastIndex-i-1)/2;
                    tree[parentIndex] = new Node(parent.index, parent.data);
                }
            }
            lastIndex -= levelSize;
            levelSize = levelSize/2;
        }
        return tree;
    }
    //重新选举最小元素
    public static void updateTree(int index, Node[] tree){
        while(index != 0){
            Node node = tree[index];
            Node sibling = null;
            if((index&1) == 1){
                //index为奇数,该结点是左孩子
                sibling = tree[index+1];
            }else {
                //index为偶数,该结点是右孩子
                sibling = tree[index-1];
            }
            Node parent = node;
            int parentIndex = (index-1)/2;
            if(node != null && sibling != null) {
                parent = node.data<sibling.data?node:sibling;
            }else if (node == null){
                parent = sibling;
            }
            tree[parentIndex] = parent==null ? null : new Node(parent.index, parent.data);
            index = parentIndex;
        }
    }
    //获得仅大于number的完全平方数
    public static int nearestPowerOfTwo(int number) {
        int square = 1;
        while(square < number){
            square = square<<1;
        }
        return square;
    }
    //结点类
    private static class Node {
        int data;
        int index;
        Node(int index, int data){
            this.index = index;
            this.data = data;
        }
    }
    public static void main(String[] args) {
        int[] array = {9,3,7,1,5,2,8,10,11,19,4};
        tournamentSort(array);
        System.out.println(Arrays.toString(array));
    }
}

 

在这段代码中,二叉树的存储方式并非传统的链式存储,而是采用数组进行存储。因此,该二叉树的每一个父结点下标,都可以由(孩子下标-1/2 来获得。

640.png640.png

网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|
网络异常,图片无法展示
|

相关文章
|
算法 JavaScript 前端开发
日拱算法:什么是“煎饼排序”?
通过“煎饼翻转”来进行排序,叫“煎饼排序”,那什么是“煎饼翻转”呢?(禁止套娃🐶)
|
算法 JavaScript 前端开发
日拱算法,水果成篮问题
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
|
算法 搜索推荐 Windows
从荷兰国旗问题出发,详解快速排序算法的原理
从荷兰国旗问题出发,详解快速排序算法的原理
121 0
088.马克思手稿中的数学题
088.马克思手稿中的数学题
72 0
|
算法 开发者
再学一道算法题:水果忍者
再学一道算法题:水果忍者
再学一道算法题:水果忍者
|
算法 搜索推荐 Shell
植物大战 希尔 排序 ——纯C
植物大战 希尔 排序 ——纯C
植物大战 希尔 排序 ——纯C
|
搜索推荐 算法
漫画:三种 “奇葩” 的排序算法
介绍三种“异想天开”的排序算法。
123 0
漫画:三种 “奇葩” 的排序算法
|
算法
漫画:什么是鸡尾酒排序?(修订版)
昨天小灰发布的漫画中存在一些勘误,所以今天重新发布一篇修订版,修正了代码当中的一些细节问题。在上一篇漫画中,小灰介绍了冒泡排序的思路和几种变化:漫画:什么是冒泡排序?那么,鸡尾酒排序又是何方神圣呢?我们这一期将会详细讲述。
112 0
漫画:什么是鸡尾酒排序?(修订版)
|
算法 搜索推荐
漫画:什么是基数排序?
数组每一个下标位置的值,代表了数列中对应整数出现的次数。 有了这个“统计结果”,排序就很简单了。直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次: 0,1,1,2,3,3,3,4,4,5,5,6,7,7,8,9,9,9,9,10 显然,这个输出的数列已经是有序的了。 这就是计数排序的朴素版本。
119 0
漫画:什么是基数排序?