1. 问题
商人要访问5个城市,问按何种顺序访问所费的时间最短?
2. 问题分析
2.1 最直观的解法
遍历所有的访问顺序,然后计算出所费时间最短的一种,那么问题转化为如何遍历5个城市所有的访问顺序
假设5个城市编号分别为{0, 1, 2, 3, 4},那么问题实际上是一个全排列问题,即这5个数能组成多少种排列顺序,如0→1→2→3→4是一种,4→3→2→1→0也是一种。
学过排列组合立即可以算出来:5! = 120
但最重要的是如何把这120中组合遍历出来
3. 编程的角度思考这个问题
3.1 思路
第0次访问的城市有5中选择方法,
第1次访问的城市有4中选择方法
…
所以只要在第n次访问时,遍历当前还未访问的城市,然后再访问下一个城市,最后到所有的城市都已访问即可。
这实际上是个递归的思路,第0到4次的访问城市的方法都可用上面的描述处理。
故:
递归的终止条件:所有的城市已被访问
每次递归的步骤:遍历尚未访问的城市,设置一个为已访问,让后进行下一次递归
3.2 python code
def recursion_visit_order(visit_citys, index = 0): """in visit_citys, index : city number, value : visit index, if not visited, visit index is -1""" #index = visit index visited_num = 0 for i, city in enumerate(visit_citys): if -1 == city: new_visit_citys = list(visit_citys) new_visit_citys[i] = index recursion_visit_order(new_visit_citys, index + 1) else: visited_num += 1 #if all city is visited, print the visit order if visited_num == len(visit_citys): global total_visit_order_num total_visit_order_num += 1 print visit_citys
4. 《离散数学及其应用》书中”计数”一章中的方法
4.1 思路
将城市访问的顺序”0→1→2→3→4”看着一个数字:01234,因为数字有大小(良序性),那么所有访问顺序组成的数字必可按照由小到大组成一个序列,那么按照这个序列从小到大遍历即可。
其中,最小的序列是01234,最大序列是43210
4.2 问题: 如何知道下一个序列数是多少?
因为这个序列组成的5位数的每个数字都不同,所以下一个序列数一定需要至少移动一个较大的数字到较小的数字的前面。如01234,移动4,变为01243
4.2.1 从那个位置开始移动
设数字序列为a0a1…axax+1ax+2…an
某一个高位ax被替换为一个较大的数字后,类似于一个数字进位,进位一般为1,在这里由于数字不能重复,进位只能为比ax低的数字位中选一个最小的,但又比ax大的数ay,否则移动后的数字反而会变小
∴ay是一个比ax大,且在ax+1ax+2…an中比ax大的数字中又是最小的一个
如01432,因为4 > 3 > 2,所以这3个数互换位置后该数一定会变小,没有移动的意义
而 1 < 4,所以可以在432中选一个比1大,但又是比1大的数中最小的数,满足该要求的数是2,将2移动到1的位置上后,变为02143
4.2.2 移动数字后,中间会漏一些数字吗?
ax低位的数字有没有可能组合出较小的数?
如02143,移动后其低位组成的数是143,很明显,其还可以组合成一个最小的数134。
为了避免这种情况,必然需要在对互换后ax的后半部分的数字取最小值。
就像数字进位,一旦数字进位了,则该位需要置为最小数0,在这里因为数字不能有重复,所以只能置为最小数。
所以,操作过程就是:
1> 从低位(右边)开始,找到aj<aj+1的数(即高位较小的数)
如01432,1 < 4,所以1满足要求
2> 从ax开始的低位部分取大于ax的最小数,并将其移动到ax的位置,将ax往后移一位
ax的低位部分为432,2是大于ax的最小数,移位后变为:02143
3> 将ax的后半部分的数字置为最小数
互换后后半部分为143,置为最小数就是134,所以01432下一个数为:02134
4.3 如何证明这样操作后的数和上一个数中间没有漏掉其他数字
∵ax是从低位开始,往高位找到的第一个ax < ax+1的数。
∴ ax+1 > ax+2 > … > an
∴操作完毕后满足,组成的新数列为b0b1…bxbx+1…bn,其中
bx > ax,
bx+1 < bx+2 < … < bn
ax ≥ bx+1
假设存在一个数列c, 满足:a0a1…axax+1…an < c0c1…cxcx+1…cn < b0b1…bxbx+1…bn
其中a0a1…ax-1 = b0b1…bx-1 = c0c1…cx-1
则cx ≥ ax (否则c < a)
cx ≤ bx,(否则c > b)
1> 若cx > ax
∵bx是比ax大的数中最小的一个
∴bx = cx (否则cx > bx,则c > b)
∵bx+1bx+2bn已是最小数列
∴cx+1cx+2cn不可能比bx+1bx+2bn小
∴此情形不存在
2> 若cx = ax
则cx+1…cn > ax+1…an
∵ax+1…an 已是ax+1, ax+2, …, an中的最大数列
∴此情形也不存在
4.4 python code
此部分的code较长,见附件中recursion_number_order()的实现
5. 从第4节中得到的启发
5.1 思路
5.1.1 从第4节中的方法可以总结出,其证明和思路主要将访问序列”0→1→2→3→4”看着一个数字:01234 来简化处理,那么这些数字有什么特征呢?
1> 只会出现{0, 1, 2, 3, 4},5进制数可以满足此表示方式
2> 没有重复的数字,去除5进制数中有重复数字的数即可
3> 生成5进制数可以用计数进位的方法:要进位时,将低位置0,高位+1
5.2 方法
1> 从5进制数0开始遍历,找出没有重复数字的5进制数(当然,为了提高效率,也可以从最小的不重复的5进制数:01234开始遍历)
2> 找到最大的一个不重复的5进制数:43210即可终止
5.3 python code
def has_reduplicate_digit(num_list): digit_time_list = [0] * len(num_list) for item in num_list: digit_time_list[item] += 1 if digit_time_list[item] > 1: return True return False def crate_num(max_num_str, base = 5): """create no reduplicate number""" max_num = int(max_num_str, base) print "max_num :", max_num num_list = [0] * base #create 0 to max_num -1 for i in range(max_num): # create base num carry = False for j, num in enumerate(num_list): #hangle number carry if carry: num += 1 carry = False if num >= base: carry = True num_list[j] = 0 else: num_list[j] = num #print no reduplicate number if not has_reduplicate_digit(num_list): global total_visit_order_num total_visit_order_num += 1 print_list = list(num_list) print_list.reverse() print print_list #next number num_list[0] += 1