C语言实现区间堆,特定输入下前序/中序遍历打印结果异常
抱歉我的英语不太好,我的母语是韩语。
我刚学习了区间堆(interval heap)的相关知识,正在用C语言实现区间堆。
我的代码预期逻辑为:从输入文件读取整数,若读取的整数为正,则将该值插入区间堆;若为负,则执行删除最小值操作。每次完成插入或删除操作后,需要将区间堆的前序(preorder)、中序(inorder)遍历结果打印到标准输出。
完整代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct ipair_{ int min; //inserted first int max; int cnt; //number of data in node } interval; typedef struct{ interval * h; int size; int cnt; //number of node } *iheap; typedef enum{ NO_EMPTY=0, EMPTY_HEAP } isEmpty; void init_heap(iheap h, int n) { //initialize heap; n++; // 1_based indices h->h = calloc(n, sizeof *h->h); h->size = n; h->cnt = 0; } //parent, child for 1_based_index inline static int prt(int i) { return i/2; } inline static int lch(int i) { return i*2; } inline static int rch(int i) { return i*2+1; } void insert_aux(iheap h, int cur, int data) { //insert to h->h[cur] in accordance with state of node; interval *current = h->h + cur; if((*current).cnt == 0) { (*current).min = data; (*current).cnt++; } //else if(h->h[h->cnt].cnt == 1) else if((*current).cnt == 1) { int prev = (*current).min; if(data < prev) { (*current).min = data; (*current).max = prev; } else { (*current).max = data; } (*current).cnt++; } else// if((*current).cnt >= 2) { current++; (*current).min = data; (*current).cnt++; h->cnt++; } } void insert(iheap h, int data) { int cur = h->cnt + 1; int parent = prt(cur); if(data > h->h[parent].max) { while(data > h->h[parent].max) { if(cur == 1) { break; } //h->h[cur].max = h->h[parent].max; insert_aux(h, cur, h->h[parent].max); cur = parent; parent = prt(parent); } //h->h[cur].max = data; insert_aux(h, cur, data); } else if(data < h->h[parent].min) { while(data < h->h[parent].min) { if(cur == 1) { break; } //h->h[cur].min = h->h[parent].min; insert_aux(h, cur, h->h[parent].min); cur = parent; parent = prt(parent); } //h->h[cur].min = data; insert_aux(h, cur, data); } else { insert_aux(h, cur, data); } } void preorder(iheap h, int i) { if(h->h[i].cnt) { if(h->h[i].cnt == 1) { printf(" (%d)", h->h[i].min); } else { printf(" (%d,%d)", h->h[i].min, h->h[i].max); } preorder(h, lch(i)); preorder(h, rch(i)); } } void printPreorder(iheap h) { printf("preorder:"); preorder(h, 1); putchar(10); } void inorder(iheap h, int i) { if(h->h[i].cnt) { inorder(h, lch(i)); if(h->h[i].cnt == 1) { printf(" (%d)", h->h[i].min); } else { printf(" (%d,%d)", h->h[i].min, h->h[i].max); } inorder(h, rch(i)); } } void printInorder(iheap h) { printf("inorder:"); inorder(h, 1); putchar(10); } isEmpty deleteMin(iheap h, int * buf) { int p, last, cur, temp; _Bool rch_lower; if(!h->cnt) { if(h->h[0].cnt == 0) { return EMPTY_HEAP; } else if(h->h[0].cnt == 1) { *buf = h->h[0].min; h->h[0].cnt--; return NO_EMPTY; } } *buf = h->h[0].min; last = h->cnt + 1; p = h->h[last].min; h->h[last].cnt--; if(h->h[last].cnt == 0) { h->cnt--; } cur = 1; while(1) { if(!h->h[lch(cur)].cnt) { break; } int minch; rch_lower = 0; if(h->h[rch(cur)].cnt == 0) { minch = h->h[lch(cur)].min; } else { if(h->h[lch(cur)].min > h->h[rch(cur)].min) { rch_lower = 1; minch = h->h[rch(cur)].min; } else { minch = h->h[lch(cur)].min; } } if(!(p>minch)) { break; } h->h[cur].min = minch; if(rch_lower) { cur = rch(cur); } else { cur = lch(cur); } if(p > h->h[cur].max) { temp = p; p = h->h[cur].max; h->h[cur].max = temp; } } h->h[cur].min = p; return NO_EMPTY; } int main(int argc, char * * argv) { int * buf; int cnt; int temp; iheap h = malloc(sizeof *h); if(argc != 2) { printf("usage: %s <filename>", argv[0]); exit(EXIT_FAILURE); } FILE *fp = NULL; if((fp = fopen(argv[1], "r")) == NULL) { //file open failed fprintf(stderr, "file open error\n"); exit(EXIT_FAILURE); } //file open succeeded for(cnt=0; ; cnt++) { if((fscanf(fp, "%i", &temp)) == EOF) { break; } } rewind(fp); buf = calloc(cnt, sizeof *buf); for(int i=0; i<cnt; i++) { fscanf(fp, "%i", buf+i); } init_heap(h, cnt); for(int i=0; i<cnt; i++) { if(buf[i] < 0) { if(deleteMin(h, &temp)) { puts("공백 트리"); } else { printPreorder(h); printInorder(h); } } else { insert(h, buf[i]); printPreorder(h); printInorder(h); } } return 0; }
短输入场景下代码运行看似正常,但输入如2 40 3 17 4 32 4 12 3 11 5 10 6 30 4 10 5 11 5 9 4 7 8 8 7 9 15 15 -1的长序列时,会输出异常结果:
... (skipped) ... preorder: (2,40) (3,17) (4,12) (4,10) (5,11) (3,11) (5,9) (4,7) (4,32) (5,10) (8,8) (7,9) (15,30) (10) inorder: (4,10) (4,12) (5,11) (3,17) (5,9) (3,11) (4,7) (2,40) (8,8) (5,10) (7,9) (4,32) (10) (15,30) preorder: (2,40) (3,17) (4,12) (4,10) (5,11) (3,11) (5,9) (4,7) (4,32) (5,10) (8,8) (7,9) (15,30) (10) (15) (1041,0) (1919247474,841490490) inorder: (4,10) (4,12) (5,11) (3,17) (5,9) (3,11) (4,7) (2,40) (8,8) (5,10) (7,9) (4,32) (10) (15,30) (1041,0) (15) (980575588,741615648) preorder: (3,40) (3,17) (4,12) (4,10) (5,11) (4,15) (5,9) (7,11) (4,32) (5,10) (8,8) (7,9) (15,30) (10) inorder: (4,10) (4,12) (5,11) (3,17) (5,9) (4,15) (7,11) (3,40) (8,8) (5,10) (7,9) (4,32) (10) (15,30)
我无法定位代码中的逻辑错误,运行时无编译报错、也没有触发段错误,但运行行为不符合预期,希望能得到帮助排查问题。
1. 堆数组越界(垃圾值直接诱因)
你采用1基索引存储堆节点,初始化时仅为输入总数cnt分配了cnt+1个节点空间,但每个interval节点最多存2个元素,插入逻辑中当前节点存满后会直接访问下一个节点,当元素总数较多时会超出申请的内存范围,读取未初始化的随机值。
修复方案:初始化时扩大节点分配量,改为n = (n/2) + 4即可覆盖所有存储需求,也可直接临时分配2倍输入长度的节点空间验证问题。
2. 根节点索引错误
你的整个堆设计为1基索引,根节点是下标1的位置,但deleteMin函数中全部用下标0访问根节点,读取的是堆数组前未使用的无效内存,导致删除最小值逻辑完全错误。
修复方案:将deleteMin函数中所有h->h[0]替换为h->h[1]。
3. 首次插入计数逻辑缺失
插入第一个元素时,h->cnt没有从0更新为1,导致后续父节点计算的起始位置错误,插入逻辑的上浮流程异常。
修复方案:在insert_aux函数的空节点插入分支中,新增判断如果是根节点首次插入,将h->cnt置为1。
4. 遍历逻辑无边界校验
前序、中序遍历函数仅判断节点cnt是否为0,没有校验当前下标是否超出堆数组的最大长度,下标越界后访问的无效内存如果cnt值非0,会递归输出垃圾值。
修复方案:在preorder和inorder函数的第一行新增判断if (i >= h->size) return;,避免越界访问。
内容的提问来源于stack exchange,提问作者k9wan

