Java 堆排序

1、关于堆

堆就是一个简单的数组。只是我们用一种完全二叉树的角度来看它。以最大堆为例,比如说我们有一棵如下的二叉树:
在这里插入图片描述
上图中,如果从根结点开始按照从左到右一层一层的编号的话,对这些元素的访问就构成了一个序列。比如上图中的序列按照编号顺序如下:16, 14, 10, 8, 7, 9, 3, 2, 4, 1

如果我们将这种从二叉树的结点关系转换成对应的数组形式的话,则对应的数组如下图:
在这里插入图片描述
从二叉树的每个结点的编码到它的左右字结点的关系,我们发现一个有意思的地方:

  • 左子结点的编号 = 父结点编号 * 2
  • 右子结点的编号 = 父结点编号 * 2 + 1

按照数组标的编号,有类似的对应关系:

  • 左子结点的数组索引号 = 父结点索引号 * 2
  • 右子结点的数组索引号 = 父结点索引号 * 2 + 1

在实现的时候考虑到我们的数组下标是从0开始的,对应的关系修改为:

  • left(n) = n * 2 + 1
  • right(n) = n * 2 + 2

对应的代码实现如下:

public static int left(int i) {  
    return i * 2 + 1;  
}
public static int right(int i) {  
    return i * 2 + 2;  
} 

2、调整堆

前面我们已经理解了堆和对应的数组之间的关系了。
我们假定是要建立一个最大堆。它有一个重要的特性就是处于父结点的值必须比它的子结点要大。如果某一棵树上面的父结点不满足这个要求,我们就必须进行调整。笼统的说,调整就是将这个不符合条件的结点和子结点进行比较,通过交换将最大的结点作为父结点。具体的流程见下图:

在这里插入图片描述
在上图中,我们发现值为4的结点不符合要求。那么就需要进行交换调整。接着就需要在它的两个子结点中选择最大的那个,然后交换位置。它的子结点中最大的是14.交换之后的结果如下图:
在这里插入图片描述
经过交换之后,我们发现原来元素所在的位置确实符合要求了。可是4交换到新的结点之后又不符合最大堆的条件了。没办法,还需要继续选择最大子结点进行交换。这么一交换之后的结果如下:

在这里插入图片描述

经过这么两轮交换,我们终于可以保证以i = 2这个结点为根的树最终达到了一个符合最大堆的状态。总结前面这么一个交换调整的过程,主要如下:

  1. 比较当前结点和它的子结点,如果当前结点小于它的任何一个子结点,则和最大的那个子结点交换。否则,当前过程结束。
  2. 在交换到新位置的结点重复步骤1,直到叶结点。

对上面的过程进行细化之后编码,我们可以得到两个版本的方法:

  • 递归版本
public static void maxHeapify(int[] a, int i) {  
    int l = left(i);  
    int r = right(i);  
    int largest = i;  
  
    if(l < a.length && a[l] > a[i])  
        largest = l;  
    if(r < a.length && a[r] > a[largest])  
        largest = r;  
    if(i != largest)  
    {  
        swap(a, i, largest);  
        maxHeapify(a, largest);  
    }  
}  
  • 非递归版本
public static void maxHeapify(int[] a, int i)  
{  
    int l = left(i);  
    int r = right(i);  
    int largest = i;  
    while(true)  
    {  
        if(l < a.length && a[l] > a[i])  
            largest = l;  
        if(r < a.length && a[r] > a[largest])  
            largest = r;  
        if(i != largest)  
            swap(a, i, largest);  
        else  
            break;  
        i = largest;  
        l = left(largest);  
        r = right(largest);  
    }  
}  

以上两个版本的实现主要有几个要点要注意:

  1. 每次求一个结点的子结点的时候要检查是否越界。
  2. 每次通过将当前结点和子结点的比较来选取最大值,如果最大值就是当前结点,则程序返回。
  3. 里面的swap方法就是交换两个索引位置元素的位置。

3、建最大堆

