从堆到策略模式到C语言泛型

最近在准备C语言的考试,众所周知C语言没有C++提供的很多数据结构,而堆就是刷题中经常会用到的数据结构,这篇文章将介绍如何快速实现一个堆。

#

堆就是一种特殊的完全二叉树,如果父节点大于其左右孩子,称为大根堆,如果父节点小于其左右孩子,成为小根堆

可以将堆通过二叉树的层次遍历的方式映射到数组中,如果父节点的下标为i,则其左孩子的下标为2i + 1,右孩子的下标为2i + 2。如果得到其左右孩子的下标为i,则父节点的下标为(i - 1)/2

我们通常可以使用数组来实现堆。

#堆的插入

当一个新元素到来的时候,我们通常将其插入完全二叉树的最后一个节点,在数组表示中即数组最后一个元素,然后将其上浮到合适的位置。

上浮的原理就是,将新插入的值同其父节点的值做比较,如果是大根堆且新插入的值比父节点的值大,则同父节点做交换,直至上浮到合适的位置,即新插入的值比当前父节点的值小,或者到根节点。

#堆的弹出

在弹出时通常将根节点弹出,然后将最后一个节点移动到根节点的位置,最后将根节点下沉到合适的位置。

下沉的原理就是,如果是大根堆,将父节点的值同左右孩子中最大的孩子作比较,如果父节点的值小于最大的孩子,则将其与最大的孩子做交换,直到到达合适的位置,即新插入的值大于其左右孩子。

#简单int堆

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int *data;
    int size;
    int capacity;
} Heap;

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

Heap *HeapInit(int capacity) {
    Heap *h = malloc(sizeof(Heap));
    h->data = malloc(sizeof(int) * capacity);
    h->size = 0;
    h->capacity = capacity;
    return h;
}

void HeapFree(Heap *h) {
    free(h->data);
    free(h);
}

/*******
 * 参数:h:堆指针
 *    idx:要上浮的元素下标
 * 返回值:上浮后的元素下标
 * 说明:将下标为idx的元素上浮到合适位置
 *******/
void heap_up(Heap *h, int idx) {
    while (idx > 0) {
        int parent = (idx - 1) >> 1;
        if (h->data[idx] < h->data[parent]) {
            swap(&h->data[idx], &h->data[parent]);
            idx = parent;
        } else
            break;
    }
}

/******
 * 参数:h:堆指针
 *    idx:要下沉的元素下标
 * 返回值:下沉后的元素下标
 * 说明:将下标为idx的元素下沉到合适位置
 *******/
void heap_down(Heap *h, int idx) {
    while (1) {
        int l = (idx << 1) + 1, r = (idx << 1) + 2, min = idx;
        if (l < h->size && h->data[min] > h->data[l]) {
            min = l;
        }
        if (r < h->size && h->data[min] > h->data[r]) {
            min = r;
        }
        if (min != idx) {
            swap(&h->data[min], &h->data[idx]);
            idx = min;
        } else
            break;
    }
}

void *HeapPush(Heap *h, int ele) {
    if (h->size == h->capacity) {
        h->capacity *= 2;
        h->data = realloc(h->data, h->capacity * sizeof(int));
    }
    h->data[h->size] = ele;
    heap_up(h, h->size);
    h->size++;
}

int HeapPop(Heap *h) {
    int ret = h->data[0];
    h->data[0] = h->data[--h->size];
    heap_down(h, 0);
    return ret;
}

int main() {
    // Test Heap
    Heap *h = HeapInit(4);
    int testHeapArr[] = {512, 343, 81, 154, 142, 742, 14, 51};
    for (int i = 0; i < sizeof(testHeapArr) / sizeof(testHeapArr[0]); i++) {
        HeapPush(h, testHeapArr[i]);
    }
    printf("Heap elements in sorted order:\n");
    while (h->size > 0) {
        printf("%d ", HeapPop(h));
    }
    printf("\n");
    HeapFree(h);

    return 0;
}
1
2
Heap elements in sorted order:
14 51 81 142 154 343 512 742

#策略模式

  1. 策略模式是什么

在设计模式里,策略模式(Strategy Pattern) 就是: 把可变的算法逻辑单独抽象出来,让它在运行时可以被替换。

比如:

  • 排序算法:冒泡 / 快排 / 归并,都是“排序策略”。
  • 堆的比较:小根堆 / 大根堆,其实就是“比较策略”。
  • 加密:AES / RSA / DES,也都可以看成不同“加密策略”。

核心思想就是:
不在核心代码里写死if-else,而是把算法逻辑抽象成接口,然后在外部注入不同实现。

  1. C语言如何实现策略模式

C没有接口和类,但有函数指针。 函数指针就是一块“可替换的行为”,天然契合策略模式。

例如堆的比较:

