一首回文歌 —— 茶道

回文

回文指的是把相同的词汇或句子 ,在下文中调换位置或颠倒过来,产生首尾回环的情趣,也叫回环。回文并非是中文独有,但因为汉字可以单独成意,汉语词可以在不改变形态(写法)的情况下同时具备多种语法属性等,使得回文诗成为中华文化中独有的一朵奇葩。

风舞艳花落雪
流 & nbsp;             飞
雾 & nbsp;             芳
香 & nbsp;             树
迷 & nbsp;             幽
月薄霞淡雨红

著名的茶壶回文诗

茶壶回文诗,依次左循环,可得诗 20 首。

  1. 落雪飞芳树,幽红雨淡霞,薄月迷香雾,流风舞艳花。
  2. 雪飞芳树幽,红雨淡霞薄,月迷香雾流,风舞艳花落。
  3. 飞芳树幽红,雨淡霞薄月,迷香雾流风,舞艳花落雪。
  4. 芳树幽红雨,淡霞薄月迷,香雾流风舞,艳花落雪飞。
  5. 树幽红雨淡,霞薄月迷香,雾流风舞艳,花落雪飞芳。
  6. 幽红雨淡霞,薄月迷香雾,流风舞艳花,落雪飞芳树。
  7. 红雨淡霞薄,月迷香雾流,风舞艳花落,雪飞芳树幽。
  8. 雨淡霞薄月,迷香雾流风,舞艳花落雪,飞芳树幽红。
  9. 淡霞薄月迷,香雾流风舞,艳花落雪飞,芳树幽红雨。
  10. 霞薄月迷香,雾流风舞艳,花落雪飞芳,树幽红雨淡。
  11. 薄月迷香雾,流风舞艳花,落雪飞芳树,幽红雨淡霞。
  12. 月迷香雾流,风舞艳花落,雪飞芳树幽,红雨淡霞薄。
  13. 迷香雾流风,舞艳花落雪,飞芳树幽红,雨淡霞薄月。
  14. 香雾流风舞,艳花落雪飞,芳树幽红雨,淡霞薄月迷。
  15. 雾流风舞艳,花落雪飞芳,树幽红雨淡,霞薄月迷香。
  16. 流风舞艳花,落雪飞芳树,幽红雨淡霞,薄月迷香雾。
  17. 风舞艳花落,雪飞芳树幽,红雨淡霞薄,月迷香雾流。
  18. 舞艳花落雪,飞芳树幽红,雨淡霞薄月,迷香雾流风。
  19. 艳花落雪飞,芳树幽红雨,淡霞薄月迷,香雾流风舞。
  20. 花落雪飞芳,树幽红雨淡,霞薄月迷香,雾流风舞艳。

当然因为回文的要求,对语法、韵律和意向有诸多限制。因此,回文诗词多为游戏、炫技之作,精品可遇不可求。

茶道

演唱:少司命
作词:凉子
作曲:少司命
编曲 & 后期:灰原穷

茶道 海报
这首《茶道》大体比较通顺,有出彩的地方,例如

山晚樵渔 回环 谁遣月岚
岚月遣谁 环回 渔樵晚山

部分也有牵强的感觉。如下方歌词的第二句。

黄昏又舟上 话别
堂辞后驻杖 杖驻后辞堂
别话 上舟又昏黄

当然听歌就曲,总体来说这首歌还是不错的,更多的分析可以看知乎的这篇回答

检测

根据回文的定义(顺读和倒读一样),可以很快通过编程实现回文字符串或数组的检测。

一个思路是,设计两个指针,分别从头尾像中间前进。比较遇到的字符。若相遇前都相同,则说明回文成立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int palindrome(char *str, int length)    
{
char *p,*q;
p = str;
q = str+length-1;
while(length)
{
if(*p == *q)
return 1;
else
return 0;
p ++;
q --;
}

}

当然,根据回文串的定义,其实有更直观的方法,既将所给串(数组)逆置,在和原串比较。这种方法耗费空间较多,但借助一些语言自带的库可以很快实现。

1
2
3
4
5
def palindrome(list):
if list[::-1] == list:
return True
else:
return False

不过单纯的检测一个串是否回文意义不大。现实问题中,更多的需要是检查一个串中的最大回文子串。这里展示一种时间复杂度为 $O (n)$ 的 Manacher 算法。详细原理会在专门的文章中介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
ublic static int getPalindromeLength(String str) {
// 1.构造新的字符串
// 为了避免奇数回文和偶数回文的不同处理问题,在原字符串中插入'#',将所有回文变成奇数回文
StringBuilder newStr = new StringBuilder();
newStr.append('#');
for (int i = 0; i < str.length(); i ++) {
newStr.append(str.charAt(i));
newStr.append('#');
}

// rad[i]表示以i为中心的回文的最大半径,i至少为1,即该字符本身
int [] rad = new int[newStr.length()];
// right表示已知的回文中,最右的边界的坐标
int right = -1;
// id表示已知的回文中,拥有最右边界的回文的中点坐标
int id = -1;
// 2.计算所有的rad
// 这个算法是O(n)的,因为right只会随着里层while的迭代而增长,不会减少。
for (int i = 0; i < newStr.length(); i ++) {
// 2.1.确定一个最小的半径
int r = 1;
if (i <= right) {
r = Math.min(rad[id] - i + id, rad[2 * id - i]);
}
// 2.2.尝试更大的半径
while (i - r >= 0 && i + r < newStr.length() && newStr.charAt(i - r) == newStr.charAt(i + r)) {
r++;
}
// 2.3.更新边界和回文中心坐标
if (i + r - 1> right) {
right = i + r - 1;
id = i;
}
rad[i] = r;
}

// 3.扫描一遍rad数组,找出最大的半径
int maxLength = 0;
for (int r : rad) {
if (r > maxLength) {
maxLength = r;
}
}
return maxLength - 1;
}