179Java实现堆排序

Posted zhangchao19890805

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了179Java实现堆排序相关的知识,希望对你有一定的参考价值。

堆是一种特殊的完全二叉树。堆分为最大堆和最小堆。最大堆的任意父节点都大于子节点。最小堆的任意父节点都小于子节点。堆可以用数组或列表来表示。数组或列表的第0个元素是堆的根节点,第 i 个元素的左子节点是第 (2i + 1)个元素,第 i 个元素的右子节点是第 (2i + 2)个元素。第 i 个元素的父节点是第 【(i - 1 ) / 2 向下取整】个元素。

以按照从小到大排序为例子。

创建最大堆:

  • 第 0 个元素是堆的根节点。把第0个元素视为一个已经建好的堆。

  • 第 1 个元素作为新节点加入堆中,第 1 个元素的父节点是 第 0 个元素。计算方法:(1-1) / 2 = 0 。第1个元素和第 0 个元素比较大小,如果第 1 个元素大于第 0 个元素,两者交换位置;反之不做操作。

  • 第 2 个元素作为新节点加入堆中,第 2 个元素的父节点是 第 0 个元素。计算方法:(2-1) / 2 = 0.5 向下取整为0 。第 2 个元素和第 0 个元素比较大小,如果第 2 个元素大于第 0 个元素,两者交换位置;反之不做操作。

  • 第 3 个元素作为新节点加入堆中,第 3 个元素的父节点是 第 1 个元素。计算方法:(3-1) / 2 = 1 。第3个元素和第 1 个元素比较大小,如果第 3 个元素大于第 1 个元素,两者交换位置;然后因为新节点到了新的 1 号位置,需要跟父节点比较大小,此时的父节点是第 0 个元素,新节点如果大于第 0 个元素,就交换位置。

  • 第 i 个元素作为新节点加入堆中,需要计算父节点的位置。计算方法:(i - 1) / 2 向下取整。新节点和父节点比较大小。因为前面的 (i - 1) 个元素已经组成一个堆了,所以新节点没有必要和兄弟节点做比较。如果新节点小于等于父节点,就不做处理。如果新节点大于父节点,二者交换位置。交换位置后,新节点需要继续计算出父节点的位置,并且和父节点做比较,如果大于父节点就交换位置。被交换到下一级的父节点没必要再和子节点做比较,因为已经是一个堆了,父节点虽然小于新节点,但是肯定大于或等于旧的子节点。

这里你可能已经看出来了,新节点找父节点比较和交换位置的过程是一个循环。要想终止循环,只需要满足下面任意一个条件即可:

  1. 新节点已经到了第 0 个的位置,已经是根节点了。
  2. 新节点小于等于父节点。

当数组或列表中所有的元素都加入了堆以后,创建堆的步骤就完成。

在创建完堆后,第 0 个元素就是堆顶,也就是最大值。因为是从小到大排序,所以需要不断从堆顶找出最大元素放到数组或列表的末尾。具体步骤如下:

  • 把最后一个元素和第 0 个元素交换位置。此时堆的大小就是 n - 1。此时堆顶的新节点可能不是最大值,找出最大的子节点,如果新节点大于等于最大子节点,就不做操作;如果新节点小于最大子节点,就交换位置。交换位置后,新节点继续和子节点做比较,直到新节点大于等于子节点,或者新节点成为叶子节点。显然新节点和子节点比较和交换位置的过程是一个循环。

  • 把第倒数2个元素和第 0 个元素交换位置。堆的大小变成了 n - 2,堆顶新节点的调整方法和上一段相同。

下面是堆排序的源码,文件名 HeapSort.java

package zhangchao;
import java.util.Comparator;
import java.util.List;

/**
 * 堆排序
 * @author zhangchao 
 */
public class HeapSort 
    /**
     * 创建堆
     * @param list 要进行排序的列表
     * @param comparator 比较用的函数钩子
     * @param <T> list中的元素类型
     */
    private static<T> void createHeap(List<T> list, Comparator<T> comparator) 
        int size = list.size();
        // 假设第0个元素已经是堆了,从第1个元素开始加入堆。
        for (int i = 1; i < size; i++) 
            int newIndex = i;
            while (newIndex > 0) 
//              int parentIndex = (newIndex - 1) / 2;
                int parentIndex = (newIndex - 1) >> 1;
                T parent = list.get(parentIndex);
                T newNode = list.get(newIndex);
                if (comparator.compare(newNode, parent) > 0) 
                    list.set(parentIndex, newNode);
                    list.set(newIndex, parent);
                    newIndex = parentIndex;
                 else 
                    // 小于等于父亲节点,没有上升的需要,不需要再查找上级节点了。
                    newIndex = -1;
                
            
        
    

    /**
     * 排序
     * @param list 要进行排序的列表
     * @param comparator 比较用的函数钩子
     * @param <T> list中的元素类型
     */
    public static<T> void sort(List<T> list, Comparator<T> comparator) 
        if (null == list || list.size() < 2) 
            return;
        
        createHeap(list, comparator);

        int size = list.size();
        for (int i = size - 1; i >= 0; i--) 
            // 当前节点和堆顶交换位置。
            T current = list.get(i);
            list.set(i, list.get(0));
            list.set(0, current);

            // 当前节点放到堆顶后,不断和子节点做比较,以便调整当前节点在堆中的位置。
            int currentIndex = 0;
            int heapSize = i;
            boolean whileFlag = true;
            while(whileFlag) 