1
typedef int (*HeapCmp)(const void *a, const void *b)

这就是一个“策略接口”。

  • 小根堆策略:cmp_int_asc
  • 大根堆策略:cmp_int_desc

堆本身并不关心怎么比较,它只是调用:

1
if (h->cmp(x, y) < 0) ...

至于这个<0是啥意思,取决于传进来的函数指针。

总结:函数指针 = 可插拔的算法/行为,调用方依赖的是接口(函数指针签名),而不是具体实现,这就是策略模式。

策略模式重构:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <stdio.h>
#include <stdlib.h>

typedef int (*HeapCmp)(const void *a, const void *b);

int cmp_int_asc(const void *pa, const void *pb) {
    int a = *(int *)pa;
    int b = *(int *)pb;
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

int cmp_int_desc(const void *a, const void *b) { return -cmp_int_asc(a, b); }

typedef struct {
    int *data;
    int size;
    int capacity;
    HeapCmp cmp; // 比较函数指针
} Heap;

void swap(int *a, int *b) {
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

Heap *HeapInit(int capacity, HeapCmp cmp) {
    Heap *h = malloc(sizeof(Heap));
    h->data = malloc(sizeof(int) * capacity);
    h->size = 0;
    h->capacity = capacity;
    h->cmp = cmp;
    return h;
}

void HeapFree(Heap *h) {
    free(h->data);
    free(h);
}

/*******
 * 参数:h:堆指针
 *    idx:要上浮的元素下标
 * 返回值:上浮后的元素下标
 * 说明:将下标为idx的元素上浮到合适位置
 *******/
void heap_up(Heap *h, int idx) {
    while (idx > 0) {
        int parent = (idx - 1) >> 1;
        if (h->cmp(&h->data[idx], &h->data[parent]) < 0) {
            swap(&h->data[idx], &h->data[parent]);
            idx = parent;
        } else
            break;
    }
}

/******
 * 参数:h:堆指针
 *    idx:要下沉的元素下标
 * 返回值:下沉后的元素下标
 * 说明:将下标为idx的元素下沉到合适位置
 *******/
void heap_down(Heap *h, int idx) {
    while (1) {
        int l = (idx << 1) + 1, r = (idx << 1) + 2,
            best = idx; // min改名best,意味选最大选最小
        if (l < h->size && h->cmp(&h->data[best], &h->data[l]) > 0) {
            best = l;
        }
        if (r < h->size && h->cmp(&h->data[best], &h->data[r]) > 0) {
            best = r;
        }
        if (best != idx) {
            swap(&h->data[best], &h->data[idx]);
            idx = best;
        } else
            break;
    }
}

void *HeapPush(Heap *h, int ele) {
    if (h->size == h->capacity) {
        h->capacity *= 2;
        h->data = realloc(h->data, h->capacity * sizeof(int));
    }
    h->data[h->size] = ele;
    heap_up(h, h->size);
    h->size++;
}

int HeapPop(Heap *h) {
    int ret = h->data[0];
    h->data[0] = h->data[--h->size];
    heap_down(h, 0);
    return ret;
}

int main() {
    // Test Min Heap
    Heap *h = HeapInit(4, cmp_int_asc); // 创建一个容量为4的最小堆
    int testHeapArr[] = {512, 343, 81, 154, 142, 742, 14, 51};
    for (int i = 0; i < sizeof(testHeapArr) / sizeof(testHeapArr[0]); i++) {
        HeapPush(h, testHeapArr[i]);
    }
    printf("Min Heap elements in sorted order:\n");
    while (h->size > 0) {
        printf("%d ", HeapPop(h));
    }
    printf("\n");
    HeapFree(h);

    // Test Max Heap
    Heap *h_max = HeapInit(4, cmp_int_desc); // 创建一个容量为4的最大堆
    for (int i = 0; i < sizeof(testHeapArr) / sizeof(testHeapArr[0]); i++) {
        HeapPush(h_max, testHeapArr[i]);
    }
    printf("Max Heap elements in sorted order:\n");
    while (h_max->size > 0) {
        printf("%d ", HeapPop(h_max));
    }
    printf("\n");
    HeapFree(h_max);
    return 0;
}

输出结果:

1
2
3
4
Min Heap elements in sorted order:
14 51 81 142 154 343 512 742 
Max Heap elements in sorted order:
742 512 343 154 142 81 51 14 

#泛型编程

现在我们的堆只支持int类型,如果我想要其他类型的,比如结构体,又要重构会很麻烦。

C语言中的void*指针支持指向任何类型的数据,然后通过类型转换可以转换为任意想要的类型。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#include <stdio.h>
#include <stdlib.h>

typedef void *HeapEle;
typedef int (*HeapCmp)(const void *a, const void *b);

int cmp_int_asc(const void *pa, const void *pb) {
    int *a = *(int **)pa;
    int *b = *(int **)pb;
    if (*a < *b) {
        return -1;
    } else if (*a > *b) {
        return 1;
    } else {
        return 0;
    }
}

int cmp_int_desc(const void *a, const void *b) { return -cmp_int_asc(a, b); }

typedef struct {
    int point; // 得分
    int win;   // 胜场
} Player;

int cmp_player_desc(const void *pa, const void *pb) {
    Player *a = *(Player **)pa;
    Player *b = *(Player **)pb;
    if (a->point != b->point) {
        return b->point - a->point; // 按得分降序
    } else {
        return b->win - a->win; // 得分相同按胜场多的排前面
    }
}

typedef struct {
    HeapEle *data;
    int size;
    int capacity;
    HeapCmp cmp; // 比较函数指针
} Heap;

void swap(HeapEle *a, HeapEle *b) {
    HeapEle tmp = *a;
    *a = *b;
    *b = tmp;
}

Heap *HeapInit(int capacity, HeapCmp cmp) {
    Heap *h = malloc(sizeof(Heap));
    h->data = malloc(sizeof(HeapEle) * capacity);
    h->size = 0;
    h->capacity = capacity;
    h->cmp = cmp;
    return h;
}

void HeapFree(Heap *h) {
    if (!h)
        return;
    free(h->data);
    free(h);
}

/*******
 * 参数:h:堆指针
 *    idx:要上浮的元素下标
 * 返回值:上浮后的元素下标
 * 说明:将下标为idx的元素上浮到合适位置
 *******/
void heap_up(Heap *h, int idx) {
    while (idx > 0) {
        int parent = (idx - 1) >> 1;
        if (h->cmp(&h->data[idx], &h->data[parent]) < 0) {
            swap(&h->data[idx], &h->data[parent]);
            idx = parent;
        } else
            break;
    }
}

/******
 * 参数:h:堆指针
 *    idx:要下沉的元素下标
 * 返回值:下沉后的元素下标
 * 说明:将下标为idx的元素下沉到合适位置
 *******/
void heap_down(Heap *h, int idx) {
    while (1) {
        int l = (idx << 1) + 1, r = (idx << 1) + 2,
            best = idx; // min改名best,意味选最大选最小
        if (l < h->size && h->cmp(&h->data[best], &h->data[l]) > 0) {
            best = l;
        }
        if (r < h->size && h->cmp(&h->data[best], &h->data[r]) > 0) {
            best = r;
        }
        if (best != idx) {
            swap(&h->data[best], &h->data[idx]);
            idx = best;
        } else
            break;
    }
}

void *HeapPush(Heap *h, HeapEle ele) {
    if (h->size == h->capacity) {
        h->capacity *= 2;
        h->data = realloc(h->data, h->capacity * sizeof(HeapEle));
    }
    h->data[h->size] = ele;
    heap_up(h, h->size);
    h->size++;
}

HeapEle HeapPop(Heap *h) {
    HeapEle ret = h->data[0];
    h->data[0] = h->data[--h->size];
    heap_down(h, 0);
    return ret;
}

int main() {
    // Test Int Heap
    Heap *h = HeapInit(4, cmp_int_asc); // 创建一个容量为4的最小堆
    int testIntHeapArr[] = {512, 343, 81, 154, 142, 742, 14, 51};
    for (int i = 0; i < sizeof(testIntHeapArr) / sizeof(testIntHeapArr[0]);
         i++) {
        HeapPush(h, &testIntHeapArr[i]);
    }
    printf("Int in sorted order:\n");
    while (h->size > 0) {
        int *val = (int *)HeapPop(h);
        printf("%d ", *val);
    }
    printf("\n");
    HeapFree(h);

    // Test Player Heap
    Heap *ph = HeapInit(4, cmp_player_desc); // 创建一个容量为4的最大堆
    Player players[] = {{14, 5}, {12, 3}, {12, 7}, {20, 10}, {15, 5}, {10, 5}};
    for (int i = 0; i < sizeof(players) / sizeof(players[0]); i++) {
        HeapPush(ph, &players[i]);
    }
    printf("Players in sorted order (by point desc, win desc):\n");
    while (ph->size > 0) {
        Player *p = (Player *)HeapPop(ph);
        printf("Point: %d, Win: %d\n", p->point, p->win);
    }
    HeapFree(ph);
    return 0;
}

输出结果:

1
2
3
4
5
6
7
8
9
Int in sorted order:
14 51 81 142 154 343 512 742 
Players in sorted order (by point desc, win desc):
Point: 20, Win: 10
Point: 15, Win: 5
Point: 14, Win: 5
Point: 12, Win: 7
Point: 12, Win: 3
Point: 10, Win: 5
updatedupdated2025-09-222025-09-22