哈密顿回路问题
巡回售货员(或者货郎担问题)即求最短的哈密顿回路。
题目:求最短哈密顿回路,代码实现。
哈密顿(1805——1865)路指在一个图中,从图的一个顶点出发,沿着图中的边(各顶点连线),经过所有顶点各一次,这条路线就称之为哈密顿路;如果最终又回到了起点,则称为哈密顿回路。有哈密顿回路的图称之为哈密顿图。最短哈密顿回路即是要求所有哈密顿回路中最短的一条路径。
哈密顿路径问题在上世纪七十年代初,终于被证明是“NP完备”的。据说具有这样性质的问题,难于找到一个有效的算法。实际上对于某些顶点数不到100的网络,利用现有最好的算法和计算机也需要比较荒唐的时间(比如几百年)才能确定其是否存在一条这样的路径。
解决哈密顿回路的思路有很多,本人总结了几种:
参考资料:
1.
哈密顿回路问题和“六度空间”
哈密顿(1805——1865)路指在一个图中,从图的一个顶点出发,沿着图中的边(各顶点连线),经过所有顶点各一次,这条路线就称之为哈密顿路;如果最终又回到了起点,则称为哈密顿回路。有哈密顿回路的图称之为哈密顿图。
以上问题是哈密顿1859年从他发明的一个数学游戏——每个面都是正五边形的正十二面体的顶点都通过一遍而回到起点的数学游戏中体现出来的,所以后来称之为哈密顿回路。
哈密顿回路是成立的,也就是可解的。但是我们讨论数学问题,更注意的是它的普遍规律问题。在任意多面体上周游世界问题还没有确定的答案和解决方案,在任意一个连接图中有没有哈密顿路和哈密顿回路还是一个世界之迷。
这个问题的最终疑问是:对于一个连接图,存在哈密顿路和哈密顿回路的充要条件是什么?
与哈密顿路、回路有关的可归于同类型的讨论题目是“六度空间猜想”问题。20世纪60年代,耶鲁大学的社会心理学家米格兰姆就设计了一个连锁信件实验。他将一套连锁信件随机发送给居住在内布拉斯加州奥马哈的160个人,信中放了一个波士顿股票经纪人的名字,信中要求每个收信人将这套信寄给自己认为是比较接近那个股票经纪人的朋友。朋友收信后照此办理。最终,大部分信在经过五、六个步骤后都抵达了该股票经纪人。六度空间(也叫“六度分隔”)的概念由此而来。这个连锁实验,体现了一个似乎很普遍的客观规律:社会化的现代人类社会成员之间,都可能通过“六度空间”而联系起来,绝对没有联系的A与B是不存在的。这是一个更典型、深刻而且普遍的自然现象。那么,怎样用数学理论揭示“六度空间现象”?这是现代数学领域又一个重大的数学猜想。
由于是多路线联系起来的问题,那么其研究方法就要比单路线的哈密顿路、回路更加复杂。
魅力无穷的数学王国,在我们不懈的探求中,还有多少不断被发现和有待发现、解决的未知之迷?!
有兴趣的网友,可以研究一下笔者曾经探讨过的“完美正多边形”中所提及的各种类型正多面体的哈密顿路和哈密顿回路问题。
2.“N皇后”和哈密顿回路问题 (程序及结果)
http://tiancai3680.blog.163.com/blog/static/51608017200710266129573/
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
一、主元素问题
设T[0..n-1]是n个元素的数组。对任一元素x,设S(x)={i|T[i]=x}。当|S(x)|>n/2时,称x为T的主元素。
1) 如果T中元素存在序关系,按分治策略设计并实现一个线性时间算法,确定T[0..n-1]是否有一个主元素。
2) 若T中元素不存在序关系,只能测试任意两个元素是否相等,试设计并实现一个O(nlogn)有效算法,确定T是否有一个主元素。进一步,能找到一个线性时间算法吗?
注:实现的算法要求列出足够的实验结果。
1) 基于分治法的线性时间求主元素算法
中位数:数列排序后位于最中间的那个数。如果一个数列有主元素
,
那么必然是其中位数。求一个数列有没有主元素,只要看中位数是不是主元素。
找中位数的方法
:
选择一个元素作为划分起点,然后用快速排序的方法将小于它的移动到左边,大于它的移动到右边。这样就将元素划分为两个部分。此时,划分元素所在位置为
k
。如果
k>n/2
,那么继续用同样的方法在左边部分找;如果
k<n/2
就在右边部分找;
k=n/2
就找到了中位元素。
根据快速排序的思想,可以在平均时间复杂度为
O(n)
的时间内找出一个数列的中位数。然后再用
O(n)
的时间检查它是否是主元素。
算法实现:
对应的
Java
程序在
MajorElement.java
中
----------------------------------------------------------------------------------
判断是否是主元素的伪代码
:
master(A):
len
←
length[A]
median
←
randomizedSelect(A , 0 , n - 1 , n/2);
▹
求中位数
cnt
←
0
▹
计算中位数出现次数
for i
←
0 to len – 1
do if A[i] = median
then cnt
←
cnt + 1
if cnt > n/2
then print "
主元素
:" +median + "
出现次数
:" + cnt
else print "
无主元素
"
----------------------------------------------------------------------------------
找一个序列中第
k
大的数伪代码
randomizedSelect(A , p , q , k):
r
←
randomizedPartition (p , q)
▹
找出划分元素
r
if r = k
then return A[r]
else if r > k
then randomizedSelect(A , p , r – 1, k)
else randomizedSelect(A , r + 1 , q , k)
----------------------------------------------------------------------------------
实现随机划分的伪代码
:
randomizedPartition(A , p , q ):
rand
←
random(p , q)
exchange A[rand] ↔A[q]
return partition(p , q)
----------------------------------------------------------------------------------
基于快速排序思想的划分伪代码
:
partition(A , p , q ):
pivot
←
A[q]
i
←
p – 1
for j
←
p to q – 1
do if A[j] <= pivot
then i
←
i + 1
exchange A[i] ↔ A[j]
exchange A[i + 1] ↔ A[q]
return i + 1
----------------------------------------------------------------------------------
时间复杂度分析:
master()
中求中位数可以在平均时间复杂度为
O(n)
的时间内完成
,
检查中位数是否是主元素耗时
O(n),
所以时间复杂度为O(n)。
2) 无序关系时求主元素的O(nlgn)的算法
算法思想:
若
T
中存在主元素,则将
T
分为两部分后,
T
的主元素也必为两部分中至少一部分的主元素,因此可用分治法。
将元素划分为两部分,递归地检查两部分有无主元素。算法如下:
a.
若
T
只含一个元素,则此元素就是主元素,返回此数。
b.
将
T
分为两部分
T1
和
T2
(二者元素个数相等或只差一个),分别递归调用此方法求其主元素
m1
和
m2
。
c.
若
m1
和
m2
都存在且相等,则这个数就是
T
的主元素,返回此数。
d.
若
m1
和
m2
都存在且不等,则分别检查这两个数是否为
T
的主元素,若有则返回此数,若无则返回空值。
e.
若
m1
和
m2
只有一个存在,则检查这个数是否为
T
的主元素,若是则返回此数,若否就返回空值。
f.
若
m1
和
m2
都不存在,则
T
无主元素,返回空值。
算法实现:
相应的
Java
程序在
MasterElement.java
中
----------------------------------------------------------------------
O(nlgn)
的算法伪代码
:
▹求
T[p..q]
中的主元素。返回主元素及其出现次数或空
(
表示无主元素
)
CheckMaster(T , p , q):
if p ← q
then return T[p] and 1
len ← q – p + 1
r ← p + len / 2
a and numa ← CheckMaster(T , p , r – 1)
b and numb ← CheckMaster(T , r , q)
if a = NIL and b = NIL
then return NIL
if a = NIL and b
≠
NIL
then return CheckAnotherPart(T , len , p , r – 1 , b , numb)
if a
≠
NIL and b = NIL
then return CheckAnotherPart(T , len , r , q , a , numa)
if a
≠
NIL and b
≠
NIL
then if a = b
then numa ← numa + numb
return a and numa
else re ← CheckAnotherPart(T , len , p , r – 1 , b ,numb)
if re
≠
NIL
then return re
else return CheckAnotherPart(T, len, r, q, a, numa)
----------------------------------------------------------------------
▹检查候选主元素是否是主元素
CheckAnotherPart(T , len , p , q , c , numc):
numc ← CheckNum(T , p , q , c) + numc
if num > len/2
then return c and numc
else return NIL
----------------------------------------------------------------------
▹计算
T[p..q]
中
element
出现的次数
CheckNum( T , p , q , element):
cnt ← 0
for i ← p to q
do if T[i] = element
then cnt ← cnt + 1
return cnt
----------------------------------------------------------------------
时间复杂度分析:
T(n)=2T(n/2)+n
所以时间复杂度为
O(nlgn)
3)无序关系时求主元素的O(n)算法
算法思想
在一个集合中,删除两个不同的数,则集合的主元素保持不变。根据这个原理,可以很快求出主元素。
算法实现:
------------------------------------------------------------------------------
相应的
Java
程序在
MainElement.java
中
master(A):
n ← length[A]
count ← 1
seed ← A[0]
▹
找候选主元素
for i ← 1 to n – 1
do if A[i] = seed
then count ← count + 1
else if count > 0
then count ← count – 1
else seed ← A[i]
▹
查找候选主元素是否是主元素
count ← 0
for i ← 0 to n – 1
do if A[i] = seed
then count ← count + 1
if count > n/2
then return seed and count
else return NIL
------------------------------------------------------------------------------
时间复杂度分析:
时间复杂度为
O(n)
4)
算法测试
对前面三个求主元素算法,使用测试数据进行测试
:
测试数组
|
结果
|
0,0,1,1,0,8,1,1,1
|
主元素
:1
出现次数
:5
|
13,17,26,3,5,2,17,3
|
无主元素
|
1,2,3,4,5,6,7,8
|
无主元素
|
1,0,0,1,0,2,0
|
主元素
:0
出现次数
:4
|
1,3,4,1,2,1,1,4,0,1,1,1
|
主元素
:1
出现次数
:7
|
0,1,1
|
主元素
:1
出现次数
:2
|
13,17,26,3,5,2,17,3,3,3,3,3,3
|
主元素
:3
出现次数
:7
|
100,58
|
无主元素
|
597
|
主元素
:597
出现次数
:1
|
6,1,2,2,2,3,5
|
无主元素
|
7,7,7,7,7,7
|
主元素
7
出现次数
:6
|
5,9,45,96,77,28,13
|
无主元素
|
文章来源:http://wintys.blog.51cto.com/425414/100688