堆的 C 语言实现

    技术2024-07-03  70

    堆是一种特殊的完全二叉树,其父元素总是比两个子元素大(小),根据这个特性我们可以用堆实现优先队列。

    这里使用数组模拟堆。

    上浮

    新加入的元素为堆底元素,为了保持堆的性质,需要比较新加入的元素的值和根节点的值的大小,这里用大根堆举例。大根堆:父节点的值总是比两个子节点的值要大。

    如果新加入的元素的值比父节点要大,交换他们的值,一直执行该操作直到到达堆顶或满足堆的性质。

    代码如下:

    // 堆的上浮操作 // 新加入的元素默认在堆底,通过跟根元素比较大小来实现上浮 void swim(int idx, CMP_FUNC cmp_func){ while(idx > 1 && cmp_func(heap[idx], heap[idx>>1])){ exch(&heap[idx], &heap[idx>>1]); idx >>= 1; } }

    下沉

    这里仍然用大根堆举例。

    当堆顶元素被取出后,为了保持堆的性质,需要找到堆中第二大的元素来代替堆顶元素,找是不难找,但是这样一来又会破坏堆的结构,一个解决方法是用堆底元素(第二小或最小的元素)来代替堆顶元素,然后将其和其两个子元素中较大的那个进行比较,使堆恢复性质。

    代码如下:

    // 下沉操作 // 先将堆顶元素(最大/小元素)和堆中最后一个元素交换(最后一个元素绝对是第一/第二小的值) // 然后通过值的比较交换元素实现下城、沉 void sink(int idx, CMP_FUNC cmp_func){ while((idx << 1) <= curr_idx){ // 如果还有下一层 int j = idx << 1; if(j < curr_idx && cmp_func(heap[j+1], heap[j])){ // j 始终为下一层中最大/最小的元素 j++; } if(!cmp_func(heap[j], heap[idx])){ // 如果当前元素已经比根大/小了,表明堆已经有序,结束 return ; } // 交换 exch(&heap[j], &heap[idx]); idx = j; } }

    插入/删除

    插入用到了先前讲到的上浮操作,删除用到了先前讲到的下沉操作,代码如下:

    // 插入 // 在堆底插入元素,然后使该元素上浮 void insert(int v){ heap[++curr_idx] = v; swim(curr_idx, cmp); } // 取堆顶元素 int pop(){ int v = heap[1]; exch(&heap[1], &heap[curr_idx--]); // 交换堆顶和堆底的元素 heap[curr_idx + 1] = 0; sink(1, cmp); // 下沉 return v; }

    上面是之前的代码,这里有个问题在于如果只是想查看堆顶元素 pop 会有删除元素的副作用,可以定义一个 peek 函数用于返回堆顶值,而 pop 专门用来删除堆顶元素(类似 C++ 中的 front 和 pop)。

    完整代码

    // 《算法4》中堆的 C 语言实现 #include <stdio.h> // 数组大小(堆大小) const int N = 1e5; // curr_idx 用来保存当前元素下标,从 1 开始计数 int heap[N], curr_idx; // 比较函数 typedef int (*CMP_FUNC)(int a, int b); // 将大于号改成小于号即可实现小根堆 int cmp(int a, int b){ return a > b; } // 交换两个整型 void exch(int *a, int *b){ int temp = *a; *a = *b; *b = temp; } // 堆的上浮操作 // 新加入的元素默认在堆底,通过跟根元素比较大小来实现上浮 void swim(int idx, CMP_FUNC cmp_func){ while(idx > 1 && cmp_func(heap[idx], heap[idx>>1])){ exch(&heap[idx], &heap[idx>>1]); idx >>= 1; } } // 下沉操作 // 先将堆顶元素(最大/小元素)和堆中最后一个元素交换(最后一个元素绝对是第一/第二小的值) // 然后通过值的比较交换元素实现下城、沉 void sink(int idx, CMP_FUNC cmp_func){ while((idx << 1) <= curr_idx){ // 如果还有下一层 int j = idx << 1; if(j < curr_idx && cmp_func(heap[j+1], heap[j])){ // j 始终为下一层中最大/最小的元素 j++; } if(!cmp_func(heap[j], heap[idx])){ // 如果当前元素已经比根大/小了,表明堆已经有序,结束 return ; } // 交换 exch(&heap[j], &heap[idx]); idx = j; } } // 插入 // 在堆底插入元素,然后使该元素上浮 void insert(int v){ heap[++curr_idx] = v; swim(curr_idx, cmp); } // 取堆顶元素 int pop(){ int v = heap[1]; exch(&heap[1], &heap[curr_idx--]); // 交换堆顶和堆底的元素 heap[curr_idx + 1] = 0; sink(1, cmp); // 下沉 return v; } int main(){ insert(0); insert(2); insert(1); insert(4); printf("%d\n", pop()); // 4 printf("%d\n", pop()); // 2 return 0; }
    Processed: 0.011, SQL: 9