堆的定义、使用数组实现堆的创建、调整、删除和插入、堆排序

本文介绍了堆的基本概念,包括大顶堆和小顶堆,并详细讲解了如何用数组实现大顶堆的创建、调整、删除和插入操作。特别地,文章深入探讨了堆排序的原理,通过不断调整堆结构并交换堆顶元素,实现序列的排序,既节省空间又高效。

堆的定义

堆是一棵完全二叉树,并且树中的每一个结点的值都不小于(或者不大于)其左右孩子结点的值。分为两种:大顶堆:父亲结点的值不小于(大于或者等于)其左右孩子结点的值。小顶堆:父亲结点的值不大于(小于或者大于)其左右孩子结点的值。堆一般借助于优先队列来实现(前面已经写过了优先队列的文章),今天我们将会以数组实现大顶堆为例。
1. 首先实现向下调整函数:
总是将结点x和它的左右孩子(如果存在的话)进行比较,如果孩子中存在比x结点更大的值,就将其中那个值更大的孩子结点和x交换。交换之后,要接续将x和新的孩子结点进行比较,一直到x结点的值大于或等于孩子结点的值或者x已经不存在孩子结点为止。

//向下调整建堆函数。 
void downadjust(int heap[],int adjust,int last){     //heap[]为实现堆的数组,adjust是待调整结点的下标,last为最后一个数组元素的小标。
    int i=adjust,j=2*i;                              //i为工作下标,j是预调整结点的左孩子。 
    while(j<=last){
        if(j+1<=last&&heap[j+1]>heap[j]){            //选择左右孩子结点值更大的那一个 
            j++;
        } 
        if(heap[j]>heap[i]){                        // 如果左右孩子中最大的那个都比待调整结点要大 
            swap(heap[i],heap[j]);               //交换两个结点的值 
            i=j;                                 //i更新,始终记录待调整节点的下标 
            j=2*i;                               //j更新为i的左孩子 
        } 
        else{                          //如果待调整结点的值比左右孩子结点都要大,那么停止。 
            break;
        }
    }   
}

2 建堆函数:
假设共有n个结点,那么根据完全二叉树的性质很容易想到在[1,n/2]的范围内的结点都是非叶子结点,数组下标超过n/2的都是叶子结点。我们选择从n/2的结点开始倒着往上枚举。因为这样每次调整完一个结点后,之前以该节点为根的子树就一定是一个大根堆了,根节点大于孩子结点,那么之后再调整该根节点的父亲结点时,只要调整到这一步就直接使用就好了,保证了每个结点都是以其作为根节点子树的最大值。

void creatheap(int heap[],int n){           //堆的建立 
    for(int i=n/2;i>=1;i--){                //从最后一个非叶子结点进行 
        downadjust(heap,i,n);
    }
} 

3 删除堆顶元素:
删除堆顶元素后为了保证剩下的结构仍然是堆结构,那么可以将最后一个元素覆盖原来的堆顶元素,然后再依次向下调整结构即可。

void del(int heap[],int n){          //从堆中删除结点。 堆顶元素出去。  n为结点的个数 
    heap[1]=heap[n];                 //用最后一个元素将第一个元素(堆顶元素弹出)覆盖,然后从上往下调整 
    n--;
    downadjust(heap,1,n);
} 

4.向上调整和插入函数:
插入元素时,首先将其插入到最末尾的位置,然后对其进行向上调整。每次将该新结点x与其父亲结点比较,如果x结点更大,那么将x结点和其父亲结点进行交换,直至x达到堆顶或者x小于父亲结点。

void upadjust(int heap[],int last){     //添加一个元素,首先将其添加到堆的最后一个位置,然后向上调整即可 
    int i=last,j=i/2;                //i记录最后一个元素(即待调整元素)的下标,j记录了i的父亲结点的下标 
    while(j>=1){
        if(heap[i]>heap[j]){         //如果待调整元素的值要大于其父亲节点的值,那么交换 
            swap(heap[i],heap[j]);
            i=j;
            j=i/2;                 //更新i,j 
        }
        else{
            break;
        }
    }
}
void insert(int heap[],int n,int x){   // 将新元素x添加到最后一个位置。然后向上调整 
    heap[++n]=x;
    upadjust(heap,n);
}

堆排序

堆排序就是使用堆结构对一个序列进行排序。我们以递增排序为例。
最容易想到的就是已经对序列建立了堆结构之后,不断的输出堆顶元素,然后调整堆结构,直至整个堆只有一个元素为止。这样做的话需要另开辟数组来记录最后的排序过程。
为了节约空间,我们可以换一种思路:假设由n个结点,从后往前进行遍历,每次将堆顶元素和最后一个元素进交换,然后堆[1,n-1]进行结构调整,那么堆低的元素就是最大的元素。不难看出,当访问到i位置时只需将堆顶元素和i号位的元素交换,之后对[1,i-1]进行结构调整。

void heapsort(int heap[],int n){    
    creatheap(heap,n);               //建堆 
    for(int i=n;i>=1;i--){     //从后往前遍历 
        swap(heap[1],heap[i]);      //交换堆顶和i号位的结点 
        downadjust(heap,1,i-1);    //调整 
    }
}

int main()
{
    int heap[13]={0,3,6,1,9,3,13,56,77,12,45,43,65};
    heapsort(heap,12) ;

    for(int i=1;i<=12;i++){
        printf("%d ",heap[i]);
    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值