堆以及堆排序

堆是一棵完全二叉树。

小根堆:每个父节点的值,都小于等于其子节点的值。因此,根节点的值为集合的最小值。

大根堆:每个父节点的值,都大于等于其子节点的值。因此,根节点的值为集合的最大值。

通常使用一维数组来存储堆。根节点的值存放在数组中索引值为1的位置。由于完全二叉树的特性,若父节点在数组的索引为 x x x,则其左子节点的索引为 2 x 2x 2x,右子节点的索引为 2 x + 1 2x+1 2x+1。(凡是完全二叉树,基本上都是用一维数组来存储的)

堆最核心的是up操作down操作,使用这两个操作可完成以下堆(小根堆)主要支持的函数:

1)插入一个数insert(int x)

heap[++ size] = x;
up(size);

2)求集合当中的最小值getMin()

heap[1];

3)删除最小值deleteMin()

heap[1] = heap[size];
size --;
down(1);

4)删除任意一个元素deleteMin(int k)

heap[k] = heap[size];
size --;
down(k);
up(k);

5)修改任意一个元素modify(int k, int x)

heap[k] = x;
down(k);
up(k);

up操作的代码:

public void up(int u){
    while(u / 2 >= 1 && heap[u / 2] > heap[u]){
        int tmp = heap[u / 2];
        heap[u / 2] = heap[u];
        heap[u] = tmp;
        u /= 2;
    }
}

down操作的代码:

public void down(int u){
    int t = u;
    if(2 * u <= size && heap[2 * u] < heap[t]) t = 2 * u;
    if(2 * u + 1 <= size && heap[2 * u + 1] < heap[t]) t = 2 * u + 1;
    if(t != u){
        int tmp = heap[u];
        heap[u] = heap[t];
        heap[t] = tmp;
        down(t);
    }
}

堆排序

可以使用堆来实现堆排序。堆排序的思路是:先根据待排序数组建堆(小根堆),然后依次从堆中弹出最小值,也就是将堆的根节点删除,弹出的元素即构成为从小到大排序的数组。

1、建堆

建堆的直观想法是,依次将数组中的元素插入到堆中,这当然是没啥问题的,但这种做法的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)推荐的做法是:将数组元素放入到堆(heap数组)里后,对heap数组中索引值从size/2直到0,进行down操作。注意,对heap数组中索引值从0到size/2进行down操作是不正确的,这会导致排序出错。

首先,上述的推荐做法是正确的,因为:①堆中只有一半的节点是存在子节点的,剩下的一半节点则没有,对这些没有子节点的节点不进行处理当然没有问题;②按照size/2到0的顺序进行down操作,可保证每个节点满足小根堆的要求,递归的处理每个节点后,较大的节点值是”下沉‘的,留在上面的都是较小的节点值,如果down操作顺序反过来,较大的节点值可能因为满足局部最小而被调整到根节点,且后面的down操作也不会再访问该节点,从而导致错误。其次,推荐做法的时间复杂度为 O ( n ) O(n) O(n),证明如下:

如图所示,建堆的时间复杂度可写为: n 4 ∗ 1 + n 8 ∗ 2 + n 16 ∗ 3 + ⋯ + n 2 n ∗ ( n − 1 ) = n ∗ ( 1 2 2 + 2 2 3 + 3 2 4 + ⋯ + n − 1 2 n ) (1) \frac{n}{4}*1 + \frac{n}{8}*2 + \frac{n}{16}*3 + \cdots + \frac{n}{2^n}*(n - 1)=n*(\frac{1}{2^2}+\frac{2}{2^3}+\frac{3}{2^4}+\cdots+\frac{n-1}{2^n}) \tag{1} 4n1+8n2+16n3++2nn(n1)=n(221+232+243++2nn1)(1)

S = 1 2 2 + 2 2 3 + 3 2 4 + ⋯ + n − 2 2 n − 1 + n − 1 2 n (2) S=\frac{1}{2^2}+\frac{2}{2^3}+\frac{3}{2^4}+\cdots+\frac{n-2}{2^{n-1}}+\frac{n-1}{2^n}\tag{2} S=221+232+243++2n1n2+2nn1(2)

则有

2 ∗ S = 1 2 + 2 2 2 + 3 2 3 + 4 2 4 + ⋯ + n − 1 2 n − 1 (3) 2*S=\frac{1}{2}+\frac{2}{2^2}+\frac{3}{2^3}+\frac{4}{2^4}+\cdots+\frac{n-1}{2^{n-1}}\tag{3} 2S=21+222+233+244++2n1n1(3)

式(2)和式(3)错位相减,可得到:

S = 1 2 − n − 1 2 n + ( 1 2 2 + 1 2 3 + ⋯ + 1 2 n − 1 ) (4) S=\frac{1}{2}-\frac{n-1}{2^n}+(\frac{1}{2^2}+\frac{1}{2^3}+\cdots+\frac{1}{2^{n-1}})\tag{4} S=212nn1+(221+231++2n11)(4)

简化后,为: S = 1 − n + 1 2 n ≈ 1 S=1-\frac{n+1}{2^n}\approx1 S=12nn+11。因此,式(1)所示的时间复杂度为 O ( n ) O(n) O(n)

2、从堆中弹出最小值

此操作即对应上述的==deleteMin()==函数。

堆排序的整体代码为:

int[] heap;
int size;

private void down(int u){
    int t = u;
    if(2 * u <= size && heap[2 * u] < heap[t]) t = 2 * u;
    if(2 * u + 1 <= size && heap[2 * u + 1] < heap[t]) t = 2 * u + 1;
    if(t != u){
        int tmp = heap[u];
        heap[u] = heap[t];
        heap[t] = tmp;
        down(t);
    }
}

public static int deleteMin(){
    int min = heap[1];
    heap[1] = heap[size --];
    down(1);
    return min;
}

public void heap_sort(int[] a){
    int n = a.length;
    heap = new int[n + 10];
    size = n;
    
    // 建堆
    for(int i = 0; i < n; i ++) heap[i + 1] = a[i];
    for(int i = size >> 1; i >= 0; i --) down(i);
    
    // 弹出堆的最小值,并打印出来,打印出来的结果为数组a从小到大的排序结果
    for(int i = 0; i < size; i ++) System.out.print(deleteMin() + " ");
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值