STL heap部分源代码分析

本文假设你已对堆排序的算法有主要的了解。

要分析stl中heap的源代码的独到之处。最好的办法就是拿普通的代码进行比較。话不多说,先看一段普通的堆排序的代码:

//调整大顶堆。使得结构合理
void max_heap(int a[],int node,int size)
{
    int lg=node;
    int l=node*2;
    int r=node*2+1;
    if(l<=size&&a[lg]<a[l])
    {
        lg=l;
    }
    if(r<=size&&a[lg]<a[r])
    {
        lg=r;
    }
    if(lg!=node)
    {
        //a[lg]=a[lg]^a[node];//交换
        //a[node]=a[lg]^a[node];
        //a[lg]=a[lg]^a[node];
        int tt=a[lg];
        a[lg]=a[node];
        a[node]=tt;

        max_heap(a,lg,size);
    }
}
//生成一个大顶堆
void make_heap(int a[],int size)
{
    for(int i=size/2;i>0;i--)
    {
        max_heap(a,i,size);
    }
}
//堆排序,使数据在数组中按从小到大的顺序排列
void heap_sort(int a[],int size)
{
    make_heap(a,size);
    for(int i=1;i<size;i++)
    {
        int tt=a[1];
        a[1]=a[size-i+1];
        a[size-i+1]=tt;
        max_heap(a,1,size-i);
    }
}

相应第一个函数max_heap(),stl中有一个功能相似的函数adjust_heap(),也是调整整个heap,使之符合大顶堆的要求,代码例如以下:

// ============================================================================
// 保持堆的性质
//整个的过程是从根节点開始,将根节点和其子节点中的最大值对调,一直到叶节点为止(称之为下溯)
//然后。再从这个根节点開始。把它与其父节点进行比較,假设比父节点大,则父子节点对调。直到根节点为止(称之为上溯)
//============================================================================
// first 起始位置
// holeIndex 要进行调整操作的位置
// len 长度
// value holeIndex新设置的值
template <class _RandomAccessIterator, class _Distance, class _Tp>
void
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
              _Distance __len, _Tp __value)
{
    // 当前根节点的索引值
    _Distance __topIndex = __holeIndex;
    // 右孩子节点的索引值
    _Distance __secondChild = 2 * __holeIndex + 2;
    // 假设没有到末尾
    while (__secondChild < __len) {
        // 假设右孩子节点的值比左孩子节点的值要小。那么secondChild指向左孩子
        if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))
            __secondChild--;
        // 子节点的往上升
        *(__first + __holeIndex) = *(__first + __secondChild);
        // 继续处理
        __holeIndex = __secondChild;
        __secondChild = 2 * (__secondChild + 1);
    }
    // 假设没有右子节点
    if (__secondChild == __len) {
        *(__first + __holeIndex) = *(__first + (__secondChild - 1));
        __holeIndex = __secondChild - 1;
    }
    // 针对节点topIndex调用push_heap操作
    __push_heap(__first, __holeIndex, __topIndex, __value);
}
//上溯
__push_heap(_RandomAccessIterator __first,
            _Distance __holeIndex, _Distance __topIndex, _Tp __value)
{
    // 获取父节点的索引值
    _Distance __parent = (__holeIndex - 1) / 2;
    // 假设还没有上升到根节点,且父节点的值小于待插入节点的值
    while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
        // 父节点下降到holeIndex
        *(__first + __holeIndex) = *(__first + __parent);
        // 继续往上检查
        __holeIndex = __parent;
        __parent = (__holeIndex - 1) / 2;
    }
    // 插入节点
    *(__first + __holeIndex) = __value;
}

stl里算法的堆的调整的主要流程如凝视里所说,主要是先进行下溯。直到叶节点,然后用push_heap进行上溯才调增完毕。对照之前的普通代码,主要有3点改变:

  1. 把普通代码的递归操作变成了循环操作。这点非常好理解,由于递归须要系统使用资源来维护递归栈,开销比較大,所以stl中除了sort的快排之外(由于快排的递归深度有限制)。一般都会把递归的算法转换成循环来做。
  2. 普通代码中,我们直接比較根节点和左右节点的值。然后假设须要的话跟节点直接和较大的节点交换,这样仅仅须要从上到下一趟比較。就能完毕树的调整。而stl的代码中,则要先下溯。然后再上溯。两趟才干完毕调整,看起来反而效率更低了,为什么呢?我细致分析了代码,感觉可能有一下两点的原因:1.代码的复用,由于下溯的主要作用事实上在保证大顶堆性质的前提下,让要调整的那个节点从根节点開始下沉。造成了一个新插入节点的假象。而push_heap()正是为了应对新插入节点而写的一个函2.数。这就复用了这块的代码。

    2.效率上的一点提升,由于下溯的过程仅仅须要两个子节点的一次比較和根节点的一次赋值,而上溯的过程也仅仅须要与根节点的一次比較和子节点的一次赋,所以合起来事实上是两次赋值和两次比較;而普通的代码中,假设不考虑边界检查。找出三个点里的最大值,并与之交换,则至少须要两次比較,和三次赋值,所以stl的算法中,赋值运算少了一次,效率有所提升。

  3. 普通代码中,每次都须要对左子节点和右子节点进行边界的检查。而stl代码中。仅仅对右子节点进行边界检查,为了防止右子节点越界而左子节点没有越界的情况发生,在循环结束后添加了对左子节点的边界检查。这一改动大幅度降低了边界检查的次数,明显提升了效率。