//              int leftIndex = currentIndex * 2 + 1;
//              int rightIndex = currentIndex * 2 + 2;
                int leftIndex = (currentIndex << 1) + 1;
                int rightIndex = (currentIndex << 1) + 2;
                if (rightIndex < heapSize) 
                    T right = list.get(rightIndex);
                    T left = list.get(leftIndex);
                    // 找出最大子节点
                    int maxIndex = rightIndex;
                    T max = right;
                    if (comparator.compare(left, right) > 0) 
                        maxIndex = leftIndex;
                        max = left;
                    
                    if (comparator.compare(max, current) > 0) 
                        list.set(maxIndex, current);
                        list.set(currentIndex, max);
                        currentIndex = maxIndex;
                     else 
                        whileFlag = false;
                    
                    /*
                    这段代码被注释掉,是因为这种比较方式会比上一种方式多一到两次比较。
                    // 右子节点最大
                    if (comparator.compare(right, left) > 0 && comparator.compare(right, current) > 0) 
                        list.set(rightIndex, current);
                        list.set(currentIndex, right);
                        currentIndex = rightIndex;
                    
                    // 左子节点最大
                    else if (comparator.compare(left, right) > 0 && comparator.compare(left, current) > 0) 
                        list.set(leftIndex, current);
                        list.set(currentIndex, left);
                        currentIndex = leftIndex;
                     else 
                        whileFlag = false;
                    
                     */
                 else if (leftIndex < heapSize) 
                    T left = list.get(leftIndex);
                    if (comparator.compare(left, current) > 0) 
                        list.set(leftIndex, current);
                        list.set(currentIndex, left);
                        currentIndex = leftIndex;
                     else 
                        whileFlag = false;
                    
                 else 
                    whileFlag = false;
                
            

         // end for
    


为了直观感受堆排序的效率,我还写了冒泡排序来做比较,下面是冒泡排序代码,文件是 BubbleSort.java

package zhangchao;

import java.util.Comparator;
import java.util.List;

/**
 * 冒泡排序
 * @author zhangchao
 */
public class BubbleSort 

    /**
     * 排序
     * @param list 要进行排序的列表
     * @param comparator 比较用的函数钩子
     * @param <T> list中的元素类型
     */
    public static<T> void sort(List<T> list, Comparator<T> comparator) 
        int size = list.size();
        for (int i = 1; i < size; i++) 
            // 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
            boolean flag = true;

            for (int j = 0; j < size - i; j++) 
                T first = list.get(j);
                T second = list.get(j + 1);

                if (comparator.compare(first, second) > 0) 
                    list.set(j, second);
                    list.set(j + 1, first);
                    flag = false;
                
            

            if (flag) 
                break;
            
        
    


下面是测试代码 Main.java

package zhangchao;

import java.util.*;

/**
 * 测试
 * @author zhangchao
 */
public class Main 

    /**
     * 检查列表是不是从小到大排序的。
     * @param list 要检查的列表
     * @return 如果是从小到大就返回true,其中相邻元素相等不影响返回结果。
     *         只要其中相邻元素是从大到小的,就返回false。
     */
    private static boolean check(List<Integer> list, List<Integer> originList)
        if (null == list || list.isEmpty() || originList == null || originList.isEmpty()) 
            return false;
        

        Map<Integer, Integer> listMap = new HashMap<>();
        for (Integer item : list) 
            if (listMap.containsKey(item)) 
                int tmp = listMap.get(item);
                tmp++;
                listMap.put(item, tmp);
             else 
                listMap.put(item, 1);
            
        

        Map<Integer, Integer> originMap = new HashMap<>();
        for (Integer item : originList) 
            if (originMap.containsKey(item)) 
                int tmp = originMap.get(item);
                tmp++;
                originMap.put(item, tmp);
             else 
                originMap.put(item, 1);
            
        

        if (listMap.size() != originMap.size()) 
            return false;
        
        for (Integer key : originMap.keySet()) 
            int originVal = originMap.get(key);
            int listVal = listMap.get(key);
            if (originVal != listVal) 
                return false;
            
        

        for (int i = 0; i < list.size() - 1; i++) 
            if (list.get(i) > list.get(i + 1)) 
                return false;
            
        
        return true;
    

    public static void main(String[] args) 
        List<Integer> originList = new ArrayList<>();
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        List<Integer> list3 = new ArrayList<>();
        final int MAX = 70000;
        for (int i = 0; i < MAX; i++) 
            Random r = new Random();
            int num = r.nextInt(MAX * 8);
            originList.add(num);
            list1.add(num);
            list2.add(num);
            list3.add(num);
        

        Comparator<Integer> comparator = new Comparator<Integer>() 
            @Override
            public int compare(Integer o1, Integer o2) 
                return o1 - o2;
            
        ;
        long t1 = System.currentTimeMillis();
        HeapSort.sort(list1, comparator);
        long t2 = System.currentTimeMillis();
        System.out.println("heap     check="+ check(list1, originList) +" time=" + (t2-t1) );

        t1 = System.currentTimeMillis();
        list2.sort(comparator);
        t2 = System.currentTimeMillis();
        System.out.println("listSort check="+ check(list2, originList) +" time=" + (t2-t1));

        t1 = System.currentTimeMillis();
        BubbleSort.sort(list3, comparator);
        t2 = System.currentTimeMillis();
        System.out.println("bubble   check="+ check(list3, originList) +" time=" + (t2-t1));



//        System.out.println("origin=" + originList);
//        System.out.println("list1 =" + list1);
    



输出结果:

heap     check=true time=59
listSort check=true time=33
bubble   check=true time=22821

以上是关于179Java实现堆排序的主要内容,如果未能解决你的问题,请参考以下文章

C语言试题179之实现堆排序算法

C语言试题179之实现堆排序算法

Java实现堆排序和计数排序

JavaScript实现堆排序

数据结构Java版之堆&堆排序

堆排序(Python实现)