JAVA数据结构——堆

JAVA数据结构

Java数据结构(3)——堆


1.堆

堆是一颗具有特殊属性的二叉树,它具有以下两个特点:堆是一棵完全二叉树;堆的每个结点都大于或等于它的任意一个孩子。堆作为一棵二叉树,除了最后一层可以无需填满,插入元素都是按照偏左放置的。与二叉查找树不同,堆一般是通过数组线性表来实现。

1.1数组线性表

由于堆是依靠数组线性表实现的,因此在实现堆之前,我们首先要构造数组线性表。当然通过java集合框架可以直接实现数组线性表,但本文为了明确数组线性表在堆中的具体作用,将自行构造数组线性表。代码如下所示:

class arraylist<E>{
    public static final int CAPACITY=8;//初始数组容量
    public int size=0;//初始数组内元素数量
    private E[] objects=(E[])new Object[CAPACITY];
    /*构造函数*/
    public arraylist(){}
    public arraylist(E[] objects){
      for(int i=0;i<objects.length;i++)
        add(objects[i]);    
    }
    /*添加元素*/
    public void add(E e){
        add(size,e);
    }
    /*指定下标添加元素*/
    public void add(int index,E e){
        ensureCapacity();
        if(index<size){
            for(int i=size-1;i>=index;i--)
              objects[i+1]=objects[i];
            objects[index]=e;
        }else objects[index]=e;
        size++;
    }
    /*通过下标获取元素*/
    public E get(int index){
        return objects[index];
    }
    /*判断数组大小是否能够容纳新元素*/
    private void ensureCapacity(){
         if(size>=objects.length){
       E[] newData=(E[])(new Object[size*2+1]);
       System.arraycopy(objects,0,newData,0,size);
       objects=newData;
       }
    }
    /*获得指定下标元素值*/
    public E get(int index){
        return objects[index];
    }
    /*移除尾端元素*/
    public E removeLast(){
        E e=objects[size-1];
        objects[size-1]=null;
        size--;
        return e;
    }
    /*给定下标元素赋新值*/
    public void copy(int index, E e){
        objects[index]=e;
    }
}
1.2堆的实现

如果堆的大小是提前知道的,那么可以将堆存储在一个数组线性表中,并且其各个元素下标具有相应关系。假设某一结点在数组线性表中的下标为n,则具体关系如下:

  • 该结点的左孩子下标为2n+1
  • 该结点的右孩子下标为2n+2
  • 该结点的父结点下标为(n-1)/2

我们利用上述关系,可以实现堆的添加、删除、排序等方法,具体方法命名和功能如下表所示,后文将依次实现这些方法。具体代码如下所示。

方法名功能
public heap()构造函数
public heap(E[] objects)通过数组构造堆
public void insert(E e)插入元素
private void insert(int index, E e)辅助插入元素,在指定下表赋值
public E remove()删除根结点
class heap<E extends Comparable<E>>{
    arraylist<E> list=new arraylist<E>();//创建数组线性表

    public heap(){}
    public heap(E[] objects){
        for(int i=0;i<objects.length;i++)
          insert(objects[i]);
    }
    /*以下函数方法后文将一一给出*/
    public void insert(E e){}
    private void insert(int index, E e){}
    public E remove(){}
    public void heapSort(){}
}
1.2.1插入元素

为了给堆添加一个新结点,首先将它添加到堆的末尾,然后不断比较新结点与其父结点的大小。若新结点小于父结点,则新结点的位置即是堆的末尾。若新结点的值大于父结点,则将父结点的值赋值给尾端结点,并比较父结点的父结点的值与插入元素的大小。按照这种方式不断向上级比较,直到找到比新结点的值大的父结点,就可以确定新插入元素的具体下标了。具体实现代码如下所示。