通过上述的分析,事实上我们也能够以小见大。

事实上stl中,存在着大量这种优化,递归转循环,降低边界检查,用赋值取代交换等等。假设我们能细致研究,并在平时的编码中也养成这种习惯,就能极大得提升代码的效率。

时间: 2024-10-25 14:23:42

STL heap部分源代码分析的相关文章

STL源代码分析——STL算法sort排序算法

前言 因为在前文的<STL算法剖析>中,源代码剖析许多,不方便学习,也不方便以后复习.这里把这些算法进行归类,对他们单独的源代码剖析进行解说.本文介绍的STL算法中的sort排序算法,SGI STL中的排序算法不是简单的高速排序,而是交叉利用各种排序:堆排序.插入排序和高速排序:这样做的目的是提高效率.针对数据量比較大的採用高速排序,数据量比較小的能够採用堆排序或插入排序. 本文介绍了有关排序的算法random_shuffle.partition.stable_partition.sort.s

JAVA随笔篇一(Timer源代码分析和scheduleAtFixedRate的使用)

写完了基础篇,想了非常久要不要去写进阶篇.去写JSP等等的用法.最后决定先不去写.由于自己并非JAVA方面的大牛.眼下也在边做边学,所以决定先将自己不懂的拿出来学并记下来. Timer是Java自带的java.util.Timer类,通过调度一个java.util.TimerTask任务.这样的方式能够让程序依照某一个频度运行. 1.Timer类的源代码分析: public class Timer { /** * The timer task queue. This data structure

Android系统进程Zygote启动过程的源代码分析

原文地址:http://blog.csdn.net/luoshengyang/article/details/6747696 Android应用程序框架层创建的应用程序进程具有两个特点,一是进程的入口函数是ActivityThread.main,二是进程天然支持Binder进程间通信机制:这两个特点都是在进程的初始化过程中实现的,本文将详细分析Android应用程序进程创建过程中是如何实现这两个特点的. Android应用程序框架层创建的应用程序进程的入口函数是ActivityThread.ma

Android应用程序进程启动过程的源代码分析

文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创建的应用程序进程具有两个特点,一是进程的入口函数是ActivityThread.main,二是进程天然支持Binder进程间通信 机制:这两个特点都是在进程的初始化过程中实现的,本文将详细分析Android应用程序进程创建过程中是如何实现这两个特点的. Android应用程序框架层创建的应用程序进程

HBase源代码分析之HRegionServer上MemStore的flush处理流程(二)

继上篇文章<HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)>遗留的问题之后,本文我们接着研究HRegionServer上MemStore的flush处理流程.重点讲述下怎样选择一个HRegion进行flush以缓解MemStore压力,还有HRegion的flush是怎样发起的. 我们先来看下第一个问题:怎样选择一个HRegion进行flush以缓解MemStore压力.上文中我们讲到过flush处理线程假设从flushQueue队列中拉取出的一个

STL -- heap结构及算法

STL -- heap结构及算法 heap(隐式表述,implicit representation) 1. heap概述 : vector + heap算法 heap并不归属于STL容器组件,它是个幕后英雄,扮演priority queue的助手.顾名思义,priority queue允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取.binary max heap 正是具有这样的特性,适合作为priority queue 的底层机制. 让我们做一

Java中arraylist和linkedlist源代码分析与性能比較

Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arraylist和linkedlist的性能. 2,arraylist源代码分析 Arraylist底层的数据结构是一个对象数组.有一个size的成员变量标记数组中元素的个数,例如以下图: * The array buffer into which the elements of the ArrayLis

转:RTMPDump源代码分析

0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://.也提供 Android 版本. 最近研究了一下它内部函数调用的关系. 下面列出几个主要的函数的调用关系. RTMPDump用于下载RTMP流媒体的函数Download: 用于建立网络连接(NetConnect)的函数Connect: 用于建立网络流(NetStream)的函数 rtmpdump源代码

Kafka SocketServer源代码分析

Kafka SocketServer源代码分析 标签: kafka 本文将详细分析Kafka SocketServer的相关源码. 总体设计 Kafka SocketServer是基于Java NIO来开发的,采用了Reactor的模式,其中包含了1个Acceptor负责接受客户端请求,N个Processor负责读写数据,M个Handler来处理业务逻辑.在Acceptor和Processor,Processor和Handler之间都有队列来缓冲请求. kafka.network.Accepto