在排序前需要shift down将无序数组转成堆结构,即父节点数据始终不小于或不大于子节点。用以下代码实现:
//从第一个不是叶子节点的元素开始shift down,将data转为堆结构
for (int i=count/2;i>=1;i--){
shiftDown(i);
}
注:从一个最大堆中取出一个元素(只能取出最大优先级的元素,也就是根节点元素),然后将最后一位元素移至堆顶,为保持堆结构将其下移至合适位置的操作称为 shift down
方法一:
开辟额外的空间进行构造堆和对堆进行排序
方法二:
对于一个最大堆,首先将开始位置数据和数组末尾数值进行交换,那么数组末尾就是最大元素,然后再对堆顶元素进行 shift down 操作,重新生成最大堆,然后将新生成的最大数和整个数组倒数第二位置进行交换,此时倒数第二位置就是倒数第二大数据,这个过程以此类推。
两个方法的完整代码如下:
public class HeapSort<T extends Comparable> {
private T[] data;//堆结构数据
private int count;//堆中的元素个数
public HeapSort(T[] arr){
data = (T[]) new Comparable[arr.length+1];
count = arr.length;
//将arr赋值给data
for( int i = 0 ; i < arr.length; i ++ ){
data[i+1] = arr[i];
}
//从第一个不是叶子节点的元素开始shift down,将data转为堆结构
for (int i=count/2;i>=1;i--){
shiftDown(i);
}
}
//从一个最大堆中取出一个元素(只能取出最大优先级的元素,也就是根节点元素),
// 然后将最后一位元素移至堆顶,为保持堆结构将其下移至合适位置的操作称为 shift down
public void shiftDown(int index){
if(index>=1 && count>0){
// swap(data,count,index);
// count--;
int k=index;
while ( k*2<=count){
//右子节点不为空且大于左边子节点,则和k位置元素比较交换
if (2*k+1<=count && data[2*k].compareTo(data[2*k+1])<0){
if (data[2*k+1].compareTo(data[k])>0){
swap(data,2*k+1,k);
k=2*k+1;
}else {
break;
}
}else {
if (data[2*k].compareTo(data[k])>0){
swap(data,2*k,k);
k=2*k;
}else {
break;
}
}
}
}
//print(data);
}
public void swap(T[] arr,int i,int j){
T tem=arr[i];
arr[i]=arr[j];
arr[j]=tem;
}
//取出最大值并将最后一个元素移至堆顶的操作
public T extractMax(){
T max=data[1];
data[1]=data[count];
data[count]=null;
count--;
return max;
}
public void sort(){
int n=count;//记录堆中所有元素个数
System.out.println("n = " + n);
print(data);
while (count>=1){
swap(data, count,1);
count--;
shiftDown(1);
}
count=n;
print(data);
}
// 测试 MaxHeap
public static void main(String[] args) {
//对无序数组arr进行堆排序
Integer[] oldArr=new Integer[]{null,15,17,19,13,22,16,28,30,41,62};
//对arr中元素为null的处理
Integer[] arr= Arrays.stream(oldArr).filter(i -> i!=null).toArray(Integer[]::new);
//通过构造函数获得arr的堆结构数组data
HeapSort<Integer> heapSort = new HeapSort<Integer>(arr);
//方法一、这是利用额外的空间进行构造堆和对堆进行排序
/* print(arr);
for (int i=0;i<arr.length;i++){
//将堆的最大值取出赋给arr
arr[i]= heapSort.extractMax();
//取出最大值后进行shift down
heapSort.shiftDown(1);
System.out.println("arr[i] = " + arr[i]);
}
print(arr);*/
//方法二、对于一个最大堆,首先将开始位置数据和数组末尾数值进行交换,那么数组末尾就是最大元素,
// 然后再对堆顶元素进行 shift down 操作,重新生成最大堆,
// 然后将新生成的最大数和整个数组倒数第二位置进行交换,此时倒数第二位置就是倒数第二大数据,这个过程以此类推。
heapSort.sort();
}
public void print(T[] arr) {
for (T n : arr) {
System.out.print(n + " ");
}
System.out.println(" ");
}
}
使用索引堆:
一、概念及其介绍
索引堆是对堆这个数据结构的优化。
索引堆使用了一个新的 int 类型的数组,用于存放索引信息。
相较于堆,优点如下:
- 优化了交换元素的消耗。
- 加入的数据位置固定,方便寻找。
二、适用说明
如果堆中存储的元素较大,那么进行交换就要消耗大量的时间,这个时候可以用索引堆的数据结构进行替代,堆中存储的是数组的索引,我们相应操作的是索引。
使用索引堆完成两个方法的完整代码:
public class HeapSort<T extends Comparable> {
private T[] data;//堆结构数据
private int[] indexes;//索引堆
private int count;//堆中的元素个数
public HeapSort(T[] arr){
data = (T[]) new Comparable[arr.length+1];
indexes=new int[arr.length+1];
count = arr.length;
//将arr赋值给data,并给索引堆赋予data的数据索引
for( int i = 0 ; i < arr.length; i ++ ){
data[i+1] = arr[i];
indexes[i+1]=i+1;
}
//从第一个不是叶子节点的元素开始shift down,将data转为堆结构,但操作的是indexes堆,data实际上未变
for (int i=count/2;i>=1;i--){
shiftDown(i);
}
}
//从一个最大堆中取出一个元素(只能取出最大优先级的元素,也就是根节点元素),
// 然后将最后一位元素移至堆顶,为保持堆结构将其下移至合适位置的操作称为 shift down
public void shiftDown(int index){
if(index>=1 && count>0){
int k=index;
while ( k*2<=count){
//右子节点不为空且大于左边子节点,则和k位置元素比较交换
if (2*k+1<=count && data[indexes[2*k]].compareTo(data[indexes[2*k+1]])<0){
if (data[indexes[2*k+1]].compareTo(indexes[k])>0){
swap(indexes,2*k+1,k);
k=2*k+1;
}else {
break;
}
}else {
if (data[indexes[2*k]].compareTo(data[indexes[k]])>0){
swap(indexes,2*k,k);
k=2*k;
}else {
break;
}
}
}
}
//print(data);
}
public void swap(T[] arr,int i,int j){
T tem=arr[i];
arr[i]=arr[j];
arr[j]=tem;
}
public void swap(int[] arr,int i,int j){
int tem=arr[i];
arr[i]=arr[j];
arr[j]=tem;
}
//取出最大值并将最后一个元素移至堆顶的操作
public T extractMax(){
int max=indexes[1];
indexes[1]=indexes[count];
indexes[count] = -1;
count--;
return data[max];
}
public void sort(){
int n=count;//记录堆中所有元素个数
print(data,indexes);
while (count>=1){
swap(indexes, count,1);
count--;
shiftDown(1);
}
count=n;
print(data,indexes);
}
// 测试 MaxHeap
public static void main(String[] args) {
//对无序数组arr进行堆排序
Integer[] oldArr=new Integer[]{null,15,19,13,62,22,16,28,30,41,17};
//对arr中元素为null的处理
Integer[] arr= Arrays.stream(oldArr).filter(i -> i!=null).toArray(Integer[]::new);
//通过构造函数获得arr的堆结构数组data
HeapSort<Integer> heapSort = new HeapSort<Integer>(arr);
//方法一、这是利用额外的空间进行构造堆和对堆进行排序
/* heapSort.print(arr);
for (int i=0;i<arr.length;i++){
//将堆的最大值取出赋给arr
arr[i]=heapSort.extractMax();
//取出最大值后进行shift down
heapSort.shiftDown(1);
}
heapSort.print(arr);*/
//方法二、对于一个最大堆,首先将开始位置数据和数组末尾数值进行交换,那么数组末尾就是最大元素,
// 然后再对堆顶元素进行 shift down 操作,重新生成最大堆,
// 然后将新生成的最大数和整个数组倒数第二位置进行交换,此时倒数第二位置就是倒数第二大数据,这个过程以此类推。
heapSort.sort();
}
public void print(T[] arr,int[] indexes) {
for (int n : indexes) {
System.out.print(arr[n] + " ");
}
System.out.println(" ");
}
public void print(T[] arr) {
for (T n : arr) {
System.out.print(n + " ");
}
System.out.println(" ");
}
}