Java常用算法 - 选择排序

182 阅读4分钟

选择排序

简单选择排序

原理

一趟简单选择排序的操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换。

代码实现

public static void quickSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
        // 筛选出最小值位置
        int k = i;
        for (int j = k + 1; j < arr.length; j++) {
            if (arr[j] < arr[k]) {
                k = j;
            }
        }
        // 交换元素
        if (i != k) {
            int temp = arr[i];
            arr[i] = arr[k];
            arr[k] = temp;
        }
    }
    for (int i : arr) {
        System.out.print(i + " ");
    }

}
public static void main(String[] args) {
    int[] arr = new int[]{67, 2, 23, 12, 34};
    quickSort(arr);
}
/*
结果打印: 
2 12 23 34 67
*/

树型选择排序(锦标赛排序)

从前文对简单选择排序可以看出,每次我们需要筛选出最小元素,都需要对剩下的元素进行比较,由于对比较结果没有进行保存,其中存在很多重复的比较,导致效率不高,那我们该如何进行优化呢?

于是乎就出现了树型排序,基本思想与体育竞赛一样,首先取n个元素, 进行两两比较,得到n/2个胜者并保存比较结果,然后对这些胜者再进行比较,如此重复,最终抉择出最小的元素,因此树型选择排序也被成为锦标赛排序。

原理解析

假设当前存在数组{67,2,23,12,34,27,43},我们需要抉择出最小的值,首先我们需要构建1颗满二叉树,数组中的元素依次分布在叶子节点上,如下图所示:

TreeQuickSort1.jpg

接下来,像竞技赛一样,我们需要让相邻节点进行两两比较,把较小元素晋升为父级节点,如此重复,则得到最小元素。

TreeQuickSort2.jpg

此时,我们直接获取树的根节点,就可以获取到最小元素,那我们该如果取第2小元素呢?

首先,我们可以将最小元素的叶子节点值设置为Max,并对该节点经过的路径进行重新比较和刷新,如下图所示:

TreeQuickSort3.jpg

此时,树的根节点则是数组中第2小的元素。如此重复操作,便可以完成数组的升序排序。

由上述我们也不难看出,相较于简单选择排序,此种方式可以节省很多不必要的重复比较

代码实现

经过上述的图解,相信大家已经对该排序方式有一定的理解,接下来就趁热打铁,一步步通过代码实现该排序。

1. 结构定义

首先我们需要定义树节点的结构,这里有个关键点:除获取最小元素外,后续胜者的决出,我们都只是对经过路径的节点进行重新比较和刷新,为了记录该路径,我们定义了1个变量,用来存储胜者节点的索引。

private class Node {

    int data; // 数据
    int id;  // 胜者索引

    Node(int _data, int _id) {
        data = _data;
        id = _id;
    }
}

根据该设定,重新绘制下树,如下图所示,方框中的数值则为胜者索引。

TreeQuickSort4.jpg

这里大家可能会好奇,上述图,为啥叶子节点索引数值从7开始,而不是0?

细心一点的同学会发现,假设我们定义1个数组作为树,从左往右,从上到下,依次来存储这些节点,最小元素2的节点刚好位于数组下标为8的位置,此时要对该叶子节点的值设置为Max,就很简单了。其次,通过该值,结合满二叉树的性质,我们也可以直接计算出兄弟节点、父亲节点在数组中的位置,这样就方便我们对经过路径的节点进行重新比较和刷新。

2. 初始化树

由于需要满足满二叉树的性质,因此我们需要先计算出叶子节点的数量。

// 叶子节点的数量
private int getLeafNodes(int arrLength) {
    int leafNodes = 1;
    while (leafNodes < arrLength) {
        leafNodes = leafNodes << 1;
    }
    return leafNodes;
}

接下来我们就可以初始化树,该初始化过程分为2个步骤:

  1. 将数组的值依次填充至叶子节点上
  2. 自下而上,两两元素进行比较,将较小数值晋升为父亲节点
// 初始化树
private Node[] initTree(int[] arr) {

    int leafNodeSize = getLeafNodes(arr.length);

    // 树节点数量
    int treeSize = 2 * leafNodeSize - 1;

    // 锦标赛树
    Node[] tree = new Node[treeSize];

    // 将数组的值填充至叶子节点上, 若数组值不够,则使用最大值进行填充
    for (int i = leafNodeSize - 1; i < treeSize; i++) {
        int idx = i - (leafNodeSize - 1);
        if (idx < arr.length) {
            tree[i] = new Node(arr[idx], i);
        } else {
            tree[i] = new Node(Integer.MAX_VALUE, -1);
        }
    }

    // 自下而上选出数值小的节点,晋升为父亲节点
    for (int i = leafNodeSize - 2; i >= 0; i--) {
        Node leftNode = tree[i * 2 + 1];
        Node rightNode = tree[i * 2 + 2];
        if (leftNode.data < rightNode.data) {
            tree[i] = leftNode;
        } else {
            tree[i] = rightNode;
        }
    }

    return tree;
}
3. 根节点调整

取出树的根节点(即最小元素),需要将对应的叶子节点值设置为Max,同时对该节点经过路径进行重新比较和刷新,简而言之就是重新抉择出最小元素。

private int[] sort(int[] arr) {
    Node[] tree = initTree(arr);

    for (int i = 0; i < arr.length; i++) {
        Node node = tree[0];
        arr[i] = node.data;
        tree[node.id].data = Integer.MAX_VALUE;
        
        adjust(tree, node.id);
    }

    return arr;
}
private void adjust(Node[] tree, int id) {
    while (id != 0) {
        if (id % 2 == 1) { // 奇数,兄弟节点: id+1
            int parentId = (id - 1) / 2;
            if (tree[id].data < tree[id + 1].data) {
                tree[parentId] = tree[id];
            } else {
                tree[parentId] = tree[id + 1];
            }
            id = parentId;
        } else { // 偶数,兄弟节点: id-1
            int parentId = id / 2 - 1;
            if (tree[id - 1].data < tree[id].data) {
                tree[parentId] = tree[id - 1];
            } else {
                tree[parentId] = tree[id];
            }
            id = parentId;
        }
    }
}

至此,我们就完成了锦标赛排序算法的代码实现,每次只需要取出树的根节点,然后再抉择出新的根节点,这样就能完成对数组的排序。

参考文献

Java八大常用算法

锦标赛排序