堆排序原理及Java实现

一、堆排序原理

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

(1)堆中某个结点的值总是不大于或不小于其父结点的值。

(2)堆总是一棵完全二叉树。

将根结点最大的堆称为最大堆或大根堆,根结点最小的堆称为最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆是非线性数据结构,相当于一维数组,有两个直接后继(即左右孩子节点)。

堆的定义如下:n个元素的序列{k1,k2,ki,…,kn},当且仅当满足下关系时,称之为堆。

若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。

大顶堆和小顶堆示例:

对堆中的结点,按层进行编号,将这种逻辑结构映射到数组中,就是下面这个样子:

从逻辑上讲,这个数组就是一个堆结构。我们用简单的公式来描述一下堆的定义就是:

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]  

小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]  

了解这些定义后,我们来看看堆排序的算法思想和基本步骤:

第1步:将待排序序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。

第2步:将其与末尾元素进行交换,此时,末尾元素就是最大值。

第3步:将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。

第4步:如此反复执行,便能得到一个有序序列。
 

二、堆排序Java实现

2.1 堆排序实现方式1

从根节点开始构建大顶堆,然后将堆顶元素与末尾元素进行交换。将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。如此反复执行,便能得到一个有序序列。

package com.mvp.test.sortway;


import java.util.Arrays;

public class HeapSortTest {

    public void sort(int[] array) {
        if (array == null || array.length <= 1) {
            return;
        }

        // 从根节点开始,构建大顶堆
        System.out.println("Before heapInsert " + Arrays.toString(array));
        for (int i = 0; i < array.length; i++) {
            heapInsert(array, i);
        }
        System.out.println("After heapInsert " + Arrays.toString(array));

        // 利用大顶堆的性质,升序排列
        int heapSize = array.length;
        swap(array, 0, --heapSize); // 将 索引0处的最大值与索引length-1处的值 进行交换,使得索引heapSize-1处的值为最大值
        while (heapSize > 0) {
            System.out.println("Before heapify " + Arrays.toString(array));
            heapify(array, 0, heapSize); // 将索引0到索引heapSize-1的数组 调整为大顶堆
            System.out.println("After heapify " + Arrays.toString(array));
            swap(array, 0, --heapSize); // 将 索引0处的最大值与索引heapSize-1处的值 进行交换,使得索引heapSize-1处的值为最大值
        }
    }

    // 构建大顶堆
    public void heapInsert(int[] array, int nodeIndex) {
        // index > 0 的条件可以省略,原因为:如果index是0,左值和右值都等于a[0],无法进入循环。
        int parentIndex = (nodeIndex - 1) / 2;
        while (array[parentIndex] < array[nodeIndex]) { // 如果父节点的值小于子节点的值,则交换父节点和子节点的值。
            swap(array, nodeIndex, parentIndex);
            nodeIndex = parentIndex;
            parentIndex = (nodeIndex - 1) / 2;
        }
    }

    // 调整位置,保持索引范围[nodeIndex,heapSize-1]内的元素序列为大顶堆
    public void heapify(int[] array, int nodeIndex, int heapSize) {
        int leftChildIndex = nodeIndex * 2 + 1;
        while (leftChildIndex < heapSize) {
            int rightChildIndex = leftChildIndex + 1;
            int maxChildIndex = ((rightChildIndex < heapSize) && (array[leftChildIndex] < array[rightChildIndex])) ? rightChildIndex : leftChildIndex;
            int maxIndex = array[maxChildIndex] > array[nodeIndex] ? maxChildIndex : nodeIndex;
            if (maxIndex == nodeIndex) {
                break;
            }

            swap(array, nodeIndex, maxIndex);
            nodeIndex = maxIndex;
            leftChildIndex = nodeIndex * 2 + 1;
        }
    }

    public void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static void main(String[] args) {
        int[] array = {3, 5, 6, 2, 9, 0};
        HeapSortTest heapSortTest = new HeapSortTest();
        heapSortTest.sort(array);
        System.out.println("sorted=" + Arrays.toString(array));
    }
}

运行结果如下所示:

Before heapInsert [3, 5, 6, 2, 9, 0]
After heapInsert [9, 6, 5, 2, 3, 0]
Before heapify [0, 6, 5, 2, 3, 9]
After heapify [6, 3, 5, 2, 0, 9]
Before heapify [0, 3, 5, 2, 6, 9]
After heapify [5, 3, 0, 2, 6, 9]
Before heapify [2, 3, 0, 5, 6, 9]
After heapify [3, 2, 0, 5, 6, 9]
Before heapify [0, 2, 3, 5, 6, 9]
After heapify [2, 0, 3, 5, 6, 9]
Before heapify [0, 2, 3, 5, 6, 9]
After heapify [0, 2, 3, 5, 6, 9]
sorted=[0, 2, 3, 5, 6, 9]

2.2 堆排序实现方式2