public void insert(E e){
  if(list.size==0)//若线性数组表为空,则插入新元素作为根结点
    list.add(e);
  else
    insert(list.size,e);
}
/*辅助插入元素*/
private void insert(int index, E e){
  list.add(index,e);//把新结点添加到堆的末尾
  /*寻找新元素的具体位置*/
    while(index>0){
      int indexOfparent=(index-1)/2;//当前位置的父结点下标
      E parent=list.get(indexOfparent);//获得当前位置的父结点的值
      if(e.compareTo(parent)>0){//比较当前结点与父结点值的大小
        list.copy(index,parent);
        index=indexOfparent;
        }else break;
      }
    list.copy(index,e);//在查找到的具体下标位置赋值新插入元素的值
}       
1.2.2删除根结点

我们有时需要从堆中删除最大元素,也就是这个堆的根结点。删除这个根结点后,就必须要重建这课树来保持堆的特征。删除根结点后堆的重建过程按照如下几步进行:

  • 第一步:删除根结点,并将尾结点的值赋给根结点,并设为当前结点
  • 第二步:比较当前结点的左右两个子孩子的值,选取较大的子孩子与当前结点交换位置,交换后当前结点的下标为其原来的较大的孩子结点的下标。此时分为两种情况:
    • 情况1:当前结点仅存在左孩子。此时当前结点需要与其左孩子比较值的大小,若小于左孩子,则交换两者位置。
    • 情况2:当前结点存在左右两个孩子结点,此时具体步骤同第二步一样。
  • 第三步:循环第二步,直到当前结点为叶子结点或当前结点仅存在一个左孩子并且其值大于其左孩子结点的情况。

具体实现算法如下所示:

public E remove(){
    if(list.size==0) return null;    

    E root=list.get(0);//获得头结点元素值
    E last=list.removeLast();//获得尾结点元素值并删除尾结点
    list.copy(0,last);//将尾结点元素值赋值给头结点
    int index=0;//初始化当前下标
    /*父子结点之间比较并交换循环*/
    while(!isleaf(index)){//判断当前结点是否为叶结点,若为叶节点说明已完成堆的重建
        int leftIndex=2*index+1;//获得当前结点的左叶子结点的下标
        int rightIndex=2*index+2;//获得当前结点右叶子结点下标
        E left=list.get(leftIndex);//获得左叶子值
        E right=list.get(rightIndex);//获得右叶子值
        /*情况1:当前结点仅存在左叶子结点的情况*/
        if(right==null){
            if(left.compareTo(last)>0){//判断当前结点与其左叶子结点的大小,并判断是否需要交换位置
              list.copy(index,left);
              list.copy(leftIndex,last);
              index=leftIndex;
              break;                    
            }else break;//若当前结点大于其左叶子结点则跳出循环,因为此时已完成堆的重建
        }
        /*情况2:当前结点存在左右叶子结点*/
        if((left.compareTo(right)>0)){//比较当前结点左右叶子结点大小,若左叶子值较大则交换当前结点与左叶子
          list.copy(index,left);
          list.copy(leftIndex,last);
          index=leftIndex;
        }else{//若右叶子值较大,则交换右叶子与当前结点
          list.copy(index,right);
          list.copy(rightIndex,last);               
          index=rightIndex;
        }                   
    }
        return root;//返回删除的头结点的值
  }
1.2.3堆排序

通过堆很容易就能实现对堆内元素的排序,前文提到的堆的删除根结点方法中,每次删除当前根结点后堆都要进行重构,每次重构完成时,当前根结点肯定是堆内最大元素。因此我们根据这个性质,不断删除当前堆的根结点,并输出删除结点元素,就可以得到具有从大到小顺序的元素排序。具体算法如下所示:

public class heapTest{
  public static void main(String[] args){
    Integer[] data=new Integer[]{3,5,1,4,7,6,9,8,2};
    System.out.println("heapSort:");
    heapSort(data);
  }
  /*方法中创建heap类*/
  public static <E extends Comparable<E>> void heapSort(E[] list){
      heap<E> heapsort=new heap<E>(list);
      for(int i=0;i<list.length;i++)
        System.out.println(heapsort.remove());
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值