堆排序的简单实现

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

堆排序的简单实现的相关文章

算法导论 第6章 堆排序(简单选择排序、堆排序)

堆数据结构实际上是一种数组对象,是以数组的形式存储的,可是它能够被视为一颗全然二叉树,因此又叫二叉堆.堆分为下面两种类型: 大顶堆:父结点的值不小于其子结点的值,堆顶元素最大 小顶堆:父结点的值不大于其子结点的值,堆顶元素最小 堆排序的时间复杂度跟合并排序一样,都是O(nlgn),可是合并排序不是原地排序(原地排序:在排序过程中,仅仅有常数个元素是保存在数组以外的空间),合并排序的全部元素都被复制到另外的数组空间中去,而堆排序是一个原地排序算法. 1.在堆排序中,我们通常使用大顶堆来实现,因为堆

排序之选择排序:简单选择+堆排序

一.简单选择排序 1.思想:每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置.与冒泡排序相比,移动数据次数少,节省时间 ,性能优于冒泡排序. 2.时间复杂度: 最好:O(N2),正序 最坏:O(N2),逆序 平均:O(N2) 3.辅助空间:O(1) 4.稳定性:不稳定,交换过程中可能打乱顺序 5.适用场合:n小的情况 public static void selectSort(int[] a) { int i,j,min,t; for(j = 0;j < a

算法----堆排序(heap sort)

堆排序是利用堆进行排序的高效算法,其能实现O(NlogN)的排序时间复杂度,具体算法分析可以点击堆排序算法时间复杂度分析. 算法实现: 调整堆: void sort::sink(int* a, const int root, const int end) { int i=root; while(2*i +1 <= end) { int k = 2*i+1; if(k+1<=end && a[k]<a[k+1]) k++; if(a[k] < a[i]) break;

堆排序JAVA实现

package kpp.sort; /** * 堆的定义如下: n个元素的序列{k0,k1,...,ki,…,k(n-1)}当且仅当满足下关系时,称之为堆. " ki<=k2i,ki<=k2i+1;或ki>=k2i,ki>=k2i+1.(i=1,2,…,[n/2])" 若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树, 则完全二叉树中每一个节点的值的都大于或等于任意一个字节的值(如果有的话),称之为大顶堆. 则完全二叉树中每一个

堆排序、胜者树、败者树,孰优孰劣?

在顺序存储结构中,堆排序是一种非常不错的高级选择排序算法,普通情况和最差情况下都可以将时间复杂度控制在O(n * logn). 堆排序可以用在顺序存储结构,是因为完全二叉树的一种独特性质.而这里还要先提一下满二叉树. 啥叫满二叉树?满二叉树是这样一种二叉树,它的每一层都是"满"的,设根部为第0层,则每一层都有2^n个节点.所有节点的度数要么是2,要么是0(叶子). 那完全二叉树呢?我们首先做出如下规定,即对二叉树中的节点,按从根部到叶子.每层从左到右递增的编号:如果某棵树,其所有节点的

排序之堆排序算法实现

前一段时间师姐在看大话数据结构这本书,当看到堆排序是她问我,当时我觉得堆排序很简单,无非就是堆顶和堆尾对换,并输出最后一个,剩下的进行堆调整再一次循环下去.但是她又问道怎么实现堆调整,当时有点炉子,但是当我真正想的时候,却出现了很多问题,正好最近实现排序算法,所以今天就详细说说堆排序的具体. 堆: 堆是一个完全二叉树,分有最大堆和最小堆:它的每个节点值总比每个孩子值都大(这是最大堆,最小堆反之,下面提到得堆均为最大堆),并且最后一层处于左侧. 下面我们说说初始化堆:利用数组实现 如下图所示,我们

算法一之简单选择排序

一.  选择排序的思想 选择排序的基本思想是:每一趟在n-i+1(i=1,2,-n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录.基于此思想的算法主要有简单选择排序.树型选择排序和堆排序. 简单选择排序的基本思想:第1趟,在待排序记录r[1]~r[n]中选出最小的记录,将它与r[1]交换:第2趟,在待排序记录r[2]~r[n]中选出最小的记录,将它与r[2]交换:以此类推,第i趟在待排序记录r[i]~r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕.

选择排序(直接选择排序、堆排序)——Java

选择排序 思想:每趟从待排序的记录序列中选择关键字最小的记录放置到已排序表的最前位置,直到全部排完. 关键问题:在剩余的待排序记录序列中找到最小关键码记录. 方法: –直接选择排序 –堆排序 (1)简单的选择排序 1.基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换:然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止. 2.实例 实现代码: public static void main(String[] args) { int[] n

各种排序算法的分析及java实现

各种排序算法的分析及java实现 排序一直以来都是让我很头疼的事,以前上<数据结构>打酱油去了,整个学期下来才勉强能写出个冒泡排序.由于下半年要准备工作了,也知道排序算法的重要性(据说是面试必问的知识点),所以又花了点时间重新研究了一下. 排序大的分类可以分为两种:内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.下面讲的排序都是属于内排序. 内排序有可以分为以下几类: (1).插入排序:直接插入排序.二分法插入排序.希尔排序. (2).