从倒数第1个非叶子结点开始,从下至上、从右至左构建大顶堆,然后将堆顶元素与末尾元素进行交换。将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。如此反复执行,便能得到一个有序序列。

package com.mvp.test.sortway;

import java.util.Arrays;

public class HeapSortAdjustTest {

    public static void sort(int[] array) {
        if ((array == null) || (array.length <= 1)) {
            return;
        }

        // 1.构建大顶堆
        for (int i = array.length / 2 - 1; i >= 0; i--) {
            // 从倒数第1个非叶子结点 从下至上、从右至左 调整结构
            adjustHeap(array, i, array.length);
        }

        // 2.调整结构,交换堆顶元素与末尾元素
        for (int j = array.length - 1; j > 0; j--) {
            swap(array, 0, j); // 将堆顶元素与末尾元素进行交换
            adjustHeap(array, 0, j); // 将剩余j-1个元素调整为大顶堆
        }
    }

    /**
     * 调整大顶堆
     *
     * @param array  数组
     * @param nodeIndex 下标
     * @param length 序列长度
     */
    public static void adjustHeap(int[] array, int nodeIndex, int length) {
        int temp = array[nodeIndex]; // 先取出当前元素
        int rightChildIndex;
        int maxChildIndex;
        for (int leftChildIndex = nodeIndex * 2 + 1; leftChildIndex < length; ) { // 从nodeIndex结点的左子结点开始,也就是2*nodeIndex+1处开始
            rightChildIndex = leftChildIndex + 1;
            maxChildIndex = leftChildIndex;
            if ((rightChildIndex < length) && (array[leftChildIndex] < array[rightChildIndex])) { // 如果左子结点小于右子结点,k指向右子结点
                maxChildIndex = rightChildIndex;
            }

            if (array[maxChildIndex] > temp) { // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                array[nodeIndex] = array[maxChildIndex];
                nodeIndex = maxChildIndex;
                leftChildIndex = nodeIndex * 2 + 1;
            } else {
                break;
            }
        }

        array[nodeIndex] = temp; // 将temp值放到最终的位置
    }

    /**
     * 交换元素
     *
     * @param array 数组
     * @param a 下标值a
     * @param b 下标值b
     */
    public static void swap(int[] array, int a, int b) {
        int temp = array[a];
        array[a] = array[b];
        array[b] = temp;
    }

    public static void main(String[] args) {
        int[] array = {3, 5, 6, 2, 9, 0, 7, 11, 5, 12, 3, 0, 1};
        System.out.println("排序前:" + Arrays.toString(array));
        sort(array);
        System.out.println("排序前:" + Arrays.toString(array));
    }
}

运行结果如下所示:

排序前:[3, 5, 6, 2, 9, 0, 7, 11, 5, 12, 3, 0, 1]
排序前:[0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 11, 12]

参考文档:

https://blog.csdn.net/qq_36186690/article/details/82505569

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验目的: 通过本实验,实现堆排序算法的理解,掌握堆排序算法原理以及实现方法,并掌握Java语言的编程技巧。 实验内容: 堆排序是一种树形选择排序方法,是对直接选择排序算法的有效改进。堆排序的基本思想是:将待排序的序列构造成一个大根堆或小根堆,堆顶元素为最大值或最小值,然后将堆顶元素与最后一个元素交换,然后把剩余的元素重新构造成堆。如此反复执行,直到排序完成。由于堆排序算法的时间复杂度为O(nlogn),因此在大数据量的情况下,堆排序是高效的。 堆排序算法实现主要有两个步骤:构建初始堆和堆排序。其中,构建初始堆是将待排序的序列构建成一个大根堆或小根堆的过程,堆排序是不断将堆顶元素与最后一个元素交换,并重新调整堆的过程。 以下是Java语言实现堆排序的代码: ```java public class HeapSort { public static void heapSort(int[] arr) { int n = arr.length; // 构建初始堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(arr, n, i); // 堆排序 for (int i = n - 1; i >= 0; i--) { // 将堆顶元素与最后一个元素交换 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 调整堆 heapify(arr, i, 0); } } // 调整堆 public static void heapify(int[] arr, int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; // 找到左子节点和右子节点中的最大值 if (left < n && arr[left] > arr[largest]) largest = left; if (right < n && arr[right] > arr[largest]) largest = right; // 如果最大值不是根节点,则交换根节点和最大值,并递归调整子堆 if (largest != i) { int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; heapify(arr, n, largest); } } public static void main(String[] args) { int[] arr = { 64, 34, 25, 12, 22, 11, 90 }; heapSort(arr); System.out.println("排序后的数组:"); for (int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); } } ``` 实验结果: 经过测试,以上代码能够正确地对数列进行堆排序,得到正确的排序结果。 实验总结: 通过本次实验,我学习了堆排序算法原理实现方法,并通过Java语言编写了相应的代码。堆排序算法具有高效的时间复杂度,在大数据量的情况下具有明显的优势。这次实验让我对Java语言的编程技巧有了更深刻的理解,也对算法实现有了更深的认识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值