C语言快速实现优先队列(排雷)

写在前面

  • C语言没有自带常用数据结构的库,所以当需要使用它们处理问题时必须自己实现它们。
  • 解题时快速并正确实现数据结构的ADT可以为我们节省宝贵的时间去解决真正的问题。
  • 本博客本更像是个人的排坑排雷笔记,讲解不全请见谅,但如果对你有一点启发那就再好不过了。

优先队列

为了简单起见,存放数据的结构使用数组。

  1. 组织序号
    我们知道优先队列,也叫大(小)顶堆(默认为二叉堆)。堆是一棵完全二叉树,但实际上我们使用数组并通过组织序号便能够表示它,这依赖于完全二叉树的良好性质。
    为防止以后出错或造成混乱,实现前一定要先确定好如何组织序号:
  • i n d e x = 0 index = 0 index=0 开始存放数据
    s o n = 2 ⋅ d a d + 1 son = 2\cdot dad+1 son=2dad+1

  • i n d e x = 1 index = 1 index=1 开始存放数据(此时数组的第一个位置不要存放元素)
    s o n = 2 ⋅ d a d son = 2\cdot dad son=2dad

    (1)上面的 s o n son son均为左儿子,因为实际情况右儿子可能不存在,而且显然 s o n r i g h t = s o n l e f t + 1 son_{right}=son_{left}+1 sonright=sonleft+1
    (2)由 s o n son son d a d dad dad 经上式变形注意取整问题即可

    这些规律根据完全二叉树的性质可以严谨的证明出来,但为了快捷,可以联想哈弗曼二进制编码,以 i n d e x = 1 index = 1 index=1 开始存放数据为例
    C语言快速实现优先队列(排雷)_第1张图片
    观察可发现
    (1)向左子树走在末尾添 0 0 0,向右子树走添加 1 1 1
    (2) × 2 \times2 ×2操作在二进制下即添加 0 0 0,也就是找左儿子,对应式子 s o n l e f t = 2 ⋅ d a d son_{left} = 2\cdot dad sonleft=2dad
    (3)如果是从 i n d e x = 0 index=0 index=0开始存,仅需在记忆 i n d e x = 1 index=1 index=1 的情况下 + 1 +1 +1即可

  1. 建堆操作
    i n d e x = 0 index=0 index=0 起始建立小顶堆为例
// start 起始索引 end 末尾索引
// min_heapify 在更新根节点后维护整个堆的堆序性
void min_heapify(ElementType* elements, int start, int end){
    int dad, son;//左儿子
	
	for(dad = start;2*dad + 1 <= end;dad = son){
		son = 2*dad + 1;
		//找到更小的儿子
		if(son+1 <= end && elements[son+1]<elements[son])
			son ++;
		if(elements[dad] < elements[son]) return;
		//进行下渗操作
		else swap(&elements[dad], &elements[son]);		
	}	
}

void createHeap(ElementType* elements, int elementsSize){
	int i;
	for(i = elementsSize/2 - 1;i >= 0;i --)
		min_heapify(elements, i, elementsSize - 1);
}
  • 注意min_heapify的作用以及调用它的前置条件:
    (1)min_heapify的作用是在更新根节点后维护整个堆的堆序性
    (2)默认此刻根节点的左子树和右子树都满足堆序性(都是小顶堆)
  • 无论插入还是删除元素,都有以下共同临界的判断:
    (1) s o n < = e n d son <= end son<=end ,其中 s o n son son表示左儿子, e n d end end表示最后一个元素的索引
    (2)插入或者维护堆序性时,容易在偶数个节点时出错,及注意下面的判断:
    s o n + 1 < = e n d son+1 <= end son+1<=end,在找更小节点时,右儿子可能压根不存在!
/*(1)对应*/					2*dad + 1 <= end
/*(2)对应*/		if(son+1 <= end && elements[son+1]<elements[son])
  • 创建堆
    (1)从第一个非叶子节点开始至根节点,逐一维护它们的堆序性(先保证是堆,再来向上扩展)
    (2)如何计算第一个非叶子节点?建议根据父子序号关系从简单情况找规律,还要注意是使用索引还是数组长度计算。(如果规定用数组长度(与节点个数无关,只看“数组”)可以统一)
    l a s t = l e n / 2 − 1 last=len/2 -1 last=len/21
/*对应*/				i = elementsSize/2 - 1
  1. 删除操作(deleteMin
//确保调用时 堆 非空
ElementType deleteMin(ElementType* elements, int elementsSize){
	int empty, son;//empty为空穴 son为空穴的左子
	ElementType min = elements[0], lastone = elements[--elelementsSize - 1];

	for(empty = 0;empty*2+1 < elementsSize;empty = son){
		son = 2*empty + 1;
		//找到更小的儿子
		if(son+1 < elementsSize && elements[son+1]<elements[son])
			son ++;
		if(lastone < elements[son]) break; 
		else elements[i] = elements[son]; //上溢
	}
	elements[i] = lastone;

	return min;
}
  • 上面提到的临界判断依旧
  • 删除最容易出错的地方就是,如何确保正确的补充空穴
    l a s t o n e lastone lastone 指最后一个元素
for{
	//.....
	if(lastone < elements[son]) break; 
		else elements[i] = elements[son]; //上溢
	/......
}
elements[i] = lastone;

你可能感兴趣的:(数据结构,算法)