[数据结构]最小堆的类模板实现

堆数据结构是一种数组对象,它可以被视为一科完全二叉树结构。

它的特点是父节点的值大于(小于)两个子节点的值(分别称为最大堆和最小堆)。它常用于管理算法执行过程中的信息,应用场景包括堆排序,优先队列等。

1、根结点若有子树,则子树一定也是堆。

2、根结点一定大于(或小于)子结点。

因为要求堆必须是完全二叉树,所以可以用线性的数据结构,比如数组,来实现堆。

利用数组实现,则对于长为N的堆中的元素从0到N-1排列,有:

i的父结点:Parent(i)=(i+1)/2-1

i的左叶子:Left(i)=(i+1)*2-1

i的右叶子:Right(i)=(i+1)*2

堆(Heap),其实也没什么大不了,简单地说就是一种有序队列而已,普通的队列是先入先出,而二叉堆是:最小先出。
这不是很简单么?如果这个队列是用数组实现的话那用打擂台的方式从头到尾找一遍,把最小的拿出来不就行了?行啊,可是出队的操作是很频繁的,而每次都得打一遍擂台,那就低效了,打擂台的时间复杂度为Ο(n),那如何不用从头到尾fetch一遍就出队呢?二叉堆能比较好地解决这个问题,不过之前先介绍一些概念。

完全树(Complete Tree):从下图中看出,在第n层深度被填满之前,不会开始填第n+1层深度,还有一定是从左往右填满。

这样有什么好处呢?好处就是能方便地把指针省略掉,用一个简单的数组来表示一棵树,如图:


那么下面介绍二叉堆:二叉堆是一种完全二叉树,其任意子树的左右节点(如果有的话)的键值一定比根节点大,上图其实就是一个二叉堆。
你一定发觉了,最小的一个元素就是数组第一个元素,那么二叉堆这种有序队列如何入队呢?看图:


假设要在这个二叉堆里入队一个单元,键值为2,那只需在数组末尾加入这个元素,然后尽可能把这个元素往上挪,直到挪不动,经过了这种复杂度为Ο(logn)的操作,二叉堆还是二叉堆。

那如何出队呢?也不难,看图:

将最小的节点删除后,把最后的一个节点放到第一个节点的位置,然后在调整。

最小堆的类模板实现:类接口部分

/////////////////////////
#include <iostream>
using namespace std;

#define DefaultSize 10

template<class T>
class MinHeap       //最小堆的类模板实现
{
public:
    MinHeap(int sz=DefaultSize);
    MinHeap(T arr[], int n);
    ~MinHeap(){delete []heap;}

    bool isEmpty()const{return currentSize==0;}
    bool isFull()const{return currentSize==maxHeapSize;}
    void makeempty(){currentSize=0;}

    bool insert(const T& x); //在数组尾部插入,并调整堆
    bool removeMin(T& x);    //删除堆顶上的最小元素,最后一个元素补到堆顶,然后调整

private:
    T * heap;       //采用数组作为其存储方式。
    int currentSize;
    int maxHeapSize;

    void siftDown(int start, int m);//从start到m下滑调整为最小堆
    void siftUp(int start);         //从start到0上滑调整为最小堆
};

具体接口函数实现部分:

template<class T>
MinHeap<T>::MinHeap(int sz=DefaultSize)
{
    maxHeapSize=(sz<DefaultSize)?DefaultSize:sz;
    heap=new T[maxHeapSize];
    if(heap==NULL){cerr<<"err\n";exit(-1);}
    currentSize=0;
}

template<class T>
MinHeap<T>::MinHeap(T arr[], int n)
{
    maxHeapSize=(n<DefaultSize)?DefaultSize:n;
    heap=new T[maxHeapSize];
    if(heap==NULL){cerr<<"err\n";exit(-1);}
    currentSize=n;

    int i=0;
    while(i<n){                 //copy
        heap[i]=arr[i];
        ++i;
    }

    int currentPos=(currentSize-2)/2; //找到最初调整位置,最后节点的父节点位置,也就是最后的分支节点
    while(currentPos>=0){             //自底向上逐步扩大形成堆
        siftDown(currentPos,currentSize-1);
        --currentPos;
    }
}

