3.选择排序(直接选择排序和堆排序)

选择排序

选择排序算法有两种:直接选择排序和堆排序。

直接选择排序

直接选择排序(Straight Select Sort)算法思想:第一趟从n个元素的数据序列中选出关键字最小/大的元素并放到最前/后位置,下一趟再从n-1个元素中选出最小/大的元素并放到此前/后位置,以此类推,经过n-1趟完成排序。(就是一遍一遍的选择出最小/大的,所以)

直接选择排序算法分析:

  • 直接选择排序的比较次数与数据序列数与初始排列有关,第i趟排序的比较次数是n-1;移动次数与初始排列有关,排序序列移动0次;反序排列的数据序列,每趟排序都要交换,移动3(n- 1)次。算法总比较次数C = n*(n-1)/2,约等于(n2)/2,**时间复杂度为O(n2)**。

  • 直接选择排序的空间复杂度为O(1)。

  • 直接选择排序算法是不稳定的。

一个案例:关键字序列{38,97,26,19,38,15}的直接选择排序(升序)过程如下图,其中,i表示子序列的起始位置,min表示最小元素位置,一趟扫描后将min位置元素交换到i位置,{}表示排序子序列

直接选择排序算法实现如下:

//直接选择排序(升序)
public static void selectSort(int[] keys) {
    System.out.println("直接选择排序(升序)");
    
    //外层循环控制选择第几个
    for (int i=0; i<keys.length-1; i++){                //n-1趟排序
        int min=i;
        //每趟在从keys[i]开始的子序列中寻找最小元素
        for (int j=i+1; j<keys.length; j++)            
             //if (keys[j]>keys[min])                 //(降序)
             //(升序)
            if (keys[j]<keys[min]) {
                //min记住本趟最小元素下标
                 min = j;  
            }                                                     
        System.out.print("第"+(i+1)+"趟,下标"+i+"~"+(keys.length-1)+",min="+min+",");
        //将本趟最小元素交换到前边
        if (min!=i){
              swap(keys, i, min);
        }                                       
    }
}
//交换
static void swap(int[] arr, int i, int j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

堆排序

堆的概念

堆是一种数据结构,它又可以叫二叉堆,因为它是一个完全二叉树(生成结点的顺序是从上到下,从左往右,依次生成)。

二叉堆满足二个特性:

  • 父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
  • 每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。

最大堆和最小堆:

  • 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆
  • 当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆
  • 最大/最小堆用于求最大/小值,堆序列用于多次求极值的应用问题。

堆的存储

一般都用数组来表示堆,i结点的父结点下标为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。如第0个结点左右子结点下标分别为1和2。

以上就是二叉树性质5:完全二叉树中的第i个(0<=i<=n)个结点,如果有孩子,则左孩子为第2i+1个结点,右孩子为第2i+2个结点。

堆排序

堆排序算法描述:以升序为例,

  • 将一个数据序列建立为一个最大堆,此时根节点就是整个数组中最大的数,
  • 进行选择排序,每趟将这个最大的数与数组最后一个数交换位置(这一点就是找到最大的数放在最后,和直接选择排序的思路一样,只不过这里是用二叉堆来找最大数),再将其余值调整成最大堆,将根节点交换到后面,依次重复,直到子序列长度为1,排序完成。所以,关键是建立一个最大堆。

实现思路:

  • 一步一步实现,先只考虑三个结点如何建立最大堆,

  • 然后考虑如何将整个数组建立成一个最大堆:这一步就是为了找最大值(和直接选择排序找最大值的思路可以对比一下)

  • 最后将最大的元素与最后的元素交换顺序,"除去"最后一个,剩下的继续建立最大堆,找出最大元素,再放到最后…

堆排序算法分析:

  • 堆排序时间复杂度为O(n*log2n)。
  • 堆排序空间复杂度为O(1)。
  • 堆排序算法不稳定。

堆排序算法实现:

package com.wlw.algorithm;

/**
 * 堆排序:利用最小/大堆的特性,每次选择出最小/大的值(这也是完全二叉树的一种应用,利用的节点与节点之前的顺序关系)
 * 实现思路:一步一步实现:
 * 1.先只考虑三个结点如何建立最大堆,
 * 2.然后考虑如何将整个数组建立成一个最大堆,:从下往上的把每一棵子树建立成最大堆即可,所以应该从下标为 n/2-1 的节点开始,依次-1,就可把所有的子树都弄完
 * 3.最后 将最大的元素与最后的元素交换顺序,"除去"最后一个,剩下的继续建立最大堆,找出最大元素,再放到最后......
 *
 */
public class HeapSort {
    public static void main(String[] args) {
        int[] keys = {16, 32, 4, 7, 64};
        heapSort(keys, keys.length);
        print(keys);
    }

    //堆排序
    public static void heapSort(int[] keys, int len) {
        //一开始先建立一个堆
        buildHeap(keys, len);

        //建立第一个堆之后,将第一个(最大的)与数组最后的元素交换,之后再建立堆,这里有i来控制把排好序的最大值去掉
        for (int i = len-1; i >= 0; i--) {
            //第一个(最大的)与数组最后的元素交换
            swap(keys, i, 0);

            // 把整个树再建一次最大堆,目的都是找最大值。
            //buildHeap(keys, i);
            sift(keys, i);
        }
    }

    //将整个数组,建立成最大堆
    static void buildHeap(int[] keys, int len) {
        for (int i = len/2-1; i >= 0; i--) {
            heapify(keys, len, i);
        }
    }

    //将某个父节点为parent的子树 调整为最大堆
    static void heapify(int[] keys, int len, int parent) {
        if (parent >= len) {
            return;
        }
        int c1 = 2*parent + 1;
        int c2 = 2*parent + 2;
        int max = parent;
        if (c1 < len && keys[c1] > keys[max]) {
            max = c1;
        }
        if (c2 < len && keys[c2] > keys[max]) {
            max = c2;
        }
        if (max != parent) {
            swap(keys, max, parent);
        }
    }

    // 将buildHeap() 与 heapify()方法合并
    static void sift(int[] keys, int len) {
        for (int parent = len/2-1; parent >= 0 ; parent--) {
            int c1 = 2 * parent + 1;
            int c2 = 2 * parent + 2;
            int max = parent;
            if (c1 < len && keys[c1] > keys[max]) {
                max = c1;
            }
            if (c2 < len && keys[c2] > keys[max]) {
                max = c2;
            }
            if (max != parent) {
                swap(keys, max, parent);
            }
        }
    }


    static void swap(int[] keys, int i, int j) {
        int temp = keys[i];
        keys[i] = keys[j];
        keys[j] = temp;
    }

    static void print(int[] keys) {
        for (int key : keys) {
            System.out.print(key+" ");
        }
    }
}

教材上的:

//(第5版)9.3.2   堆排序
public static void heapSort(int[] keys)                //堆排序(升序),最大堆
{
    heapSort(keys,true);
}
//堆排序,若asc取值为true,升序排序,创建最大堆;否则降序,创建最小堆
public static void heapSort(int[] keys, boolean asc) 
{
    for (int i=keys.length/2-1; i>=0; i--)             //创建最小/大堆,根结点值最小/大
        sift(keys, i, keys.length-1, !asc);
    System.out.print("最"+((!asc)?"小":"大")+"堆:");
    Array1.print(keys);
    System.out.println("非递归算法,最小堆? "+isHeap(keys,true)+",最大堆? "+isHeap(keys,false));
    System.out.print("堆排序("+((!asc)?"降":"升")+"序):");
    for (int i=keys.length-1; i>0; i--)                //每趟将最小/大值交换到后面,再调整成最小/大堆
    {
        swap(keys, 0, i);                              //交换keys[0]与keys[i]
        sift(keys, 0, i-1, !asc);
    }
    Array1.print(keys);
}
//将keys数组中以parent为根的子树调整成最小/大堆,子序列范围为parent~end。
private static void sift(int[] keys, int parent, int end, boolean minheap)
{
    //        System.out.print("sift  "+parent+".."+end+"  ");
    int child=2*parent+1;                              //child是parent的左孩子
    int value=keys[parent];
    while (child<=end)                                 //沿较小/大值孩子结点向下筛选
    {
        if (child<end && (minheap ? keys[child]>keys[child+1] : keys[child]<keys[child+1]))
            child++;                               //child记住孩子值较小/大者
        if (minheap ? value>keys[child] : value<keys[child])   //若父母结点值较小/大
        {   keys[parent] = keys[child];                //将较小/大孩子结点值上移
         parent = child;                            //parent、child两者都向下一层
         child = 2*parent+1;
        }
        else break;
    }
    keys[parent] = value;                              //当前子树的原根值调整后的位置
    //        Array1.print(keys);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值