堆是一个完全二叉树,堆分两种,一种为小堆,一种为大堆
小堆是指对于这个树的任意任意一个子树来说,子树的根节点都要小于左右孩子节点的值,小堆的根节点是这个树的最小元素
大
堆是指对于这个树的任意任意一个子树来说,子树的根节点都要大于左右孩子节点的值,大堆的根节点是这个树的最大元素
1.对堆进行初始化操作
其中有一个比较函数Compare,来比较确定是大堆还是小堆
16 //初始化,决定是大堆还是小堆 17 void HeapInit(Heap* heap,Compare cmp) 18 { 19 if(heap == NULL) 20 { 21 //非法输入 22 return ; 23 } 24 heap->size = 0; 25 heap ->cmp = cmp; 26 return ; 27 }
5 //为大堆打造的比较函数 6 int Greater(HeapType a,HeapType b) 7 { 8 return a>b?1:0; 9 } 10 11 //为小堆打造的比较函数 12 int Less(HeapType a,HeapType b) 13 { 14 return a<b?1:0; 15 }
2.销毁堆
注:在销毁堆操作的时候,不能释放内存,因为不是使用malloc来申请的空间
29 //销毁堆 30 //注:此时内存不能释放,因为没有malloc 31 void HeapDestroy(Heap* heap) 32 { 33 if(heap == NULL) 34 { 35 //非法输入 36 return ; 37 } 38 heap->size = 0; 39 heap->cmp = NULL; 40 return ; 41 }
3.向堆中插入元素
向堆中插入一个元素,因为堆是一种完全二叉树,那么就一定满足,插入的元素紧接在最后一个元素的后面,相当于是对数组进行尾插
以下面的堆为例,要将0.5插入到下面的堆中,首先先将要插入的元素0.5插入到原先的堆2的右子树上,由于该堆为小堆,这样的结果不满足小堆的定义,因此要将0.5“上浮”,即与此时位置的父节点交换位置,交换之后发现还是不满足小堆的定义,因此要再次“上浮”,
与此时位置的父节点交换位置,直到满足小堆的定义
首先先要对堆进行合法性判断,使用AdjustUp函数来辅助完成插入操作
72 void HeapInsert(Heap* heap ,HeapType value) 73 { 74 if(heap == NULL) 75 { 76 //非法输入 77 return ; 78 } 79 //相当于对数组进行尾插 80 if(heap->size >= HeapMaxSize) 81 { 82 //堆满 83 return ; 84 } 85 heap->data[heap->size++] = value; 86 //对这个堆进行上浮式调整 87 //调整的起始位置为size-1,即插入的元素 88 AdjustUp(heap,heap->size-1); 89 return ; 90 }
下面为AdjustUp“上浮“函数
首先先要确定父节点与孩子节点的下标,然后若是新插入的节点不能满足堆的定义,就将插入的节点与当前位置的父节点交换位置,直到满足条件为止,
51 void AdjustUp(Heap* heap,size_t index) 52 { 53 //index表示从哪个位置开始调整 54 size_t child = index; 55 size_t parent = (child - 1)/2; 56 while(child > 0) 57 { 58 if(!heap->cmp(heap->data[parent],heap->data[child])) 59 { 60 Swap(&heap->data[parent],&heap->data[child]); 61 } 62 else 63 { 64 //若发现某个位置下的child,parent已满足堆的要求,就停止上浮 65 //因为上面的节点已经满足堆的要求 66 break; 67 } 68 child = parent; 69 parent = (child - 1)/2; 70 } 71 }
45 void Swap(HeapType* a,HeapType* b) 46 { 47 HeapType tmp = *a; 48 *a = *b; 49 *b = tmp; 50 }
4.取堆顶元素
相当于取数组的第一个元素
92 //取堆顶元素 93 int HeapRoot(Heap* heap,HeapType* value) 94 { 95 if(heap == NULL) 96 { 97 //非法输入 98 return 0; 99 } 100 if(heap->size == 0) 101 { 102 //空堆 103 return 0; 104 } 105 *value = heap->data[0]; 106 return 1; 107 }
5.删除堆顶元素
相当于是删除数组的下标为0的元素,但是删除之后还是要求能够满足堆的定义,首先要先将堆顶元素与堆的最后一个元素交换,这样,队首元素就变为了最后一个元素,删除堆顶元素的操作就变成了尾删的操作,但是此时的堆不再满足之前对堆的定义了,要将此时的队首元素进行“下沉”操作来完成,
“下沉”操作:将堆顶元素与其左右子树比较,与较小的子树交换位置,直到满足堆的定义,就停止“下沉”,详细见下图
首先先要对堆进行合法性判断,然后将堆首元素与堆的最后一个元素交换位置,并进行尾删操作,再使用AdjustDown函数将此时的堆首元素进行“下沉”
136 void HeapErase(Heap* heap) 137 { 138 if(heap == NULL) 139 { 140 //非法输入 141 return ; 142 } 143 if(heap->size == 0) 144 { 145 //空堆 146 return ; 147 } 148 //交换堆顶与最后一个元素 149 Swap(&heap->data[0],&heap->data[heap->size-1]); 150 //--size进行尾删 151 --heap->size; 152 //从根节点出发进行下沉 153 AdjustDown(heap,0); 154 return ; 155 }下面为AdjustDown函数将堆首元素进行“下沉”
109 //删除堆顶元素 110 void AdjustDown(Heap* heap,size_t index) 111 { 112 size_t parent = index; 113 size_t child = 2*index+1;//左子树 114 while(child < heap->size) 115 { 116 //child+1表示右子树 117 if(child+1 < heap->size && heap->cmp(heap->data[child+1],heap->data[child])) 118 { 119 //若右子树存在,且右子树比左子树更符合堆的要求 120 //假设为小堆:右子树<做子树,且让child指向右子树 121 child = child+1; 122 } 123 //child指向左右子树中更小的那个元素 124 if(heap->cmp(heap->data[child],heap->data[parent])) 125 { 126 Swap(&heap->data[child],&heap->data[parent]); 127 } 128 else 129 { 130 break; 131 } 132 parent = child; 133 child = 2*parent+1; 134 }
6.给一个数组,将数组构建成堆
遍历数组,再将数组中的元素依次插入到堆中即可
157 //给一个数组,将数组构建成堆,这个堆通过heap来表示 158 //时间复杂度为O(N*logN) 159 void HeapCreate(Heap* heap,HeapType arry[],size_t size) 160 { 161 if(heap == NULL) 162 { 163 return ; 164 } 165 //遍历arry数组,将数组元素依次插入到堆中 166 size_t i = 0; 167 for(;i<size;++i) 168 { 169 HeapInsert(heap,arry[i]); 170 } 171 return; 172 } 173
7.实现堆排序
堆排序就是首先将数组构建成堆,然后循环将堆进行删除操作,循环结束,堆排序就完成了
注:升序要使用大堆,降序要使用小堆
174 //实现堆排序 175 //升序->大堆 176 //降序->小堆 177 void HeapSort(HeapType arry[],size_t size) 178 { 179 //把数组构建成堆 180 Heap heap; 181 HeapInit(&heap,Greater); 182 HeapCreate(&heap,arry,size); 183 //循环将堆进行删除操作 184 while(heap,size>0) 185 { 186 HeapErase(&heap); 187 } 188 //循环结束后,堆排序完成了 189 memcpy(arry,heap.data,size* sizeof(HeapType)); 190 return ; 191 }