前面的过程主要针对的是一个树中的一个结点。如果树中间有多个结点不符合最大堆的条件,只调整某一个结点是没有用的。那么,就需要一个办法来将整棵树调整成符合条件的最大堆。
一个最简单的办法就是从最低层的结点开始起调整。很明显,如果我们从a[a.length -1]这样的结点来调整的话,有相当一部分结点是没必要的。因为这些结点很显然是叶结点,也就是说他们根本就没有子结点,连找子结点和去比较的必要都没有了。所以,我们可以从最后面往前到过来去找那些有子结点的结点,然后从这些结点开始一个个的进行堆调整。
首先第一个问题,从哪个结点开始进行调整。我们来看这棵二叉树,很显然,它最后的一个元素也肯定就是最终的一个叶结点。那么取它的父结点应该就是有子结点的最大号的元素了。那么从它开始就是最合适的。取它的父结点可以通过一个简单的i / 2来得到,i为当前结点的下标。
然后我们再来看第二个问题,为什么要从后往前而不是从前往后。这个相对也比较好理解。我们从下面的层开始调整,保证当上面的父结点来调整的时候,下面的子树已经满足最大堆的条件了。这样出现不符合条件的父结点只需要用前面的maxheapify过程就可以。而从前面往后调整呢,我们看下面的一个示例:

在这里插入图片描述
如果我们从根结点开始,根结点元素4比它的两个子结点都大,不需要调整。而再往后面的时候它的子结点1调整之后被换成16.这样就出现了它的子结点比它还要大的情况,因此从前往后这么调整的过程不行。

经过前面的讨论,构建最大堆的过程就相当的简单了:

public static void buildMaxHeap(int[] a) {  
    for(int i = a.length / 2; i >= 0; i--)  
        maxHeapify(a, i);  
}  

总的来说,建立最大堆的过程无非就是要建立一个符合如下条件的二叉树:它所有的结点值都比它的子结点要大。

4、堆排序

我通过建了一个最大堆,能够保证最大的元素就是根结点,那么,我们如果要从小到大排序的话,最大的元素就只要取根结点就可以了。如果我们把根结点拿走了,放到结果集的最末一个元素,接着就应该找第二大的元素。因为要保证这棵树本身是近似完全二叉树的性质,我们不能把中间的结点直接挪到根结点来比较。但是前面的maxHeapify过程提醒我们,如果我们从集合的最低一层叶结点来取,然后放到根结点进行调整的话,肯定也是可以得到剩下元素里面的最大结点的。就这样,我们可以得到这么一个过程:

  1. 取最大堆的根结点元素。
  2. 取集合最末尾的元素,放到根结点,调用maxHeapify进行调整。重复步骤1.

在具体实现的时候我们可以发现,每次都要取集合中后面的元素,我们原来得到的最大结点正好可以放到集合的末尾,正好达到最大的元素放到最后的效果。

public static void heapSort(int[] a) {  
    if(a == null || a.length <= 1)  
        return;  
  
    buildMaxHeap(a);  
    int length = a.length;  
    for(int i = a.length - 1; i > 0; i--) {  
        swap(a, i, 0);  
        length--;  
        maxHeapify(a, 0, length);  
    }  
}  

