排序算法(五)

2. 选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)为最小项(小顶堆)。

若以一维数组存储一个堆,则堆对应一颗完全二叉树,且所有非叶节点的值均不大于(或不小于)其子女的值,根节点的值(堆顶元素)的值是最小(或最大)的。如:

(a)大堆顶序列(96,83,27,38,11,09)

(b)小堆顶序列(12,36,24,85,47,30,53,91)

初始时把要排列的的n个数的序列看做是一颗顺序存储的二叉树(一维数组存储二叉树),调整他们的存储序,使之成为一个堆。将堆顶元素输出,得到n个元素中最小(或最大)的元素,这时堆的根节点的数最小(或最大)。然后对前面的n-1个元素重新调整使之成为堆,输出堆顶元素,得到n个元素中次小(或次大)的元素。依此类推,知道只有俩个节点的堆,并对他们做交换,最后得到n个节点的有序序列,称这个过程为堆排序。

因此,实现堆排序需要解决俩个问题:

1、如何将n个待排序的数建成堆。

2、输出堆顶元素后,怎样调整剩余n-1个元素,使其成为堆。

首先,我们讨论第二个问题:输出堆顶元素后,对剩余的n-1个元素重新建成堆的调整过程。

调整小堆顶的方法(大堆顶也是一样的道理):

1、设有m个元素的堆,输出堆顶元素后,剩下m-1个元素。将堆底元素送入堆顶(最后一个元素与堆顶元素进行交换),堆被破坏,其原因仅是根节点不满足堆的性质。

2、将目前最新的根节点与左、右子数中较小元素进行交换。

3、若与左子数交换:如果左子数堆被破坏,即左子数的根节点不满足堆的性质,则重复方法(2)。

4、若与右子数交换,如果右子数堆被破坏,即右子数的根节点不满足堆的性质。则重复方法(2)。

5、继续对不满足堆性质的子数进行上述交换操作,知道叶子节点,堆被建成。

称这个字根节点到叶子节点的调整过程称为筛选。如图:

再讨论对n个元素初始建堆的过程。

建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1、n个节点的完全二叉树,则最后一个节点是n/2个节点的子数。

2、筛选从第n/2个节点为根的子数开始,该子数成为堆。

3、之后向前一次对各节点为根的子数进行筛选,使之成为堆,直到根节点。

如图建堆的初始过程:无序序列:(49,38,65,97,76,13,27,49)

算法的实现:

从算法的描述来看,堆排序需要俩个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以,堆排序有俩个函数组成,一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

public class HeapSort {

    public static void main(String[] args) {
        int[] a = {12,54,76,23,435,98,21,1,2,23,54,76,34,2,78,243};
        heapSort(a, a.length);

        for(int i=0; i<a.length; i++){
            System.out.print(a[i]+" ");
        }
    }
    /**
     * 堆排序算法
     * */
    public static void heapSort(int[] a,int length){
        buildingHeap(a, a.length);    //初始堆

        for(int i=length-1; i>0; --i){
            int temp = a[i];
            a[i] = a[0];
            a[0] = temp;
            heapAdjust(a, 0, i);
        }
    }
    /**
     * 初始堆进行调整
     * 将a[0..length-1]建成堆
     * 调整完之后第一个元素师序列的最小元素
     * */
    public static void buildingHeap(int[] a,int length){
        //最后一个有孩子的节点的位置i = length/2 - 1
        for(int i=(length/2)-1; i>=0; --i){
            heapAdjust(a, i, length);
        }
    }
    /**
     * 已知a[a...m]除了a[s]外均满足堆的定义
     * 调整a[s],使其成为大顶堆,即将对第s个节点为根的子数帅选
     *
     * @param a是待调整的数组
     * @param s是待调整的数组元素的位置
     * @param length是数组的长度
     * */
    public static void heapAdjust(int[] a,int s,int length){
        int temp = a[s];
        int child = 2*s+1;    //左孩子节点的位置。
        while(child < length){
            if(child+1 < length && a[child] <a[child+1] ){//如果右孩子大于左孩子(找到比当前待调整节点大的孩子)
                ++ child;
            }
            if(a[s]<a[child]){//如果较大的子节点大于父节点
                a[s] = a[child];//那么把较大的子节点往上移动,替换他的父节点
                s = child;    //重新设置s,即待调整的下一个节点的位置
                child = 2*s+1;
            }else{    //如果当前待调整节点大于他的左右孩子,则不需要调整,直接退出
                break;
            }
            a[s] = temp;//当前待调整的节点放到比其大的孩子的节点的位置上
        }
    }
}

算法分析:

设数深度为k,k=log2n+1。从根到叶的筛选,元素比较次数之多2(k-1)次,交换记录之多k次。所以,在建好堆后,排序过程中的次数不超过下式:

而建堆的比较次数不超过4n次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn)。

时间: 2024-08-24 07:58:20

排序算法(五)的相关文章

排序算法五:随机化快速排序(Randomized quicksort)

上一篇提到,快速排序的平均时间复杂度是O(nlgn),比其他相同时间复杂度的堆排序.归并排序都要快,但这是有前提的,就是假定要排序的序列是随机分布的,而不是有序的.实际上,对于已经排好的序列,如果用快速排序时间复杂度是O(n2).为应对这样的有序序列,于是出现了本篇要讲的随机化快速排序(Randomized quicksort). 快速排序在选主元(pivot)时,总是选择第一个:随机化快速排序的思想是,随机从序列中选择一个作为主元. (一)算法实现 1 protected void quick

