数据结构基础(19) --堆与堆排序

简介: 完全二叉树 首先让我们回顾一下完全二叉树的两个性质:  性质1:具有n个结点的完全二叉树的深度为[logn](向下取整)+1。

完全二叉树

 

首先让我们回顾一下完全二叉树的两个性质:

  性质1:具有n个结点的完全二叉树的深度为[logn](向下取整)+1。

  性质2:若对含 个结点的完全二叉树从上到下且从左至右进行 至 的编号,则对完全二叉树中任意一个编号为 的结点:

    (1) 若 i=1,则该结点是二叉树的根,无双亲,否则,编号为 [i/2](向下取整)的结点为其双亲结点;
    (2) 若 2i>n,则该结点无左孩子,否则,编号为 2i 的结点为其左孩子结点;
    (3) 若 2i+1>n,则该结点无右孩子结点,否则,编号为2i+1 的结点为其右孩子结点。

 

数组与完全二叉树

 

从上图可以看出, 如果完全二叉树从上到下且从左至右进行从0至n-1进行编号,则对完全二叉树的性质需要修改如下:

   (1) 若 i=0,则该结点是二叉树的根,无双亲,否则,编号为 [(i-1)/2](向下取整)的结点为其双亲结点;
   (2) 若 2i+1>n,则该结点无左孩子,否则,编号为 2i+1 的结点为其左孩子结点;
   (3) 若 2i+2>n,则该结点无右孩子结点,否则,编号为2i+2 的结点为其右孩子结点。


大顶堆与小顶堆

 

堆是满足下列性质的数列{r1, r2, …,rn}:


(小顶堆)


(大顶堆)

 

大顶堆的设计

template <typename Type>
class MaxHeap
{
public:
    MaxHeap(int _maxSize = 10);
    virtual ~MaxHeap();

    bool isEmpty() const;
    void push(const Type &item);
    void pop();
    const Type &top() const;

private:
    //将堆的容量增大两倍
    void resize();
    //向上渗透
    void trickUp(int index);
    //向下渗透
    void trickDown(int index);

private:
    Type *heapArray;
    int maxSize;
    //currentSize有两个含义:
    // 1.代表当前堆中已经存储的元素数
    // 2.代表当前完全二叉树的第一个空位
    int currentSize;
};

大顶堆的实现

//构造
template <typename Type>
MaxHeap<Type>::MaxHeap(int _maxSize)
    : maxSize(_maxSize),currentSize(0)
{
    if (maxSize < 1)
        throw std::length_error("heap size must >= 1");

    heapArray = new Type[maxSize];
}
//析构
template <typename Type>
MaxHeap<Type>::~MaxHeap()
{
    delete []heapArray;
    heapArray = NULL;
    currentSize = 0;
}
//判空
template <typename Type>
bool MaxHeap<Type>::isEmpty() const
{
    return currentSize == 0;
}

堆顶元素

//查看堆顶元素
template <typename Type>
const Type &MaxHeap<Type>::top() const
{
    if (isEmpty())
        throw std::underflow_error("heap is empty");

    return heapArray[0];
}

插入元素

//插入
template <typename Type>
void MaxHeap<Type>::push(const Type &item)
{
    //如果堆已满, 则需要对堆进行扩充
    if (currentSize == maxSize)
    {
        resize();
    }
    //将元素插入到堆的第一个空位置上
    heapArray[currentSize] = item;

    //维护堆的性质:向上渗透
    trickUp(currentSize);
    ++ currentSize;
}
//向上渗透, 将刚刚插入的元素移动到合适的位置上
template <typename Type>
void MaxHeap<Type>::trickUp(int index)
{
    //根据完全二叉树的性质, 找到父节点
    int parent = (index-1)/2;
    Type bottom = heapArray[index];

    //循环终止条件
    // 1. index = 0 //bottom元素插入到根节点
    // 2. heapArray[parent] >= bottom   //bottom插入到parent节点的一个子节点上
    while ((index > 0) && (heapArray[parent] < bottom))
    {
        //父节点下移
        heapArray[index] = heapArray[parent];
        index = parent;
        parent = (parent-1)/2;
    }
    //插入
    heapArray[index] = bottom;
}
//将堆的大小加倍
template <typename Type>
void MaxHeap<Type>::resize()
{
    int newSize = std::max(maxSize*2, 1);
    Type *newHeap = new Type[newSize];
    for (int i = 0; i < currentSize; ++i)
    {
        newHeap[i] = heapArray[i];
    }

    delete []heapArray;
    heapArray = newHeap;
    maxSize = newSize;
}

