基于C语言实现堆的一些操作

堆是一个完全二叉树,堆分两种,一种为小堆,一种为大堆
小堆是指对于这个树的任意任意一个子树来说,子树的根节点都要小于左右孩子节点的值,小堆的根节点是这个树的最小元素
堆是指对于这个树的任意任意一个子树来说,子树的根节点都要大于左右孩子节点的值,大堆的根节点是这个树的最大元素
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 }                                                                                                         

猜你喜欢

转载自blog.csdn.net/l_x_y_hh/article/details/80299508
今日推荐