前言
经历一段时间的找实习, 还是深深体会到算法的重要性, 感觉以前没去做做ACM很可惜, 不过也不想太多, 既有个人的原因也有大环境的原因, 最近在看刘汝佳的算法竞赛书, 看到用数组来比较链表和双向链表, 感觉还挺少见, 所以特此来写篇博客记录一下, 加深印象的同时希望也能记住用法.
题目
我和书上一样了, 先上题目, 为了不浪费时间, 我直接上图片了.
这题目如果是我第一次做的话, 我会直接使用链表也就是stl中的list来放置数据, 当然思路是没有问题的, 但是里面会涉及到很多的插入删除操作, 还是比较复杂的.不过在这里, 书的作者给出了一种新的方法, 用的也是链表, 但是用的是基于数组的链表.
思路是每输入一个字符就把它存起来, 设输入字符串是s[1~n], 则可以用next[i]表示在当前显示屏中s[i]右边的字符编号(即在s中的下标).
为了方便起见, 假设字符串s的最前面还有一个虚拟的s[0], 则next[0]就可以表示显示屏中最左边的字符. 再用一个变量cur表示光标位置, 即当前光标位于s[cur]的右边. cur=0说明光标位于”虚拟字符”s[0]的右边, 即显示屏的最左边.为了移动光标, 还需要用一个变量last表示显示屏的最后一个字符是s[last].
思路说起来简单, 但是理解起来倒是不算轻松, 让我们来看看这个算法的核心, next数组, 这里有三个变量, next, i, s, 其中i是索引, s[i]表示当前的字符, next[i]中是下一个字符的在s中的索引, 在这里来看, 很明显, 这是一个链表, next数组就担当了指针的作用. 接下来还有一个小难点, 因为我们是不断读入字符的, 所以next数组在不断放置索引, 也就是说当i = 10的时候, next[1-10]的内容实际上已经固定了, 这个时候如果我们需要移动光标的话, 也就是说遇到’[‘, 我们需要做的不是改变next[1-10]的内容, 而是改变next[0]的内容, 让它指向新的位置, 同时不断的读取新的字符, 如果读取完所有的字符或者遇到’]’, 还不能忘了将next的最后一位置为之前的头部, 比如是1, 因为之前没遇到’[‘之前是从1开始输出的.
为了方便大家理解, 我自己手画了一个结果图, 相信大家看了之后肯定会好理解很多:
可以看出来很明显next[0]储存的是开头的字符的索引, 在这里也就是11, 然后我们的打印就会从s[11]开始, 也就是打印’B’, 然后继续, 我们看到next[11]的值是12, 所以我们打印s[12], 也就是’e’, 后面我就不说了, 依次类推, 稍微值得注意的是中间的几个0我省略了没有画出来, 下面是代码, 在实现上面也有点技巧, 不那么容易理解.
1 |
|
这里值得注意的是last是用来保存上一次未遇到’[‘之前输入的最后一个字符的位置, 如果还是不能理解的话, 我个人建议可以用gdb调试下, 每一步可以输出看看next数组的值, 这样就会理解.
这个例子虽然看起来简单, 但是要真正想到能够用这种方式来解决这个问题还是不简单的, 不过复杂的算法带来的效率提升也是很明显的, 不仅代码量很小, 算法复杂度也是O(n), 我们的付出也是得到了回报.
第二题
下面来看第二题, 用两个数组来表示双向链表,为了不浪费敲字的时间我直接上图片了:
有了前面的经验, 我们使用双向链表也只是举一反三的事情了, 我们用left[i]和right[i]分别表示编号为i的盒子左边和右边的盒子编号(如果是0, 就表示不存在), 然后我们使用下面的这个辅助函数来将两个节点相互连接;
void link(int L, int R)
{
right[L] = R; left[R] = L;
}
这个函数我们简单看来就是将编号为L的盒子的右边设为R盒子, 将编号为R的盒子的左边设为L盒子, 也就是将L盒子和R盒子放到了一起, L盒子在左边, R盒子在右边.
有了这个辅助函数之后, 我们可以先记录好操作之前X, Y两边的结点, 然后用link函数根据某种顺序将他们连起来. 不过值得注意的是操作4, 这个操作我们不容易通过link这个辅助函数来完成, 不过我们可以先不管这个操作, 我们用标记inv来表示我们是否需要执行操作4(如果inv = 1的时候我们再次执行操作4, inv将会变为0). 当然, 操作1-3也需要根据inv是否为1来进行相应的变化, 不过操作3实际上是不受影响的, 大家可以动手画一下来试试, 在翻转链表前后执行交换操作不影响最后结果 不过操作1, 2是会受到影响的, 如果inv是1, 那么操作1,2需要变成3-op就好了, 所以思路并不麻烦.
1 |
|
解释都写在注释上了, 代码也不难理解, 所以这里我也不再过多解释.
总结
在这篇博客里面, 我们总结了用数组来表示链表的两种简单实现, 也打破了我们可能存在一些常规思路, 认为链表只能用指针来实现, 实际上并不是如此, 这些编程方面算法的知识很有趣但是也不容易理解, 需要我们继续努力!