删除堆顶元素

//删除
template <typename Type>
void MaxHeap<Type>::pop()
{
    if (isEmpty())
        throw std::underflow_error("heap is empty");

    //只有一个元素
    if (currentSize == 1)
    {
        heapArray[0].~Type();
        currentSize = 0;
        return ;
    }

    //显示释放堆顶元素
    heapArray[0].~Type();
    //直接将最有一个元素放到堆顶,
    //并且currentSize-1
    heapArray[0] = heapArray[-- currentSize];

    //此时如果破坏了堆的性质:向下渗透
    trickDown(0);
}
//向下渗透
template <typename Type>
void MaxHeap<Type>::trickDown(int index)
{
    int largeChild;
    Type top = heapArray[index];

    // 只需到达完全二叉树的最后一层
    // 的上一层即可
    // 循环的终止条件:
    // 1. index已经到达了最后一层(index >= currentSize/2 :找到了一个比较合适的位置)
    // 2. 在堆的中间找到了一个合适的位置(top >= heapArray[largeChild])
    while (index < currentSize/2)
    {
        int leftChild = (index*2)+1;
        int rightChild = leftChild + 1;

        //如果有右儿子节点, 而且右儿子节点还大于左儿子节点
        if (rightChild < currentSize && heapArray[rightChild] > heapArray[leftChild])
            largeChild = rightChild;
        else
            largeChild = leftChild;

        if (top >= heapArray[largeChild])
            break;

        //不然的话, 则需继续向下渗透
        heapArray[index] = heapArray[largeChild];
        // index需要向下搜索
        index = largeChild;
    }
    //插入
    heapArray[index] = top;
}

堆排序

  堆排序就是将元素一个一个插入到堆中, 然后再一个一个的取出来;

//堆排序
template <typename Type>
void heapSort(Type *begin, Type *end)
{
    int size = end-begin;
    MaxHeap<Type> maxHeap(size);
    //存入堆中
    for (Type *current = begin; current < end; ++ current)
        maxHeap.push(*current);

    //从堆中取出
    while (begin != end)
    {
        *begin = maxHeap.top();
        ++ begin;
        maxHeap.pop();
    }
}

template <typename Type>
void heapSort(Type *array, int arraySize)
{
    return heapSort(array, array+arraySize);
}

-测试代码:

int main()
{
    int array[20];
    for (int i = 0; i < 20; ++i)
    {
        array[i] = rand()%100;
        cout << array[i] << ' ';
    }

    heapSort(array, 20);

    cout << endl;
    for (int i = 0; i < 20; ++i)
    {
        cout << array[i] << ' ';
    }
    cout << endl;

    return 0;
}

目录
相关文章
|
1月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
43 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
95 16
|
2月前
|
存储 JavaScript 前端开发
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
103 1
|
2月前
|
算法 搜索推荐
数据结构与算法学习十八:堆排序
这篇文章介绍了堆排序是一种通过构建堆数据结构来实现的高效排序算法,具有平均和最坏时间复杂度为O(nlogn)的特点。
84 0
数据结构与算法学习十八:堆排序
|
2月前
|
存储 算法 调度
数据结构--二叉树的顺序实现(堆实现)
数据结构--二叉树的顺序实现(堆实现)
|
2月前
|
存储 算法 分布式数据库
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
|
2月前
|
存储 算法
探索数据结构:分支的世界之二叉树与堆
探索数据结构:分支的世界之二叉树与堆
|
2月前
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
38 0
|
2月前
|
存储 算法
【数据结构】二叉树——顺序结构——堆及其实现
【数据结构】二叉树——顺序结构——堆及其实现
|
2月前
【数据结构】大根堆和小根堆
【数据结构】大根堆和小根堆
65 0