堆排序是排序的一种,一般有大根对和小根堆之说,大根对,根节点的值比左右子树的根节点的值要大。建堆我们一般是一个完全二叉树。堆排序一般面向数据量比较大的时候,数据量比较小的时候,不适合使用堆排序,比如有种情况就是topN算法的实现,一般都是借助于一个大根对来实现,扫描海量数据,把海量数据中的把最大的前N个数据放到堆中。下面实现的时候,为了简单起见,使用的数组存储二叉树,也就是一个顺序树,这个例子不仅是回顾了堆排序的内容,还回顾了二叉树的一些相关操作。
完全二叉树的一些性质,根节点root为1,总的节点的个数为n,则第一个非叶节点是第[n/2]个节点。在顺序书中,节点i的左孩子应当是2*i,右孩子为2*i+1。
堆排序,首先要做的是先建一个初始堆,假设待排序的数据是10个,那么存储二叉树的就是用一个arr[10]的数组
(1)初始堆,这里是大根堆。从[n/2]的位置开始按照大根对的原则开始调整,一直调整到根节点。于是便得到了初始大根堆
(2)大根堆顶端的根节点一定是最大的元素。接下来我们通过n-1次进行排序。i = 0; i < n-1,每次先把root和arr[n-i+1]进行交换,然后把arr[n-i+1]从书上面断开,然后从上往下进行调整。直接看代码。
1 /** 2 * 按照大(小)根堆的规则,从上到下来调整堆 递归实现 3 * @param i 当前子树根节点在顺序树中的数组索引 4 * @param heap 表示堆的二叉树 其实就是一个顺序数组 5 * @param end 标志这个二叉树的最后一个节点的索引位置 6 */ 7 public static void upToDown(int i, int[] heap, int end){ 8 int left = i*2+1; //左孩子的索引 9 int right = left + 1; //右孩子的索引 10 11 if(right <= end)// 左右孩子都不空 12 { 13 int pos = heap[left]>heap[right]?left:right; 14 if(heap[pos] > heap[i]) 15 { 16 int tmp = heap[i]; 17 heap[i] = heap[pos]; 18 heap[pos] = tmp; 19 } 20 upToDown(left, heap, end); 21 upToDown(right, heap, end); 22 }else{ 23 if(left > end) 24 return; 25 else{ 26 if(heap[left] > heap[i]) 27 { 28 int tmp = heap[i]; 29 heap[i] = heap[left]; 30 heap[left] = tmp; 31 } 32 upToDown(left, heap, end); 33 } 34 } 35 }
接着,创建初始堆:
1 /** 2 * 穿件初始堆 3 * @param heap 堆对应的存储数组 4 */ 5 public static void createHeap(int[] heap) { 6 for(int i = heap.length/2-1; i >=0; i--) 7 upToDown(i, heap, heap.length - 1); 8 }
进行堆排序:
1 /* 2 * 使用数组简单的模拟堆排序 3 */ 4 public static void heapSort(int[] heap){ 5 6 for(int i = 0; i < heap.length - 1; i++) 7 { 8 int tmp = heap[0]; 9 int end = heap.length-i-1; 10 heap[0] = heap[end]; 11 heap[end] = tmp; 12 upToDown(0, heap, end-1);14 } 15 }
经过上面的排序,对中的元素就是有序的了,然后按照顺序疏忽堆数组即可。
下面的操作都是和树相关的操作,主要包括求书的节点数,求树的高度,树的递归遍历和非递归遍历。最后是画一个简图作为例子。
(1)求树的叶节点的个数,思路:左子树节点数+右子树节点数+1.
1 /** 2 * 递归求树的高度 3 * @param heap 存储树的数组 4 * @param root 根节点 5 * @return 返回树的高度。 6 */ 7 public static int getNode(int[] heap, int root) 8 {//递归求树的节点个数(左子树节点个数+右子树节点个数+根节点个数) 9 int left = root*2+1; 10 int right = left +1; 11 int end = heap.length - 1; //当前树的最后一个元素的索引 12 13 if(root > end) 14 return 0; 15 if(left>end && right >end) 16 return 1; 17 return getNode(heap, left) + getNode(heap, right) + 1; 18 }
(2)求树的高度,思路:max(左子树的高度,右子树的高度)+1
1 public static int getHeight(int[] heap, int root) 2 {// 左子树 和 右子树中 高度较大的一个 再加1 3 int left = root*2+1; 4 int right = left +1; 5 int end = heap.length - 1; 6 7 8 if(root > end) return 0; 9 10 if(left>end && right >end) 11 return 1; 12 13 return Math.max(getHeight(heap, left), getHeight(heap, right)) + 1; 14 }
(3)树的递归遍历,先根序,中根序,后根序便利都很相似,这里就只写一下先根序递归遍历的代码
1 public static void preTransverse(int[] heap, int i){ 2 int left = i*2+1; 3 int right = left +1; 4 5 if (i > heap.length - 1) 6 return; 7 System.out.print(heap[i]+","); 8 preTransverse(heap, left); 9 preTransverse(heap, right); 10 }
preTransverse(heap, left)和preTransverse(heap, right)的放置的位置,主要决定了是先中后的遍历顺序。
非递归,遍历一个二叉树,这里面就要使用栈数据结构。当然,非递归的遍历,也可以分为先序、中序和后序,思路相同,这里这记录借助栈的非递归先序遍历二叉树。树结构其实就是图的一种特例,而二叉树无疑又是一种更为特别的图,二叉树的先序遍历其实就相当于是图图的深度优先DFS遍历。非递归的二叉树线序遍历的思想很简单:
(1)当前节点root是否为空,不空的话访问之,并将该节点入栈,并取得当前节点的左孩子p = p*2+1,顺序存储二叉树,二叉树的根节点索引为0
(2)若当前访问的节点是一个空节点,那么弹出栈顶元素p = stack[--cn],并获取其右孩子,p = p*2+2。
(3)重复循环(1)(2)知道访问玩所有的节点,或者栈为空
1 /** 2 * 非递归现需便利一个二叉树 使用栈 3 * @param heap 4 */ 5 public static void prePrint(int[] heap) 6 { 7 int height = getHeight(heap, 0); 8 int[] stack = new int[height]; 9 int p = 0; 10 int cn = 0; 11 int end = heap.length - 1; 12 13 while(p<=end || cn!=0){ 14 if(p <= end) 15 { 16 System.out.print(heap[p]+","); 17 stack[cn++] = p; // lft child 18 p = p*2+1; 19 } 20 else{ 21 p = stack[--cn]; 22 p = p*2+2; //right child 23 } 24 } 25 }
借助队列,分层遍历二叉树。思想也很简单:
(1)首先根节点入队
(2)队列出队的时候就访问之,并且把被访问的这个元素的孩子节点一次入队
(3)重复上面两部操作,知道队列为空的时候结束。
1 // 非递归 分层便利 借助于队列 2 public static void broadTranverse(int[] heap) 3 { 4 LinkedList<Integer> que = new LinkedList<Integer>(); 5 int end = heap.length - 1; 6 7 que.offer(0); 8 while(que.isEmpty()==false) 9 { 10 int p = que.poll(); 11 System.out.print(heap[p]+","); 12 int left = p*2+1; 13 int right = left +1; 14 if(left <= end) que.offer(left); 15 if(right <=end) que.offer(right); 16 } 17 }