Java排序算法(五):堆排序

[算法说明] 堆排序是对简单选择排序的改进 简单选择排序是从n个记录中找出一个最小的记录,需要比较n-1次.但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多. 堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆. [算法思想] 将待排序的序列构造成一个大顶

常见的五类排序算法图解和实现(多关键字排序:基数排序以及各个排序算法的总结)

基数排序思想 完全不同于以前的排序算法,可以说,基数排序也叫做多关键字排序,基数排序是一种借助“多关键字排序”的思想来实现“单关键字排序”的内部排序算法. 两种方式: 1.最高位优先,先按照最高位排成若干子序列,再对子序列按照次高位排序 2.最低位优先:不必分子序列,每次排序全体元素都参与,不比较,而是通过分配+收集的方式. 多关键字排序 例:将下表所示的学生成绩单按数学成绩的等级由高到低排序,数学成绩相同的学生再按英语成绩的高低等级排序.        第一个关键字是数学成绩,第二个关键字是英

Jerry 2017年的五一小长假:8种经典排序算法的ABAP实现

2017年4月29日~5月1日,国际劳动节, 三天的小长假. 在国内,小长假往往是这样的: 然而我当时在戏称为"德村"(德国农村)的Walldorf出差并且住在Wiesloch, 这里的五一小长假能听见鸟叫,虫鸣,和风吹过的声音,除此之外再无其他. 街道上别说行人了,连行驶的汽车都很少. 如果一个在成都习惯了热闹生活的人,到了这种乡下地方来估计会觉得百无聊赖.当时国内有同事建议我小长假去德国其他地方转转,然而作为一个30年资深宅男,一个人出去转不是我的风格. 五一放假之前,坐我对面的一

各种基本算法实现小结(五)—— 排序算法

各种基本算法实现小结(五)-- 排序算法 (均已测试通过) * 选择排序 |____简单选择排序 |____堆排序 |____归并排序 * 交换排序 |____冒泡排序 |____快速排序 * 插入排序 |____直接插入排序 |____折半排序 |____希尔排序 * 分配排序 |____箱排序 |____基数排序 ====================================================================== 简单排序算法 1. 冒泡排序 测试环境

基本排序(五):基本排序算法的总结

基本排序的基础 假设一个主要的排序算法不比数据处理,如数据读入和读出慢,就没有必要寻找一个更快的算法. 假设被排序的文件适合放在内存中.则排序方法称为"内部排序".从磁盘上对文件排序称为"外部排序".差别是内部排序能够非常easy的訪问不论什么元素,可是外部排序必须顺序訪问元素.至少在大的数据块是如此. 排序程序訪问元素的方式:通过keyword进行比較.直接訪问整个元素移动: 排序函数都包过3个參数:数组.带排序字数组的左边界和右边界 void(Item a[],

算法五:希尔排序

希尔排序(Shell Sort)是插入排序的一种.也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本.希尔排序是非稳定排序算法.该方法因DL.Shell于1959年提出而得名.先取一个小于n的整数d1作为第一个增量,把文件的全部记录分组.所有距离为d1的倍数的记录放在同一个组中.先在各组内进行直接插入排序:然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量=1(<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止. 1.就是先确定步长,按照步长(le

算法学习之排序算法(五)(高速排序)

1.算法思想 设要排序的数组是A[0]--A[N-1],首先随意选取一个数据(通常选用数组的第一个数)作为重要数据,然后将全部比它小的数都放到它前面.全部比它大的数都放到它后面.这个过程称为一趟高速排序.值得注意的是,高速排序不是一种稳定的排序算法.也就是说,多个同样的值的相对位置或许会在算法结束时产生变动. 一趟高速排序的算法是: 1)设置两个变量i.j.排序開始的时候:i=0.j=N-1. 2)以第一个数组元素作为重要数据,赋值给key.即key=A[0]. 3)从j開始向前搜索,即由后開始

计数排序 - 算法数据结构面试分享(五)

数组排序问题 - 计数排序 昨天我们留了一道题目"给你一个整型数组,里面出现的数在[0-100] 之间,能用最优化的方法帮我排序吗". 1. 确保我们理解了问题,并且尝试一个例子,确认理解无误. 这是一道排序算法题,我们学过很多排序的算法.不一样的是,它给定一个额外的条件,数组里的每个数字都在1-100之间.如果我们采取传统的排序算法,这个条件我们好像用不上.大家在面试的时候如果发现有条件没有用上,基本上我们给出的算法可能不是最优的,或者我们没有解决它最原始的需求.举个例子{50, 4

排序算法(五)归并排序

前面我们讲了堆排序,因为它用到了完全二叉树所以效率 比较高.不过堆结构的设计本身是比较复杂的,老实说,能想出这样的结构就挺不容易 , 有没有更直接简单的办法利用完全二叉树来排序呢?当然有. 为了更清晰地说清楚这里的思想,大家来看图 9-8-1 所示,我们将本是无序的数组序列 {16,7,13,10,9,15,3,2,5,8,12,1,11,4,6,14} ,通过两两合并排序后再合井,最终获得了 一个有序的数组.注意仔细观察它的形状,你会发现 , 它像极了一棵倒置的完全二叉树,通常涉及到完全二叉树