【Java常用排序算法】选择排序(简单选择排序、堆排序)

选择排序

选择排序的基本思想:每一趟从待排序的元素序列中选出关键字最大(最小)的元素,按顺序放在已排序的元素序列的最后面(最前面),直到全部排完为止。
根据选出元素的方式又分为简单选择排序和堆排序。简单选择排序是通过简单的数组遍历来确定最大(最小)元素。而堆排序是利用堆这种数据结构的性质,通过堆元素的删除、调整等一系列操作选出最大(最小)元素。

简单选择排序

  • 基本思想
    第1趟,在待排序记录arr[1]~arr[n]中选出最小的记录,将它与arr[1]交换;
    第2趟,在待排序记录arr[2]~arr[n]中选出最小的记录,将它与arr[2]交换;
    以此类推,第i趟在待排序记录arr[i]~arr[n]中选出最小的记录,将它与arr[i]交换,使有序序列不断增长直到全部排序完毕。
  • 例子
    {10,6,5,3,8,9,1,4,2,7};
    第一次:[1],6,5,3,8,9,10,4,2,7——交换了1和10
    第二次:[1,2],5,3,8,9,10,4,6,7——交换了2和6
    第三次:[1,2,3],5,8,9,10,4,6,7——交换了3和5
    第四次:[1,2,3,4],8,9,10,5,6,7——交换了4和5
    第五次:[1,2,3,4,5],9,10,8,6,7——交换了5和8
    第六次:[1,2,3,4,5,6],10,8,9,7——交换了6和9
    第七次:[1,2,3,4,5,6,7],8,9,10——交换了7和10
    第八次:[1,2,3,4,5,6,7,8],9,10——没交换
    第九次:[1,2,3,4,5,6,7,8,9],10——没交换
    第十次:[1,2,3,4,5,6,7,8,9,10]——没交换
  • 算法分析
    简单选择排序的空间复杂度为O(1)。
    最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。最坏情况下,需要移动记录的次数最多为3(n-1)。然而,无论初始元素排序如何,所需要进行关键字的比较相同,当i=1时,需进行n-1次比较;当i=2时,需进行n-2次比较;依次类推,共需要进行的比较次数是(n-1)+(n-2)+…+2+1=n(n-1)/2,即进行比较操作的时间复杂度为O(n²)。
    简单选择排序是不稳定的。
  • 代码实现
package com.gray;

import java.util.Arrays;

public class SelectionSort {
    public static void main(String args[]) {
        int a[] = {
    10, 6, 5, 3, 8, 9, 1, 4, 2, 7};
        selectionSort(a);
        System.out.println("排序后:" + Arrays.toString(a));
    }

    private static void selectionSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            int k = i; // 记录最小值的位置
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[j] < arr[k]) {
                    k = j;
                }
            }
            if (k != i) {  // 交换
                int temp = arr[i];
                arr[i] = arr[k];
                arr[k] = temp;
            }
        }
    }
}

堆排序