仔细看前面的代码,大家可能会发现一个细小的改变。就是maxHeapify方法多了个参数。这是因为考虑到实际情况下,如果每次我们把找到的当前集合最大元素放到后面了,那么这些元素就相当于从前面的集合中排除出来,后面进行堆调整的时候就不需要再考虑。所以用一个length的长度来限制调整的范围,以免伤及无辜:)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 中的堆排序是一种比较快速的排序算法,它的时间复杂度为 O(nlogn),其中 n 是要排序的数组的长度。它的基本思想是:将数组中的元素构建成一个大根堆,然后依次将堆顶元素(也就是最大的元素)与末尾元素交换,再重新构建大根堆,以此类推,直到整个数组有序。 以下是 Java 中的堆排序代码: ``` public void heapSort(int[] arr) { // 构建大根堆 for (int i = arr.length / 2 - 1; i >= 0; i--) { adjustHeap(arr, i, arr.length); } // 堆排序 for (int j = arr.length - 1; j > 0; j--) { // 将堆顶元素与末尾元素交换 swap(arr, 0, j); // 重新调整为大根堆 adjustHeap(arr, 0, j); } } /** * 调整大根堆 */ public void adjustHeap(int[] arr, int i, int len) { // 将当前节点的值保存到临时变量中 int temp = arr[i]; // k 指向当前节点的左子节点 for (int k = i * 2 + 1; k < len; k = k * 2 + 1) { // 如果当前节点的右子节点存在,并且大于左子节点,则指向右子节点 if (k + 1 < len && arr[k] < arr[k + 1]) { k++; } // 如果子节点大于父节点,则交换 if (arr[k] > temp) { arr[i] = arr[k]; i = k; } ### 回答2: 堆排序(Heap Sort)是一种通过构建最大堆或最小堆来实现的排序算法,它的平均时间复杂度为O(nlogn)。 下面是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); } } // 调整以root为根节点的子树成为最大堆 public static void heapify(int arr[], int size, int root) { int largest = root; // 初始化最大元素为根节点 int left = 2 * root + 1; // 左子节点的索引 int right = 2 * root + 2; // 右子节点的索引 // 如果左子节点大于根节点,将最大元素索引更新为左子节点 if (left < size && arr[left] > arr[largest]) { largest = left; } // 如果右子节点大于根节点,将最大元素索引更新为右子节点 if (right < size && arr[right] > arr[largest]) { largest = right; } // 如果最大元素不是根节点,则交换根节点和最大元素,并继续调整子树 if (largest != root) { int temp = arr[root]; arr[root] = arr[largest]; arr[largest] = temp; heapify(arr, size, largest); } } public static void main(String args[]) { int arr[] = {56, 23, 12, 78, 45, 10, 45}; heapSort(arr); System.out.println("排序后的数组:"); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } } } ``` 以上代码定义了一个`HeapSort`类,其中包含了一个`heapSort`方法和一个`heapify`方法。`heapSort`方法用于执行堆排序算法,而`heapify`方法用于调整以某个节点为根节点的子树,使其成为最大堆。 在`heapSort`方法中,首先从最后一个非叶子节点开始构建最大堆。然后,依次将最大的元素与数组末尾交换,并重新构建最大堆。最后,输出排序后的数组。 在`main`方法中,我们定义了一个待排序的数组,并调用`heapSort`方法对其进行排序。最后,输出排序后的数组。 以上是Java中实现堆排序的代码。 ### 回答3: Java 堆排序是一种使用堆数据结构进行排序的算法。下面是一个简单的Java实现: ```java import java.util.Arrays; public class HeapSort { public static void heapSort(int[] array) { int n = array.length; // 构建最大堆 for (int i = n / 2 - 1; i >= 0; i--) heapify(array, n, i); // 从堆顶开始不断将最大元素移至数组末尾 for (int i = n - 1; i >= 0; i--) { // 交换堆顶元素与当前末尾元素 int temp = array[0]; array[0] = array[i]; array[i] = temp; // 对剩余元素重新构建最大堆 heapify(array, i, 0); } } // 将数组中的元素构建为最大堆 public static void heapify(int[] array, int n, int i) { int largest = i; // 初始化堆顶元素为最大值 int left = 2 * i + 1; // 左子节点的索引位置 int right = 2 * i + 2; // 右子节点的索引位置 // 如果左子节点大于根节点,将largest设置为左子节点 if (left < n && array[left] > array[largest]) largest = left; // 如果右子节点大于当前最大值,将largest设置为右子节点 if (right < n && array[right] > array[largest]) largest = right; // 如果largest不是根节点,将largest与根节点交换,并继续构建最大堆 if (largest != i) { int swap = array[i]; array[i] = array[largest]; array[largest] = swap; heapify(array, n, largest); } } public static void main(String[] args) { int[] array = {10, 7, 8, 9, 1, 5}; heapSort(array); System.out.println(Arrays.toString(array)); } } ``` 上述代码实现了堆排序。首先,它使用构建最大堆的函数 `heapify` 将输入数组构建为最大堆。然后,在每次循环中,将堆顶元素(即最大值)与当前数组末尾元素交换,然后对剩余元素重新构建最大堆。通过这样的迭代过程,最终得到一个有序的数组。 在主函数中,我创建了一个测试数组并调用堆排序函数 `heapSort`。最后,通过使用 `Arrays.toString()` 函数将排序结果打印出来。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值