堆、堆排序及Java实现

本文参考自《Java数据结构和算法》

动态理解堆的操作过程:http://visualgo.net/heap

一、堆

       1、什么是堆?

              堆是有如下特点的二叉树:

               1)它是完全二叉树。即除了树的最后一层节点不需要是满的,其他的每层从左到右完全是满的。

               2)它常常是数组实现的。

               3)堆中的每个节点都必须满足堆的条件。

                      每个节点的关键字都不大于其子节点的关键字,这种堆称为小根堆。

                      每个节点的关键字都不小于其子节点的关键字,这种堆称为大堆根。本文采用此结构演示

               综上所述,堆是一棵顺序存储的按照特定规则的完全二叉树

               

              数组的节点的索引为index,则:

              1)它的父节点的下标为:(index-1)/2

               2)它的左子节点的下表为:2*index + 1

               3)它的左子节点的下表为:2*index + 2

        2、堆与二叉搜索树的不同:

             堆和二叉搜索树相比是弱序的。在二叉搜索树中,所有节点的关键字大于其左子树的关键字,小于其右子树的关键字。但是在堆中,每个节点都不小于其子节点,即从 根到叶子节点的每条路径都是降序排列的。

         3、插入操作

             1)将新增节点插入到数组最后第一个空着的单元中,数组容量加一。

             2)向上筛选:与父节点比较,如果大于父节点的关键字,则与父节点交换。依次重复上述动作,直到父节点的关键字大于目标节点为止。

              

             

           

          如上图:在11处插入新节点38,与父节点26比较,38>26,两者交换;38继续与父节点36比较,38>36,两者交换;38继续与父节点90比较,38<90,插入结束。

           注意:

            

            

            换位1中每次换位需要3次复制,两次换位需要6次复制;而换位2中先将目标节点暂存,父节点覆盖子节点,最后暂存的节点覆盖空余的节点,一共4次复制。

           故需要在向上筛选过程中采用第二种换位操作。

           具体代码如下:

	//插入操作
	public boolean insert(int value) {
		if (currentSize == maxSize) 
			return false;
		
		HeapNode node = new HeapNode(value);
		heapArray[currentSize] = node;
		trickleUp(currentSize++);
		return true;
	} 
	
        //向上筛选
	private void trickleUp(int index) {
		HeapNode tmpNode = heapArray[index];
		//得到目标节点父节点的索引值
		int parentIndex = (index - 1)/2;
		while (parentIndex >= 0 && index > 0) {
			//缓存节点的值大于父节点的值,才会进行后续操作,否则直接跳出
			if (tmpNode.getValue() < heapArray[parentIndex].getValue()) 
				break;
			
			heapArray[index] = heapArray[parentIndex];
			index = parentIndex;
			parentIndex = (index - 1)/2;
		}
		heapArray[index] = tmpNode;
	}

         4、删除操作

          1)移走根节点。

          2)把最后一个节点移动到根的位置上。

          3)一直向下筛选这个节点,在两个孩子中找到最大值,如果小于最大值,两者交换,直到大于它的子节点小于它的父节点为止。

           

         

        

       删除代码如下:

public HeapNode remove() {
		HeapNode root = heapArray[0];
		heapArray[0] = heapArray[--currentSize];
		trickleDown(0);
		return root;
	}
	
//向下筛选
	private void trickleDown(int index) {
		if (index >= currentSize || index < 0) 
			return ;
		
		HeapNode tmpNode = heapArray[index];
		while (index < currentSize/2) {
			int leftChildIndex = 2*index + 1;
			int rightChildIndex = leftChildIndex + 1;
			
			//在左右子数中找到最大的那个
			int largeChildIndex = 0;
			if (rightChildIndex < currentSize && heapArray[leftChildIndex].getValue() < heapArray[rightChildIndex].getValue()) {
				largeChildIndex = rightChildIndex;
			} else {
				largeChildIndex = leftChildIndex;
			}
			
			//只有目标节点比孩子节点小才满足交换,否则直接跳出
			if (tmpNode.getValue() > heapArray[largeChildIndex].getValue()) {
				break;
			} else {
				heapArray[index] = heapArray[largeChildIndex];
				index = largeChildIndex;
			}
		}
		heapArray[index] = tmpNode;
	}
          5、整体代码如下:           
public class BinaryHeapDemo {
	public static void main(String[] args) {
		BinaryHeap bh = new BinaryHeap(50);
		bh.insert(2);
		bh.insert(7);
		bh.insert(26);
		bh.insert(25);
		bh.insert(19);
		bh.insert(17);
		bh.insert(1);
		bh.insert(90);
		bh.insert(3);
		bh.insert(36);
		bh.display();
		bh.remove();
		bh.display();
	}
}

class HeapNode {
	private int value;
	
	HeapNode (int value) {
		this.value = value;
	}
	public int getValue() {
		return value;
	}
	
	public void setValue(int value) {
		this.value = value;
	}
}

class BinaryHeap {
	private HeapNode[] heapArray = null;
	int maxSize;
	int currentSize;
	