在堆排序之前先了解一下堆。
一棵完全二叉树中,任意父结点总是大于或等于(小于或等于)任何一个子节点,我们将其称为大顶堆(小顶堆)。一棵二叉树可以使用一组地址连续的存储单元来存放二叉树的数据元素,对该二叉树中每个节点按照完全二叉树的层序编号,其编号从小到大的顺序就是节点存放在连续存储单元的先后顺序。这就是二叉树的顺序存储结构。
* 基本思想
堆排序是一种树形选择排序方式,它的特点是:在排序过程中,将待排序列看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。
具体做法(以大顶堆为例):把待排序的表的关键字存放在数组中,完全二叉树的顺序存储结构,节点arr[i]的左孩子arr[2i+1],右孩子是arr[2i+2]。
堆排序的关键是构造初始堆,这里采用的是筛选算法建堆:假如某一个节点i,它的左子树和右子树都已经成堆了,接下来要将arr[2i+1]和arr[2i+2]中的最大者与arr[i]比较,若arr[i]小,把大的换上去,换了就有可能破坏了下一级的堆。于是继续采用这种办法构造下一级的堆,直到完全二叉树节点i构成堆为止。这样大的数据“上浮”,小的就被“筛选”下去了。
这个看到这里不理解不要紧,看我的例子就能明白了。下面我们继续,排序还没完,这只是构造初始堆。
初始堆构造好后,根节点一定是最大关键字节点,将其放到数列的最后,即将根节点与最后一个叶子节点交换。由于最大元素搬走了,整个待排序的元素少了一个,剩下的不一定为堆,因此我们需要再次调用上面说的筛选算法,这样出来的根节点就是第二大的关键字节点了,将其放到数列的倒数第二的位置。。。如此类推,直到完全二叉树只剩一个根节点为止,排序结束。
* 例子
{69,65,90,37,92,6,28,54}
先把待排序的数按照完全二叉树的形式罗列好。
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第1张图片
先从第一个非叶子节点37开始
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第2张图片
54与37不满足大顶堆的要求,交换。再到下一个非叶子节点90
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第3张图片
满足大顶堆的要求,不变。再到下一个非叶子节点65
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第4张图片
65和92不满足大顶堆的要求,交换。再到下一个非叶子节点69,69是根节点,意味着这次比较就会结束本次筛选。
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第5张图片
69和92不满足大顶堆的要求,交换。余下的节点均符合大顶堆的要求,第一次构造堆完成。
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第6张图片
得到一个大顶堆,交换节点。将最后一个叶子节点与根节点交换,即92与37交换。交换完之后37到了根节点,92已经正常归位,原来要排序的元素就少一个了。
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第7张图片
像图7这样看比较直观,实际上92到了数组的最后一个位置。少了一个元素后发现新堆并不满足大顶堆的要求,重复以上步骤。
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第8张图片
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第9张图片
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第10张图片
【Java常用排序算法】选择排序(简单选择排序、堆排序)_第11张图片
(篇幅有限。。就不全部上图了)
* 算法分析
堆排序算法的空间复杂度为O(1)。由上面的例子可以看出堆排序的时间全部花在了建初始堆和反复建堆上面了。建立初始堆所花的时间不超过4n,调用筛选算法n-1次所需的时间为O(nlog2n)。
在筛选的过程中有可能将相同关键字的元素调整到前面,所以说堆排序算法是一种不稳定的排序算法。
* 代码实现

package com.gray;

import java.util.Arrays;

public class HeapSort {
     
    public static void main(String args[]) {
        int a[] = {
    69, 65, 90, 37, 92, 6, 28, 54};
        heapSort(a);
        System.out.println("排序后:" + Arrays.toString(a));
    }

    private static void heapSort(int[] arr) {
        // 建初始堆
        for (int i = arr.length  / 2; i >= 0; i--) {
            sift(arr, i, arr.length);
        }
        for (int i = arr.length-1 ; i > 0; i--) {
            int temp = arr[0]; // 交换位置
            arr[0] = arr[i];
            arr[i] = temp;
            sift(arr, 0, i); // 重新建堆 每次建堆少一个元素
        }
    }

    /**
     * 筛选算法
     *
     * @param arr
     * @param start 开始建堆的位置
     * @param size  剩下节点的容量大小
     */
    private static void sift(int[] arr, int start, int size) {
        int j = 2 * start + 1; //j是i的左孩子的位置
        int temp = arr[start];
        while (j < size) {
            if (j != size-1 && arr[j] < arr[j + 1]) {   //若右孩子大,把j指向右孩子
                j++;
            }
            if (temp < arr[j]) {
                arr[start] = arr[j]; // 将arr[j]交换到双亲节点上,并修改i,j的值,以便继续筛选
                start = j;
                j = 2 * j + 1;
            } else {
                break;
            }
        }
        arr[start] = temp; //被筛选的节点的值放入最终位置
    }
}

你可能感兴趣的:(数据结构与算法,Java常用7种排序和3大查找,java,排序算法,堆排序,选择排序)