堆排序的简单实现

Posted zhangoliver

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆排序的简单实现相关的知识,希望对你有一定的参考价值。

堆排序是排序的一种,一般有大根对和小根堆之说,大根对,根节点的值比左右子树的根节点的值要大。建堆我们一般是一个完全二叉树。堆排序一般面向数据量比较大的时候,数据量比较小的时候,不适合使用堆排序,比如有种情况就是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     }

 

以上是关于堆排序的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

C++实现各种选择排序(简单选择排序,堆排序)

Java排序算法 - 堆排序的代码

算堆排序

Java实现常见排序--希尔排序快排序堆排序归并排序等Java实现代码

堆排序的简单实现

算法排序之堆排序