经典排序算法[2]-堆排

经典排序算法[2]-堆排

  介绍堆排序之前,得先介绍什么是堆结构,这个堆和JVM的堆可不是一个堆

1、堆结构

  堆结构就是用数组来展现的完全二叉树,那啥是完全二叉树?

  完全二叉树的定义是:

  • 1)、要么二叉树就是满的,也就是一颗高度为X的二叉树的第N层节点个数是2^(N-1)个
  • 2)、如果二叉树不是满的,只能允许最后一层的节点个数不是2^(N-1),且最后一层的节点需要从左至右排,不能空

完全二叉树如下图所示:

      O                        O
     /  \                     /  \
    o    o  完全二叉树        O    O   非完全二叉树
   /                          \
  o                             O
 

  如何用数组模拟完全二叉树,假设有一个数组[6,5,2,3,4],可以将数组组织成如下结构【[]内部数字为数组索引】:

    
              6[0]
             /    \
           5[1]   2[2]
          /   \
        3[3]  4[4]
    

  归纳一下:

  • 1)、索引为i的元素,其左孩子节点和右孩子节点的索引分别是2*i+1和2*i+2
  • 2)、索引为i的节点其父亲节点的索引为 (i-1)/2 取整

  常用的堆分为大顶堆和小顶堆,大顶堆就是该完全二叉树的每一颗子树的根节点都是该子树数值最大的节点,以上的例子就是一个大顶堆,小顶堆就是根节点是子树最小的节点,如果换成下面的树这样就不是大顶堆【以2为根节点的子树中2并不是元素最大的节点】:

               6[0]
              /    \
            2[1]   5[2]
           /   \
         3[3]  4[4]
     

如何构造大顶堆?

假设用户按如下数据输入到堆中:2,3,4,5,6

首先输入的是2,堆和数组元素如下

          2[0]                  [2]

接下来输入3

     *           2[0]
     *          /
     *        3[1]

可以看出新插入的元素比根节点大,需要和根节点交换位置,而且和容易找到[1]位置的父亲节点是[(1-1)/2=0],且其父亲节点是根节点,已经到0位置,树变成

     *           3[0]                 [3,2]
     *         /
     *       2[1]

接下来插入4,也是一样调整

     *           4[0]                 [4,2,3]
     *         /    \
     *       2[1]    3[2]

插入5

     *           4[0]
     *         /    \
     *       2[1]    3[2]
     *     /
     *   5[3]

可以看出[3]号位置的数据比其父亲[1]号位置的元素大,需要交换

     *           4[0]
     *         /    \
     *       5[1]    3[2]
     *     /
     *   2[3]
     *

但是其父亲节点不是根节点位置,交换后需要继续向上找,可以看到[1]比[0]大,继续交换

     *           5[0]               [5,4,3,2]
     *         /    \
     *       4[1]    3[2]
     *     /
     *   2[3]

已经到根节点,停止向上寻找,继续插入元素6,调整的方法和6类似,最终树变成

     *           6[0]               [6,5,3,2,4]
     *         /    \
     *       5[1]    3[2]
     *     /    \
     *   2[3]    4[4]

伪代码总结一下整个流程:

     *   初始化父节点和当前位置节点
     *   WHILE(当前位置值>父节点值){
     *             
     *       
     *       交换父亲节点和当前位置的元素值
     *       当前位置=父节点
     *       父节点=父节点的父节点
     *
     *   }

如果堆顶元素被弹出,如何重新构造大顶堆?假设有如下大顶堆

     *           6[0]               [6,5,3,4,2]
     *         /    \
     *       5[1]    3[2]
     *     /    \
     *   4[3]    2[4]

现在弹出堆顶6,堆顶就空了,需要补一个元素,默认是将数组最后一个元素补上,如下

     *           2[0]
     *         /    \
     *       5[1]    3[2]
     *     /
     *   4[3]
     *

很明显这个不是个大顶堆,需要进行堆调整,步骤是

  • 1)、找到该节点的孩子节点【左右孩子】,[0]左右孩子索引是[1],[2],找到其中最大的,并和其交换,得到
     *           5[0]
     *         /    \
     *       2[1]    3[2]
     *     /
     *   4[3]
  • 2)、重复以上步骤直到该节点无孩子节点,2[1]只有一个左孩子,且左孩子大,交换
     *           5[0]
     *         /    \
     *       4[1]    3[2]
     *     /
     *   2[3]
  • 3)、重新构成大顶堆

堆结构的代码实现:

public class MyHeap {

    public int[] heap;
    public int heapSize = 0;
    public int capacity = 10;

