zoukankan      html  css  js  c++  java
  • 堆排序的简单实现

    堆排序是排序的一种,一般有大根对和小根堆之说,大根对,根节点的值比左右子树的根节点的值要大。建堆我们一般是一个完全二叉树。堆排序一般面向数据量比较大的时候,数据量比较小的时候,不适合使用堆排序,比如有种情况就是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     }
  • 相关阅读:
    python django day 1
    C# 日常
    C# NPOI使用
    SharpZipLib 压缩ZIP导出
    JSON劫持
    跨站请求伪造CSRF或XSRF
    跨站脚本XSS安全
    会话窃取
    Cookie
    Promise -ES6
  • 原文地址:https://www.cnblogs.com/OliverZhang/p/6580713.html
Copyright © 2011-2022 走看看