	BinaryHeap(int maxSize) {
		this.maxSize = maxSize;
		heapArray = new HeapNode[this.maxSize];
		currentSize = 0;
	}
	
	public boolean insert(int value) {
		if (currentSize == maxSize) 
			return false;
		
		HeapNode node = new HeapNode(value);
		heapArray[currentSize] = node;
		trickleUp(currentSize++);
		return true;
	} 
	
    //向上筛选
	private void trickleUp(int index) {
		HeapNode tmpNode = heapArray[index];
		//得到目标节点父节点的索引值
		int parentIndex = (index - 1)/2;
		while (parentIndex >= 0 && index > 0) {
			//缓存节点的值大于父节点的值,才会进行后续操作,否则直接跳出
			if (tmpNode.getValue() < heapArray[parentIndex].getValue()) 
				break;
			
			heapArray[index] = heapArray[parentIndex];
			index = parentIndex;
			parentIndex = (index - 1)/2;
		}
		heapArray[index] = tmpNode;
	}
	
	public HeapNode remove() {
		HeapNode root = heapArray[0];
		heapArray[0] = heapArray[--currentSize];
		trickleDown(0);
		return root;
	}
	
	//向下筛选
	private void trickleDown(int index) {
		if (index >= currentSize || index < 0) 
			return ;
		
		HeapNode tmpNode = heapArray[index];
		while (index < currentSize/2) {
			int leftChildIndex = 2*index + 1;
			int rightChildIndex = leftChildIndex + 1;
			
			//在左右子数中找到最大的那个
			int largeChildIndex = 0;
			if (rightChildIndex < currentSize && heapArray[leftChildIndex].getValue() < heapArray[rightChildIndex].getValue()) {
				largeChildIndex = rightChildIndex;
			} else {
				largeChildIndex = leftChildIndex;
			}
			
			//只有目标节点比孩子节点小才满足交换,否则直接跳出
			if (tmpNode.getValue() > heapArray[largeChildIndex].getValue()) {
				break;
			} else {
				heapArray[index] = heapArray[largeChildIndex];
				index = largeChildIndex;
			}
		}
		heapArray[index] = tmpNode;
	}
	
	
	public void display() {
		System.out.print("heapArray:");
		for (int i = 0; i < currentSize; ++i) {
			System.out.print(heapArray[i].getValue() + " ");
		}
		System.out.println();
		System.out.println("========================================");
		
		int nBlanks = 32;
		int itemsPerRow = 1;
		int column = 0;
		int j = 0;
		while (currentSize > 0) {
			if (column == 0) {
				for (int i = 0; i < nBlanks; ++i) {
					System.out.print(" ");
				}
			}
			System.out.print(heapArray[j].getValue());
			if (++j == currentSize) {
				break;
			}
			
			if (++column == itemsPerRow) {
				nBlanks /= 2;
				itemsPerRow *= 2;
				column = 0;
				System.out.println();
			} else {
				for (int i =0; i < nBlanks*2-2; ++i) {
					System.out.print(" ");
				}
			}
		}
		System.out.println("\n========================================");
	}
}
测试结果如下

heapArray:90 36 17 25 26 7 1 2 3 19 
========================================
                                90
                36                              17
        25              26              7              1
    2      3      19
========================================
heapArray:36 26 17 25 19 7 1 2 3 
========================================
                                36
                26                              17
        25              19              7              1
    2      3
========================================

二、堆排序

        对一个无序数组,堆排序的思想就是使用insert()在堆中插入全部无序的数据项,然后重复的使用remove(),直到堆内无数据。

for (int i = 0; i < currentSize; ++i) {
    insert(a[i]);
}
for (int i = 0; i < currentSize; ++i) {
    HeapNode node = remove();
    node.getValue();
}
      但可以通过 两点来优化堆排序:

      1、向下筛选到适合的位置

       从一个无序的数组建堆的过程就是反复使用trickleUp()函数N次,但是此过程可以使用trickleDown()函数N/2次即可完成。

      

       堆中存在诸多叶子节点,这些叶子节点因为没有子节点所以没有违背堆的条件(大于子节点的关键字),因此不用对这些节点进行trickleDown()操作。可以从最后一个有子节点的节点开始依次向下筛选,进行trickleDown()操作直到根节点位置。那最后一个有子节点的节点索引是多少呢?若最后一个叶子节点的索引为index,则父节点便是最后一个有子节点的节点,索引为(index-1)/2=(currentSize-1-1)/2=currentSize/2-1

       从一个无序数组建堆的代码如下:

//将一个无序数组建堆就是从最后一个有子节点的节点开始,依次向下筛选
	public void buildHeap() {
		for (int i = currentSize/2 - 1; i >= 0; --i) {
			trickleDown(i);
		}
	}
      2、使用同一个数组

          堆排序的过程需要两个大小为N的数组:初始数组和用于堆的数组。事实上堆和初始数组可以用同一个数组。当执行remove()操作后,堆内的节点数减一,数组最后的位置便空了出来,这时此位置可以存储移除的节点。

       

      代码如下:

	private void insertAt(int index, HeapNode node) {
		if (index < 0 || index >= maxSize)
			return ;
		
		heapArray[index] = node;
	}
	
	public void heapSort() {
		for (int i = currentSize-1; i > 0; --i) {
			HeapNode node = remove();
			insertAt(i, node);
		}
	}

       堆排序完整代码如下:

