堆的四种操作+堆排序

1,定义+图解

直接推荐一篇讲解比较好的博客吧:彻底弄懂最大堆的四种操作(图解+程序)(JAVA)

堆排序算法分析:

  • 时间复杂度:平均情况 O(nlogn);最好情况O(nlogn);最坏情况O(nlogn);
  • 空间复杂度:O(1)。
  • 稳定性:不稳定。

2,堆的插入、删除、初始化、堆排序源码

package manduner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author Manduner_TJU
* @version 创建时间:2019年4月9日下午2:47:46
*/
public class 堆 {
	public static void main(String[] args) {
		List<Integer> list = new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));
		//初始化最大堆
//		System.out.println("构造最大堆如下:");
//		List<Integer> heap1 = initialHeap1(list);
//		print(heap1);
		
//		System.out.println("排序后如下:");
//		heapSort(heap1);
//		print(heap1);
//		
//		System.out.println("删除index位置上的元素:");
//		delete(heap1,3);
//		System.out.println("删除操作后的堆:");
//		print(heap1);
//		System.out.println("删除操作后进行排序的堆:");
//		heapSort(heap1);
//		print(heap1);
		
		
		initialHeap2(list);
		print(list);
//		initialHeap3(list);
//		print(list);
		heapSort(list);
		System.out.println("排序后如下:");
		print(list);
	}
	
	//1,最大堆插入操作
	public static void insert(List<Integer> heap, int value) {
		//在数组的尾部添加
		if(heap.size()==0) heap.add(0);//数组下标为0的位置不放元素
		heap.add(value);
		//开始上升操作
		heapUp(heap,heap.size()-1);
	}
	
	//1.1,上升操作,让插入的数和父节点的数值比较,当大于父节点的时候就和父节点的值交换
	public static void heapUp(List<Integer> heap, int index) {
		//注意,由于数值是从下标为1开始,当index=1的时候,已经是根节点了
		if(index>1) {
			//求出父节点索引值
			int parent = index/2;
			//获取相应位置的数值
			int parentValue = heap.get(parent);
			int indexValue = heap.get(index);
			//如果父亲节点比index的数值小,就交换二者的数值
			if(parentValue < indexValue) {
				//交换数值
				swap(heap,parent,index);
				//递归调用
				heapUp(heap,parent);
			}
			
		}
		
	}
	
	//1.2,交换操作:把堆中的a,b位置的值互换
	public static void swap(List<Integer> heap, int a, int b) {
		int temp = heap.get(a);
		heap.set(a, heap.get(b));
		heap.set(b, temp);
	}
	
	//2,最大堆的删除操作
	 /** 
     * 删除堆中位置是index处的节点 
     * 操作原理是:当删除节点的数值时,原来的位置就会出现一个孔 
     * 填充这个孔的方法就是,把最后的叶子的值赋给该孔,然后进行下沉操作(特殊情况需要上浮操作),最后把该叶子删除
     * 特殊情况: 删除堆10,9,3,8,5,1,2,7,6中的1节点的时候需要进行上浮操作
     * 只允许删除堆顶元素的时候,只需要执行下沉操作就行,不需要考虑特殊情况
     * @param heap  
     */   
	public static void delete(List<Integer> heap, int index) {
		if(index>heap.size()-1) return;
		//把最后的一个叶子节点的数值复制给index位置
		heap.set(index, heap.get(heap.size()-1));
		//如果删除的不是堆顶元素,需要判断是否需要执行上浮操作
		if(index > 1 && heap.get(index) > heap.get(index/2)) {
			heapUp(heap,index);
		}else {
			//下沉操作
			//heapDown(heap,index);
			heapDown2(heap,index,heap.size()-1);
		}
		//把最后一个位置的数字删除
		heap.remove(heap.size()-1);
	}
	
	//2.1, 下沉操作
    /** 
     * 递归实现 
     * 删除堆中一个数据的时候,根据堆的性质,应该把相应的位置下移,才能保持住堆性质不变。
     * @param heap 保持堆元素的数组 
     * @param index 待下沉的节点位置或者被删除的那个节点的位置 
     */   
	public static void heapDown(List<Integer> heap, int index) {
		int n = heap.size()-1;
		
		//记录最大的那个儿子节点的位置
		int child=-1;
		
		//2*index>n说明该节点没有左右儿子节点了,那么就返回
		if(2*index > n) {
			return;
		}//如果左右儿子都存在(2*index+1<n时,2*index一定是该节点的左孩子) 
		else if(2*index+1 < n) {
			//定义左儿子节点
			child = 2*index;
			//如果左儿子小于右儿子的数值,取右儿子的下标
			if(heap.get(child) < heap.get(child+1)) child++;
		}//如果只有一个儿子(左儿子节点)
		else if(2*index==n) {
			child = 2*index;
		}
		
		if(heap.get(child) > heap.get(index)) {
			//交换推中的child和index位置的值
			swap(heap,child,index);
			
			//完成交换后递归调用,继续下降
			heapDown(heap,child);
		}
		
	}
	
	//2.1.2 非递归下沉方法(可以处理部分节点的办法,应用于堆排序),很巧妙(其实还是递归的思想:将一个节点一直下沉到无法下沉为止)
	public static void heapDown2(List<Integer> heap, int i, int n) {
		int child;
		while(i<=n/2) {
			child = i*2;
			//使child指向值较大的孩子
			if(child+1<=n && heap.get(child)<heap.get(child+1)) {
				child+=1;
			}
			if(heap.get(i)<heap.get(child)) {
				swap(heap,i,child);
				//交换后,以child为根的子树不一定满足堆定义,所以从child处开始调整
				i =child;
			}else break;
			
		}
	}
	
	
	//3, 初始化操作(根据给定序列,构造最大堆的过程)
	/**方法1:插入法: 
	  *从空堆开始,依次插入每一个结点,直到所有的结点全部插入到堆为止。 
	  *时间:O(n*log(n)) 
	*/
	public static List<Integer> initialHeap1(List<Integer> list) {
		List<Integer> heap = new ArrayList<Integer>();
		
		if(list.isEmpty()) return heap;
		for(int i=1; i<list.size();i++) {
			insert(heap,list.get(i));
		}
		
		return heap;
	}
	
	/**方法2:下沉法: 
	  *序列对应一个完全二叉树;从最后一个分支结点(n/2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
	  *以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。 
	  *时间:O(n) 
	*/
	public static void initialHeap2(List<Integer> heap) {
		//根据树的性质建堆,树节点前一半一定是分支节点,即有孩子的,所以我们从这里开始调整出初始堆
		if(heap.isEmpty()) return;
		for(int i=heap.size()/2;i>0;i--) {
			heapDown(heap,i);
		}
	}
	
	/**方法3:调整法: 
	  *序列对应一个完全二叉树;从最后一个分支结点(n/2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),
	  *以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。 
	  *时间:O(n) 
	*/
	public static void initialHeap3(List<Integer> heap) {
		for(int i=heap.size()/2;i>0;i--) {
			heapDown2(heap,i,heap.size()-1);
		}
	}

	//4, 堆排序
	/**
	 * (1)将给定序列初始化为一个最大堆
	 * (2)把根节点跟最后一个元素交换位置,调整剩下的n-1个节点,即可排好序
	 */
	//假设heapSort的输入heap已经是一个最大堆
	public static void heapSort(List<Integer> heap) {
		for(int i=heap.size()-1; i>0; i--) {
			swap(heap,1,i);
			heapDown2(heap,1,i-1);
		}
	}


//打印链表   
    public static void print(List<Integer> list) {   
        for (int i = 1; i < list.size(); i++) {   
            System.out.print(list.get(i) + " ");   
        }   
        System.out.println();  
    } 
   
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值