    public MyHeap(){
        heap = new int[capacity];
    }

    public void put(int e){

        if(heapSize>=capacity){
            System.out.println("堆已满");
            return;
        }

        heapInsert(e);
        heapSize++;
    }

    private void heapInsert(int e) {
        heap[heapSize] = e;
        int curIndex = heapSize;
        int parent = (curIndex-1)/2;
        while(heap[curIndex]>heap[parent]){
            swap(parent,curIndex);
            curIndex = parent;
            parent = (curIndex-1)/2;
        }

    }


    public int pop(){
        if(heapSize==0){
            System.out.println("堆已空");
            return Integer.MIN_VALUE;
        }
        int res = heap[0];

        swap(0,heapSize-1);

        heapSize--;

        heapify(0);


        return res;


    }

    private void heapify(int i) {
        int curIndex = i;
        int leftChild = curIndex*2+1;
        int rightChild = curIndex*2+2;

        while(leftChild<heapSize){

            int largest = leftChild;

            if(rightChild<heapSize && heap[leftChild]<heap[rightChild]){
                largest = rightChild;
            }

            if(heap[curIndex]>=heap[largest]){
                largest = curIndex;
            }

            if(largest==curIndex){
                break;
            }else{
                swap(largest,curIndex);
                curIndex=largest;
                leftChild = curIndex*2+1;
                rightChild = curIndex*2+2;
            }

        }


    }


    public void swap(int index, int i) {

        if(i==index){
            return;
        }else {
            heap[index] = heap[index] ^ heap[i];
            heap[i] = heap[index] ^ heap[i];
            heap[index] = heap[index] ^ heap[i];
        }
    }


    public static void main(String[] args) {
        MyHeap myHeap = new MyHeap();

        for (int i = 0; i < 1 ; i++) {
            myHeap.put(2);
            myHeap.put(3);
            myHeap.put(4);
            myHeap.put(5);
            myHeap.put(6);
        }

        myHeap.swap(3,4);

        int[] data = Arrays.copyOfRange(myHeap.heap, 0, myHeap.heapSize);

        System.out.println(Arrays.toString(data));

        System.out.println("----------------------------");


        myHeap.pop();

        int[] data1 = Arrays.copyOfRange(myHeap.heap, 0, myHeap.heapSize);

        System.out.println(Arrays.toString(data1));

    }

}

2、 堆排序

  堆排序就是借助了大顶堆或者小顶堆的结构,假设现在有一个数组,堆排序的流程是:

  • 1)、先将数组构造成一个大顶堆
  • 2)、将堆顶元素和末尾元素交换,也就是此时最大值会到数组末尾【末尾的元素可以不动了】
  • 3)、进行堆调整,需要调整的数组范围是**[0,length-2]**位置的数据,将其重新调整成大顶堆,重复2和3对应的操作直到只剩下一个元素

堆排序的代码实现:

public class HeapSort {

    public static void heapSort(int[] data,int length){
        if(length<=0){
            return;
        }

        for (int i = 0; i < length; i++) {
            heapInsert(data,i);
        }

        for (int i = length-1; i >=0 ; i--) {

            swap(data,0,i);
            heapify(data,0,i);
        }

    }

    private static void heapify(int[] heap, int i, int heapSize) {

        int curIndex = i;
        int leftChild = curIndex*2+1;
        int rightChild = curIndex*2+2;

        while(leftChild<heapSize){

            int largest = leftChild;

            if(rightChild<heapSize && heap[leftChild]<heap[rightChild]){
                largest = rightChild;
            }

            if(heap[curIndex]>=heap[largest]){
                largest = curIndex;
            }

            if(largest==curIndex){
                break;
            }else{
                swap(heap,largest,curIndex);
                curIndex=largest;
                leftChild = curIndex*2+1;
                rightChild = curIndex*2+2;
            }

        }

    }

    private static void heapInsert(int[] heap, int index) {
        int curIndex = index;
        int parent = (curIndex-1)/2;
        while(heap[curIndex]>heap[parent]){
            swap(heap,parent,curIndex);
            curIndex = parent;
            parent = (curIndex-1)/2;
        }


    }

    public static void main(String[] args) {

        int[] data = {19,15,28,19,100,1,3,102};
        heapSort(data,data.length);
        System.out.println(Arrays.toString(data));

    }



    public static  void swap(int[] heap,int index, int i) {

        if(i==index){
            return;
        }else {
            heap[index] = heap[index] ^ heap[i];
            heap[i] = heap[index] ^ heap[i];
            heap[index] = heap[index] ^ heap[i];
        }
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值