public class HeapSortDemo {
	public static void main(String[] args) {
		HeapSort hs = new HeapSort(30);
		hs.buildRandomArray();
		hs.display();
		hs.buildHeap();
		hs.display();
		hs.heapSort();
		hs.displayArray();
	}
}

class HeapSort {
	private HeapNode[] heapArray = null;
	private int maxSize;
	private int currentSize;
	
	HeapSort(int maxSize) {
		this.maxSize = maxSize;
		heapArray = new HeapNode[this.maxSize];
		currentSize = 0;
	}
	
	private HeapNode remove() {
		HeapNode root = heapArray[0];
		heapArray[0] = heapArray[--currentSize];
		trickleDown(0);
		return root;
	}
	
	private void trickleDown(int index) {
		if (index >= currentSize || index < 0) 
			return ;
		
		HeapNode tmpNode = heapArray[index];
		while (index < currentSize/2) {
			int leftChildIndex = 2*index + 1;
			int rightChildIndex = leftChildIndex + 1;
			
			//在左右子数中找到最大的那个
			int largeChildIndex = 0;
			if (rightChildIndex < currentSize && heapArray[leftChildIndex].getValue() < heapArray[rightChildIndex].getValue()) {
				largeChildIndex = rightChildIndex;
			} else {
				largeChildIndex = leftChildIndex;
			}
			
			//只有目标节点比孩子节点小才满足交换,否则直接跳出
			if (tmpNode.getValue() > heapArray[largeChildIndex].getValue())
				break;
			
			heapArray[index] = heapArray[largeChildIndex];
			index = largeChildIndex;
		}
		heapArray[index] = tmpNode;
	}
	
	//将一个无序数组建堆就是从最后一个有子节点的节点开始,依次向下筛选
	public void buildHeap() {
		for (int i = currentSize/2 - 1; i >= 0; --i) {
			trickleDown(i);
		}
	}
	
	private void insertAt(int index, HeapNode node) {
		if (index < 0 || index >= maxSize)
			return ;
		
		heapArray[index] = node;
	}
	
	//生成一个无序的数组
	public void buildRandomArray() {
		for (int i = 0; i < maxSize; ++i) {
			int randomValue = (int)(Math.random()*100);
			HeapNode node = new HeapNode(randomValue);
			insertAt(currentSize++, node);
		}
	}
	
	public void heapSort() {
		for (int i = currentSize-1; i > 0; --i) {
			HeapNode node = remove();
			insertAt(i, node);
//			System.out.println("insert:" + i + "-" + heapArray[i].getValue());
		}
	}
	
	public void displayArray() {
		System.out.print("heapArray:");
		for (int i = 0; i < maxSize; ++i) {
			System.out.print(heapArray[i].getValue() + " ");
		}
		System.out.println();
	}
	
	public void display() {
		displayArray();
		System.out.println("========================================");
		
		int nBlanks = 32;
		int itemsPerRow = 1;
		int column = 0;
		int j = 0;
		while (currentSize > 0) {
			if (column == 0) {
				for (int i = 0; i < nBlanks; ++i) {
					System.out.print(" ");
				}
			}
			System.out.print(heapArray[j].getValue());
			if (++j == currentSize) {
				break;
			}
			
			if (++column == itemsPerRow) {
				nBlanks /= 2;
				itemsPerRow *= 2;
				column = 0;
				System.out.println();
			} else {
				for (int i =0; i < nBlanks*2-2; ++i) {
					System.out.print(" ");
				}
			}
		}
		System.out.println("\n========================================");
	}
}
     测试结果如下:

    heapArray:9 25 40 86 74 14 44 82 68 49 55 68 11 4 51 63 79 85 1 72 29 64 95 45 38 57 77 57 92 24 
========================================
                                9
                25                              40
        86              74              14              44
    82      68      49      55      68      11      4      51
  63  79  85  1  72  29  64  95  45  38  57  77  57  92  24
========================================
heapArray:95 86 92 85 74 77 57 82 68 72 64 68 57 44 51 63 79 9 1 49 29 25 55 45 38 14 11 40 4 24 
========================================
                                95
                86                              92
        85              74              77              57
    82      68      72      64      68      57      44      51
  63  79  9  1  49  29  25  55  45  38  14  11  40  4  24
========================================
heapArray:1 4 9 11 14 24 25 29 38 40 44 45 49 51 55 57 57 63 64 68 68 72 74 77 79 82 85 86 92 95 
三、效率
堆排序在最好与最快的情况下时间复杂度都是O(nlogn),空间复杂度为O(1)

堆排序是一种不稳定的排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值