堆排序思路及非递归Java实现

 

参考左程云的视频

 

 

1.完全二叉树的概念

在了解堆排序的最开始,需要明白什么是完全二叉树

对于这样一棵用编号代表节点的树,若这棵树的节点严格按照图中的顺序填充(不必填满),即称为完全二叉树

也就是说,除了最后一层之外的每一层都被完全填满,而最后一层的所有节点,都需要保持从左到右的顺序

以上面这个图举例:若去掉节点12 13 14 15,该树满足完全二叉树

但是若去掉节点8 其他不动,则不满足

简单的说就是从左到右的排列中,不允许出现 “插队”的节点

 

2.大根堆的概念

首先我们要有一棵完全二叉树,这棵完全二叉树需要满足以下情况:

在该完全二叉树的任意一棵子树中,父节点都是这棵子树的最大值

仍用上图举例(图中的数字代表序号 而不代表实际值)

在1-15号节点的树中 1号节点应是1-15号所有节点的最大值

这棵子树中 2号节点的值也应该是最大值

中,4号节点的值也应该是最大值

其他节点间的大小关系不作任何约束

这样一棵 最大值为父节点的完全二叉树 称为大根堆

相反为小根堆

 

3.由数组来“建立”大根堆

 

现在我们有数组 5 7 0 6 8

 

我们要将它搭成一个大根堆,那么首先要将它变换成一棵完全二叉树

这里有一个非常重要的概念:

这棵完全二叉树并不是真实存在的,它只是我们脑海中的排列方式

我们常见的二叉树是这种形式来表现的:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

而此时我们需要搭建的完全二叉树并不是这样,它的本体仍然是数组,也就是说,树中不同节点需要用数组下标来表示

我们仍然按照下标从0到4的顺序,来填满这棵完全二叉树

假设父节点的下标为 i 

那么它的左子节点为  2*i+1

它的右子节点为  2*i+2

对于某个节点,它的父节点的下标即为  (i-1)/2

 

节点间的对应关系清楚之后 我们发现这样一棵完全二叉树,并不符合大根堆的概念,是因为我们并没有按照规则来填满完全二叉树

正确的步骤是:

第一步:填入第一个数字 5

第二步:填入第二个数字7

此时7>5 不符合大根堆性质 我们将5与7的位置交换

注意:交换实际是发生在数组中的

之后数组变为  7  5  0  6  8

树变为

第三步:填入第三个数字0

不需要交换

第四步:填入第四个数字6

需要将5和6交换 交换前数组为:7  5  0  6  8  交换后数组即为 7 6 0 5 8

最后一步:将8填入

8填入之后需要将6与8交换 交换前数组为:7 6 0 5 8  交换后数组为:7  8  0  5  6

但是交换后发现 7和8也需要交换 其实每一次交换完之后 都需要将换上去的父节点 与这个换上去的父节点的父节点进行一次对比

交换前数组:7 8 0 5 6

交换后数组: 8  7  0  5  6

这样 由数组建立大根堆的步骤就完成了

用代码来实现也非常简单:

传入数组arr 对他的每个元素进行“填入”

for(int i=0;i<arr.length;i++)
{
    heapInsert(arr,i);
}
public static void heapInsert(int[] arr,int index)
{
    //直到当前节点不再大于它的父节点
    while(arr[index]>arr[(index-1)/2])
    {
        swap(arr,index,(index-1)/2);
        index=(index-1)/2;
    }
}

 

4.由大根堆来完成排序

建完大根堆之后的数组为  8  7  0  5  6

树中的形式为:

由于大根堆中父节点最大的性质,此时8一定是数组中最大的元素

由于整棵树的父节点,在数组中的下标一定是0 所以下标为0的这个元素即为数组中的最大值

接下来将这个最大值,与最后一个元素交换

在这个例子中,即将8与6交换

交换后的数组为  6  7  0  5  8

交换后:数组的最后一个数即为数组中的最大元素,此时这个数不再参与接下来的排序

那么如何让最后的元素不再参与接下来的排序?

在排除之前

在生成大根堆时,是如何判断X位置没有元素的?

是因为X位置 在数组中的下标为5 而我们数组实际最大下标只到4 数组越界 所以X位置没有值

那么我们在将8排除后,同样可以设置一个指针,这个指针初始指向数组实际的最大下标 即为4

排除8后,这个指针前移一位  指向3 这个指针就是是否越界的标志 指向3后,相当于8被排除了

此时数为上图

在每一次排除一个元素后,树都应该做一次检查,检查是否符合大根堆的性质,若不符合则进行变换

在上图中,元素6换到下标0位置后,显然小于7 ,不符合大根堆性质

此时应该进行交换,通俗的做法是:

从下标0的节点开始,即从整棵树的父节点开始

若这个父节点有左孩子,那么去看他是否有右孩子,在父节点/左孩子/右孩子 三者中(可能不存在孩子)选择出最大的元素的下标

若这个最大元素的下标就是父节点的下标 说明父节点是最大值 不需要调整

其他情况则交换

交换后继续向下判断是否符合大根堆性质 直到某个元素没有左孩子(没有左孩子就没有右孩子)

本次调整完成

此时7为最大值 与数组最后一个元素5交换 并排除  指针前移一位  指向2 数组为 5 6  0       7  8

树为:

之后将5  6  0进行调整 调整后为 6  5  0

调整完成  交换6与0 排除6  指针前移一位  指向1

此时数组为0 5        6 7  8  此时看似排序已经完成 但是指针下标并没有指向0 所以继续

树为

调整后为 

交换5 与 0 排除 5  此时指针下标指向0 排序完成

 

完整代码

import java.util.List;

public class Main
{

    public static void main(String[] args)
    {
        int[] arr={5,7,0,6,8};
        heapSort(arr);
        for(int i=0;i<arr.length;i++)
        {
            System.out.println(arr[i]);
        }
    }
    public static void heapSort(int[] arr)
    {
        if(arr==null||arr.length<2)
        {
            return;
        }
        for(int i=0;i<arr.length;i++)
        {
            heapInsert(arr,i);
        }
        int size=arr.length;
        swap(arr,0,--size);
        while(size>0)
        {
            heapify(arr,0,size);
            swap(arr,0,--size);
        }
    }

    public static void heapInsert(int[] arr,int index)
    {
        //建立大根堆  每个元素往上走
        while(arr[index]>arr[(index-1)/2])
        {
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }

    public static void heapify(int[] arr,int index,int size)
    {
        int left=index*2+1;
        //有左孩子时
        while(left<size)
        {
            //在左孩子和右孩子中选出较大者
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //返回当前三个节点中最大的下标
            largest=arr[largest]>arr[index] ? largest : index;
            //若最大者是自己 则不用调整
            if(largest==index)
                break;
            //某个孩子大于我 所以交换
            swap(arr,largest,index);
            //每个元素都往下走
            index=largest;
            left=index*2+1;
        }
    }

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章