堆的相关操作以及堆排序

堆:它的物理结构可以看做是数组,而逻辑结构可以看做一颗完全二叉树.
堆有大堆和小堆之分:

  • 大堆:根节点是当前所有元素中的最大值,而对于每一个子树,其子树也是一个大堆.
  • 小堆:根节点是当前所有元素中的最小值,而对于每一个子树,其子树也是一个小堆.

堆的应用:
堆还可用来表示优先级队列.队列:即只允许队列的队首进行删除,而队尾进行插入.而优先级队列就是一组有序的队列.在堆中,进行插入和删除时都是在队尾进行的,比如堆的插入,先插入到尾端,再进行调整到合适的位置;而堆的删除指的是删除堆的堆顶元素,首先要与堆尾元素交换然后再删掉堆尾元素,其实删掉的也是堆首元素.那么这样就和队列的插入和删除不谋而合了.


堆的插入

数组元素为:9,5,2,7,3

假设现在的堆是小堆,插入元素的步骤:

  1. 将元素插入到二叉树的最后
  2. 将插入的元素和其父节点的元素比较,若小于父节点,就把父节点其交换.
  3. 一直循环,直到父节点的元素小于子节点的元素.
    过程描述如下图:
    这里写图片描述
    插入的代码实现:
//函数指针,用来判断时大堆还是小堆
int Less(heap_type a,heap_type b)
{
    return a < b;
}

int Greater(heap_type a,heap_type b)
{
    return a > b;
}

//上浮 
void AdjustUp(heap_type data[],int size,int index,Compare cmp)
{
    //Compare是一个函数指针,用来判断是要排小堆还是大堆
    if(index >= size)
    {
        return;
    }
    int parent = (index - 1)/2;
    int child = index;
    //若子节点为0,就说明此时已经是根节点了
    while(child > 0)
    {
        //如果子节点小于父节点,就与父节点交换,必须保证父节点是小的节点.(小堆的要求)
        if(cmp(data[child],data[parent]))
        {
            swap(&data[child],&data[parent]);
        }
        else
        {
            break;
        }
        child = parent;
        parent = (child - 1)/2;
    }
}

void InsertHeap(heap* h,heap_type value)
{
    if(h == NULL)
    {
        return;
    }
    if(h->size >= MAX_SIZE)
    {
        //堆满
        return;
    }
    h->data[h->size++] = value;
    AdjustUp(h->data,h->size,h->size - 1,h->cmp);
}

堆的删除

堆的删除指的是堆顶元素的删除,
删除步骤:

  1. 将堆顶元素和最后一个元素进行交换
  2. 减减堆的元素个数
  3. 然后调整堆,将堆依旧调整为一个小堆
    如下图所示:
    这里写图片描述
    在调整堆时,采用下浮的方法,先比较左右子树的节点的大小,如果根节点的值大于子树的最小节点,就交换该子树节点和根节点.然后将一直循环将节点下移.
    调整的代码如下:
//下沉
void AdjustDown(heap_type data[],int size,int index,Compare cmp)
{
    if(index >= size)
    {
        return;
    }
    int parent = index;
    int child = parent * 2 + 1;  //左子树
    while(child < size)
    {
        if(cmp(data[child + 1],data[child]) && (child + 1) < size)
        {
            //如果左子树大于右子树,那么就让child指向右子树.child是指向较小的节点
            child = child + 1;
        }
        if(cmp(data[child] ,data[parent]))
        {
            //如果父节点大于子节点,就让交换
            swap(&data[parent],&data[child]);
        }
        else
        {
            break;
        }
        parent = child;
        child = parent * 2 + 1;
    }
}

//删除堆顶元素的步骤:先与最后一个元素交换再将最后一个元素删除.然后再将堆调整为符合条件的堆.
void EraseHeapTop(heap* h)
{
    if(h == NULL || h->size == 0)
    {
        return;
    }
    swap(&h->data[0],&h->data[h->size - 1]);
    --h->size;
    AdjustDown(h->data,h->size,0,h->cmp);
}

堆排序

  1. 升序排序,需要使用大堆来排序.
    步骤:
    ①将数组中的所有元素全部元素按照上浮的方法插入到堆中.
    ②然后将堆顶元素和堆尾元素交换.
    ③删掉堆尾元素,然后减减堆的元素个数.
    ④将堆中的元素进行调整,使其时一个大堆.
  2. 降序排序,需要用小堆来排序.
void Swap(heap_type* a,heap_type* b)
{
    heap_type tmp = *a;
    *a = *b;
    *b = tmp;
}
void heap_sort(heap_type arr[],int size)
{
    if(size <= 1)
    {
        return;
    }
    int i = 0;
    for(i = 0;i < size;++i)
    {
        AdjustUp(arr,i + 1,i,Less);  //降序
        //AdjustUp(arr,i + 1,i,Greater); //升序
    }
    int Size = size;
    while(Size > 0)
    {
        Swap(&arr[0],&arr[Size - 1]);
        --Size;
        AdjustDown(arr,Size,0,Less); //降序
        //AdjustDown(arr,Size,0,Greater); //升序
    }
}


方法二:当然也可以利用下沉来创建堆,然后进行排序.

void heap_sort2(heap_type arr[],int size)
{
    if(size <= 1)
    {
        return;
    }
    int i = (size - 1 - 1)/2;
    for(;i > 0;--i)
    {
        AdjustDown(arr,size,i - 1,Less);  //降序
        //AdjustDown(arr,size,i - 1,Greater);  //升序
    }
    //AdjustDown(arr,size,0,Greater);
    AdjustDown(arr,size,0,Less);
    int Size = size;
    while(Size > 0)
    {
        Swap(&arr[0],&arr[Size - 1]);
        --Size;
        AdjustDown(arr,Size,0,Less);  //降序
        //AdjustDown(arr,Size,0,Greater); //升序
    }
}


堆排序时间复杂度:

堆排序的时间复杂度为:O(nlogn).
空间复杂度:O(1);

若需要所有与堆排序相关的代码:请戳

猜你喜欢

转载自blog.csdn.net/yinghuhu333333/article/details/80936011