哨兵,顾名思义,是用来解决国家之间边界问题的,不直接参与生产活动。
同样,计算机科学中提到的哨兵,也用来解决边界问题。
在许多算法中,存在“邻居依赖问题”(我自己造的词),在处理当前元素时,要涉及到它旁边那个元素。那如果当前元素是边界元素呢,它没有旁边那个元素,如果不作处理,程序就可能出错;如果对它特别对待,就会增加代码复杂性,还会降低程序效率。应用哨兵,也就是申请若干个多余的元素作为边界元素的邻居,可以完美得解决这个问题。
下面,我们会举一些哨兵应用的例子。
链表
单链表在插入和删除时,需要修改前驱结点的后继指针,这就形成了“邻居依赖”,链表中第一个元素没有前驱结点,如果没有特殊处理,在插入和删除第一个结点时,就会出错。
所以我们可以申请一个头结点,作为原本的第一个结点的前驱结点,问题也就解决了。但是在这种方式中,我们要插入或者删除一个结点时,要知道它的前驱结点地址,这往往是麻烦的。
另一个方式,也是我更喜欢的方式,是申请一个尾结点,作为原本最后一个结点的后继结点。
要删除某个元素时,我们不删除当前这个结点,而是用后继结点的数据覆盖当前结点的数据,再删除后继结点。这种方式,不需要访问前驱结点,也就解决了获取前驱结点的困难。插入元素也是同理。而最后一个结点没有后继结点,所以需要一个尾结点作为哨兵。
如果用的是双链表,就需要在头尾各加一个哨兵。
其它
在插入排序和归并排序中,使用一个值为无穷大或者负无穷大的哨兵元素,能降低代码复杂性,提高程序运行效率。
在二叉树中,插入删除元素时也会表现出“邻居依赖”,也可以通过哨兵解决。
在n*m的矩形区域中,例如扫雷,一个点击方块时,要扫描周围8个方块的雷数,而边界方块的周围不足8个方块,一种解决方法就是在有效矩形区域的周围,添加一圈的方块,
但这个方法申请的哨兵数量有点多,数量是2n+2m-4,在实践中应该酌情考虑。
可以使用哨兵的地方还有很多,只要存在“邻居依赖”的地方,就可以考虑使用哨兵。