template<class T>
void MinHeap<T>::siftDown(int start, int m)//堆的下滑调整算法,从节点start到m为止,从上到下比较,如果子女小于父节点,
{                                          //关键码上浮 ,据需向下层比教,将局部子树调整为最小堆
    int i=start;int j=2*i+1;    //i当前子树的根节点,j左子女
    T temp=heap[i];             //保存根节点的值
    while(j<=m){                //从上向下调整,检查是否到最后位置
        if(j<m && heap[j+1]<heap[j])    //j指向两个子女中最小的
            ++j;
        if(temp<=heap[j]) break;        //小则不做调整
        else{
            heap[i]=heap[j];i=j;j=2*i+1;//小者上移,i,j下降
        }
    }
    heap[i]=temp;
}

template<class T>
void  MinHeap<T>::siftUp(int start)//新节点插入到最小堆的后面,故需从下到上,与父节点比较,调整
{                                  //从start开始到0为止,从下向上
    int j=start, i=(j-1)/2;        //j表示子节点,i表示j的父节点
    int temp=heap[j];
    while(j>0){
        if(heap[i]<=temp)break;
        else{
            heap[j]=heap[i];j=i;i=(j-1)/2;
        }
        heap[j]=temp;
    }
}

template<class T>
bool MinHeap<T>::insert(const T& x)
{
    if(currentSize==maxHeapSize){
        cerr<<"full\n";return false;
    }
    heap[currentSize]=x;
    siftUp(currentSize);
    ++currentSize;
    return true;
}

template<class T>
bool MinHeap<T>::removeMin(T& x)
{
    if(!currentSize){
        cerr<<"empty\n";return false;
    }
    x=heap[0];
    heap[0]=heap[currentSize-1];
    --currentSize;
    siftDown(0,currentSize-1);
    return true;
}

验证程序:

为了简单,验证时把类中private屏蔽掉,以便外面访问;

int main(int argc, char* argv[])
{
    int arr[6]={5,4,3,2,1,0};
    int i=0;
    while(i<sizeof(arr)/sizeof(arr[0])){
        cout<<arr[i]<<" ";
        ++i;
    }
    cout<<endl;

    MinHeap<int> h(arr,sizeof(arr)/sizeof(arr[0]));

    i=0;
    while(i<6){
        cout<<h.heap[i]<<" ";
        ++i;
    }
    cout<<endl;

    int x=10;
    h.insert(x);
    i=0;
    while(i<7){
        cout<<h.heap[i]<<" ";
        ++i;
    }
    cout<<endl;

    h.removeMin(x);
    i=0;
    while(i<6){
        cout<<h.heap[i]<<" ";
        ++i;
    }
    cout<<endl;
    system("pause");
    return 0;

}

验证结果:

5 4 3 2 1 0
0 1 3 2 4 5
0 1 3 2 4 5 10
1 2 3 10 4 5
请按任意键继续. . .

版权声明:本文为【借你一秒】原创文章,转载请标明出处。

时间: 2024-11-08 08:31:15

[数据结构]最小堆的类模板实现的相关文章

java最小堆实现优先权队列和求最大的n个数问题

堆在实现优先权队列和求最大最小的n个数问题上,有着莫大的优势! 对于最大堆和最小堆的定义此处不再赘述,课参考网上文章:http://blog.csdn.net/genios/article/details/8157031 本文主要是对最小堆进行实现和应用,仅供新手参考. 优先权队列 优先权队列是一种非常有用的数据结构,操作系统的进程调度就有优先权队列的应用,如果用最小值表示最高的优先权,则使用最小堆,否则使用最大堆. top-N值为问题: 对于求最大的n个数,可以用最小堆来实现,思路是:将n个数

转:最小堆的数组实现

最小堆的数组实现 //**************minHeap.h****************////******最小堆的类定义和各操作的实现*******////C++源码 #ifndef MINHEAP_H#define MINHEAP_H #include <iostream> using namespace std; template<class T>class MinHeap {public:    MinHeap(const int size=20) {     

C++解析(26):函数模板与类模板

0.目录 1.函数模板 1.1 函数模板与泛型编程 1.2 多参数函数模板 1.3 函数重载遇上函数模板 2.类模板 2.1 类模板 2.2 多参数类模板与特化 2.3 特化的深度分析 3.小结 1.函数模板 1.1 函数模板与泛型编程 C++中有几种交换变量的方法? 交换变量的方法--定义宏代码块 vs 定义函数: 定义宏代码块 优点:代码复用,适合所有的类型 缺点:编译器不知道宏的存在,缺少类型检查 定义函数 优点:真正的函数调用,编译器对类型进行检查 缺点:根据类型重复定义函数,无法代码复

类模板的概念和意义

思考:在C++中是否能够将泛型的思想应用于类? 类模板 一些类主要用于存储和组织数据元素 类中数据组织的方式和数据元素的具体类型无关 如:数组类,链表类,Stack类,Queue类,等 C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能. C++中的类模板 以相同的方式处理不同的类型 在类声明前使用template进行标识 <typename T>用于说明类中使用的泛指类型T template <typename T> { public:

C++数组类模板(堆内存)

#ifndef _HEAP_ARRAY_H_ #define _HEAP_ARRAY_H_ /* * why * 2016/9/5 15:18 * 实现了一个较完善的数组类模板,在堆空间上 */ template < typename T > class Heap_Array { private: T *m_array; int array_len; Heap_Array(int len);//构造函数 设置一个数组的长度,并且将数组元素值全清0 // Heap_Array(const Hea

数据结构-最大堆、最小堆【手动实现】

0,堆的简介 数据结构中的堆是一种特殊的二叉树,不同于 Java 内存模型中的堆. 堆必须符合以下两个条件: 是一棵完全二叉树. 任意一个节点的值都大于(或小于)左右子节点的值. 从第一点可以知道,堆适合用数组来存储. 第二点中,若父节点都大于等于左右子节点,则被称为大顶堆,反之则为小顶堆. 图-最大堆及其存储方式 0.1节点的父.子节点关系 一个节点[根节点除外]的父节点地址为其地址的二分之一,它的左子节点地址为其地址值的2倍,右子节点地址为其地址2倍加1.  例如:现在有个节点的地址为3,其

数据结构之最小堆的实现C++版

完全二叉树之所以用数组的方式存在,在于他的一个特性 若子节点为i,则父节点为(i-1)/2,注意c++特性,该结果肯定是个整数. 若父节点为j,则子节点必为2*j+1;则在数组里面可以非常方便的通过下标去获取. 建堆的核心思想: 堆在index的值为heap[index],然后其两个孩子的值边可求得,左孩子为heap[index*2+1],右孩子为heap[index*2+2]. 首先比较左边孩子与右边孩子,获取较小值的孩子,然后让heap[index]与值较小的孩子进行比较.若值小则交换值,并

[数据结构]栈之顺序栈的类模板实现

栈的数组实现形式,采用动态分配数组,不够时可以调整栈的大小. Stack.h文件:主要定义栈的抽象基类,提供公共的接口函数. #ifndef STACK #define STACK //栈的抽象基类 template<class T> class Stack { public: Stack(){} ~Stack(){} virtual void Push(const T& x)=0; virtual bool Pop(T& x)=0; virtual bool getTop(T

《ACM/ICPC 算法训练教程》读书笔记一之数据结构(堆)

书籍简评:<ACM/ICPC 算法训练教程>这本书是余立功主编的,代码来自南京理工大学ACM集训队代码库,所以小编看过之后发现确实很实用,适合集训的时候刷题啊~~,当时是听了集训队final的意见买的,感觉还是不错滴. 相对于其他ACM书籍来说,当然如书名所言,这是一本算法训练书,有着大量的算法实战题目和代码,尽管小编还是发现了些许错误= =,有部分注释的语序习惯也有点不太合我的胃口.实战题目较多是比较水的题,但也正因此才能帮助不少新手入门,个人认为还是一本不错的算法书,当然自学还是需要下不少