[英]Cannot locate my mistake in an algorithm involving Pythagorean triples and sets
我试图找到答案“鉴于L是电线的长度,因为可以精确地形成一个L等于1,500,000的L值多少个正整数?”, Euler项目#75 。
我并没有要求正确的答案,也没有要求找到它的代码。 我将解释如何尝试解决此问题,仅请您指出我的错误之处。 我试图解决Java和Common Lisp中的问题,总是得到相同的错误答案。 因此,我很确定算法或基本假设中的某些内容是错误的,但是我找不到它。 我对潜在错误点的猜测是:1)设置差异中的错误2)设置参数极限时的错误。
这是我遵循的算法:
我使用在这里找到的公式生成三元组,从而生成周长。 我更喜欢将公式与附加系数“ k”一起使用,因为该文章说,
尽管生成了所有原始三元组,但Euclid公式并未生成所有三元组-例如,不能使用整数m和n生成(9,12,15)。 这可以通过在公式中插入一个附加参数k来解决。
为了在合理的时间内解决问题,我需要对嵌套循环中的参数进行合理的限制。 通过下面的两个小功能,我设置了“ k”和“ m”的极限,您将在稍后提供的完整代码中看到这些极限:
(defun m-limit (m)
(if (> (make-peri m 1 1) 1500000)
m
(m-limit (1+ m))))
(defun k-limit (k)
(if (> (make-peri 2 1 k) 1500000)
k
(k-limit (1+ k))))
要为“ n”设置限制,请在公式中添加“ a”,“ b”和“ c”,然后为“ n”求解(这可能是我犯错的另一点)。
a = k *(m * m-n * n);
b = 2 * k * m * n;
c = k *(m * m + n * n);
k *(m ^ 2-n ^ 2 + 2mn + m ^ 2 + n ^ 2)<= 1500000
并发现了这一点:
nLimit = 1500000 / (2 * k * m) - m;
这是Java和Common Lisp中的代码。 请注意,由于使用HashSet,Java仅花费了2秒的时间,而在我的笔记本电脑上,Common Lips花费了1889秒的时间,这很可能是因为检查了新生成的边界是否已经是集合“ A”的成员。
Java代码:
package euler75v6;
import java.util.HashSet;
/**
*
* @author hoyortsetseg
*/
public class Euler75v6 {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
HashSet<Long> peris = new HashSet<>();
HashSet<Long> duplicatePeris = new HashSet<>();
peris = periList(865,125000, duplicatePeris);
System.out.println("Number of all perimeters: " + peris.size());
System.out.println("Number of duplicate perimeters: " + duplicatePeris.size());
System.out.println("Number of all perimeters minus number "
+ "of duplicate perimeters: " + (peris.size() - duplicatePeris.size()));
peris.removeAll(duplicatePeris);
System.out.println("Same difference, just to confirm. After 'removeAll': " + peris.size());
}
private static Long makePeri (long m, long n, long k){
//Long a, b, c, res;
//a = k * (m * m - n * n);
//b = 2 * k * m * n;
//c = k * (m * m + n * n);
return 2 * k * (m * m + m * n);
}
private static HashSet<Long> periList (long x, long z, HashSet<Long> dupList){
HashSet<Long> res = new HashSet<>();
Long temp, nLimit;
Long limit = Long.valueOf("1500000");
for (long k = 1; k <= z; k++){
for (long m = 2; m <= x; m++){
nLimit = 1500000 / (2 * k * m) - m;
for (long n = 1; ((n <= nLimit) && (n < m)); n++){
temp = makePeri(m,n,k);
if (Long.compare(temp, limit) <= 0){ // Should be redundant but just in case.
if (res.contains(temp)){
dupList.add(temp);
}
else {
res.add(temp);
}
}
}
}
}
return res;
}
}
常见的Lisp代码:
(defun make-peri (m n k)
(* 2 k (+ (* m m) (* m n))))
(defun peri-list (m n k all-peris dupl-peris)
(let ((n-limit (- (/ 1500000 (* 2 k m)) m)))
(cond ((> k 125000) (- (length all-peris)
(length (remove-duplicates dupl-peris))))
((> m 865) (peri-list 2 1 (1+ k) all-peris dupl-peris))
((or (>= n m) (> n n-limit))
(peri-list (1+ m) 1 k all-peris dupl-peris))
(t (let ((peri (make-peri m n k)))
(if (> peri 1500000) ;; Redundant with m n k limits but still.
(peri-list (1+ m) 1 k all-peris dupl-peris)
(if (member peri all-peris)
(peri-list m (1+ n) k all-peris (cons peri dupl-peris))
(peri-list m (1+ n) k (cons peri all-peris)
dupl-peris))))))))
(defun result () (peri-list 2 1 1 nil nil))
关于我弄错了什么的任何解释将不胜感激。 但是请不要给出正确的答案或相应的代码。
编辑:
我对Common Lisp代码进行了稍微改动,以查看收集到的列表(集)的外观,并希望看到出了什么问题。
我还添加了一些位来自动设置mnk变量的限制。 我还摆脱了多余的“ if”,它检查周长是否超出限制,因为我看到mn和k在其限制之内可以确保周长不超过其自身的限制。 经过这些修改后,代码如下所示:
(defun make-peri (m n k)
(* 2 k (+ (* m m) (* m n))))
(defun peri-list* (m n k limit all-peris dupl-peris)
(let* ((n-limit (- (/ limit (* 2 k m)) m))
(k-upper-limit (1- (k-limit 1 limit)))
(m-upper-limit (1- (m-limit 2 limit)))
(dupl-peris* (remove-duplicates dupl-peris))
(difference* (set-difference all-peris dupl-peris*)))
(cond ((> k k-upper-limit) (list (sort all-peris #'<)
(sort dupl-peris* #'<)
(sort difference* #'<)))
;; (length all-peris)
;; (length dupl-peris*)
;; (length difference*)))
((> m m-upper-limit) (peri-list* 2 1 (1+ k) limit all-peris dupl-peris))
((or (>= n m) (> n n-limit))
(peri-list* (1+ m) 1 k limit all-peris dupl-peris))
(t (let ((peri (make-peri m n k)))
(if (member peri all-peris)
(peri-list* m (1+ n) k limit all-peris (cons peri dupl-peris))
(peri-list* m (1+ n) k limit (cons peri all-peris) dupl-peris))))))))
(defun m-limit (m limit)
(if (> (make-peri m 1 1) limit)
m
(m-limit (1+ m) limit)))
(defun k-limit (k limit)
(if (> (make-peri 2 1 k) limit)
k
(k-limit (1+ k) limit)))
首先,我尝试了一个很小的限制,以查看其表现。 一开始我没有注释掉(length...)
部分。 我看到了一些我不理解的行为:
CL-USER> (peri-list* 2 1 1 150 nil nil)
((12 24 30 36 40 48 56 60 70 72 80 84 90 96 108 112 120 126 132 140 144 150)
(24 48 60 72 80 84 90 96 108 112 120 132 140 144) (12 30 36 40 56 70 126 150)
13 9 8)
CL-USER> (- 22 14)
8
CL-USER> (peri-list* 2 1 1 100 nil nil)
((12 24 30 36 40 48 56 60 70 72 80 84 90 96) (24 48 60 72 80 84 90 96)
(12 30 36 40 56 70) 11 5 6)
CL-USER> (- 14 8)
6
结果, all-peris*
和dupl-peris*
的长度与我所计算的不匹配。 但是,它们的差异确实与计数相符。
之后,我注释掉了(length...)
部分,让程序仅列出列表并将mapcar
ed #'length
转换为结果:
CL-USER> (mapcar #'length (peri-list* 2 1 1 100 nil nil))
(14 8 6)
这次,前两个长度确实与实际计数匹配。 但是,差异仍然相同。 表示我仍然得到错误的答案。
CL-USER> (mapcar #'length (peri-list 2 1 1 nil nil))
(355571 247853)
CL-USER> (- 355571 247853)
107718
这使我质疑我的基本假设。 所以这是我的问题。
具体问题:
all-peris
和dupl-peris
(在(remove-duplicates...)
之后(remove-duplicates...)
)能给我正确的答案吗? all-peris
代码是否正确地收集了所有周界中的all-peris
周长以及在dupl-peris
具有多个三角形dupl-peris
? (let ((peri (make-peri mnk))) (if (member peri all-peris) (peri-list* m (1+ n) k limit all-peris (cons peri dupl-peris)) (peri-list* m (1+ n) k limit (cons peri all-peris) dupl-peris)))
是什么导致length
的神秘行为?
当我在Java和Common Lisp上都得到相同的结果时,我认为这不是我在犯特定语言的错误。 为什么不设置peris.removeAll(duplicatePeris);
的大小peris.removeAll(duplicatePeris);
给我多少个三角形的周长? 我在算法上或在集合及其子集的区别上犯了一些基本错误吗?
我希望这有助于“解决”我的问题。
编辑#2,更新Java版本:
我试图使用HashMap
编写解决方案的Java版本,以周长为键,以周长频率为值。 这里是:
public static void main(String[] args) {
HashMap<Long, Long> perisMap = new HashMap<>();
periMap(865, 125000, perisMap);
System.out.println("Number of all perimeters (1 triangle, many triangles): " + perisMap.size());
Long uniqueCounter = Long.valueOf("0");
for (Map.Entry<Long, Long> entry : perisMap.entrySet()){
Long freq = entry.getValue();
if (freq == 1){
uniqueCounter++;
}
}
System.out.println("Number of all perimeters in the map which appear only once: " + uniqueCounter);
}
private static Long makePeri (long m, long n, long k){
//Long a, b, c, res;
//a = k * (m * m - n * n);
//b = 2 * k * m * n;
//c = k * (m * m + n * n);
return 2 * k * (m * m + m * n);
}
private static void periMap (long x, long z, HashMap<Long, Long> myMap){
Long nLimit;
Long limit = Long.valueOf("1500000");
for (long k = 1; k <= z; k++){
for (long m = 2; m <= x; m++){
nLimit = limit / (2 * k * m) - m;
for (long n = 1; ((n <= nLimit) && (n < m)); n++){
Long tempKey = makePeri(m,n,k);
Long tempVal = myMap.get(tempKey);
if (Long.compare(tempKey, limit) <= 0){
if (myMap.containsKey(tempKey)){
myMap.put(tempKey, tempVal + 1);
}
else {
myMap.put(tempKey, Long.valueOf("1"));
}
}
}
}
}
}
这是我运行它时得到的:
Number of all perimeters (1 triangle, many triangles): 355571
Number of all perimeters in the map which appear only once: 107718
结果与带有列表的旧Java版本和Common Lisp版本相同。 我目前正在尝试使用哈希表编写新的Common Lisp版本。
题:
这是具有相同结果的第三个版本。 显然,我的逻辑/算法/数学出了点问题。 关于哪里的任何指针?
要使用Euler公式解决此问题,必须遵循所有规则,以确保每个三元组都将生成一次 。 否则,您将多次生成相同的三倍,并且由于有效长度过高,您将跳过有效长度。
规则包括仅使用互质的(m,n)
对,其中m
和n
都不都是奇数。
我认为,如果您根据这些规则添加检查以避免无效对,则您的算法将是正确的。 至少它将更接近。
有关Java代码的其他注释:声明Long
变量很少有用。 声明long
,让自动装箱根据需要进行转换。 通常,您对Long
类型的使用很奇怪。 例如, Long.valueOf("1")
可以替换为1
或1L
。 同样, Long.compare(tempKey, limit) <= 0
应该是tempKey <= limit
。
实际上,对于此问题,不需要long
。 可以完全使用int
。
Lisp
跟踪已生成的每个长度的最简单方法是使用小整数数组。 这是Common Lisp中的想法:
(defun count-triangles (limit)
(let ((counts (make-array (1+ limit)
:element-type 'unsigned-byte
:initial-element 0))
(result 0))
(loop for m from 2 to (ceiling (sqrt limit)) do
(loop for n from 1 to (1- m)
for k1-len = (* 2 m (+ m n)) then (+ k1-len (* 2 m))
while (<= k1-len limit)
when (and (oddp (+ m n)) (= (gcd m n) 1))
do (loop for len = k1-len then (+ len k1-len)
while (<= len limit)
do (case (aref counts len)
(0 (incf result)
(incf (aref counts len)))
(1 (decf result)
(incf (aref counts len)))))))
result))
在编译的CLisp中,这大约需要0.5秒。 在我的旧MacBook上,下面的Java等价于0.014秒。
static int count() {
byte [] count = new byte[MAX + 1];
int result = 0;
for (int m = 2; m < SQRT_MAX; ++m) {
for (int n = 1; n < m; ++n) {
if (((m ^ n) & 1) == 0 || gcd(m, n) > 1) continue;
int base_len = 2 * m * (n + m);
if (base_len > MAX) break;
for (int len = base_len ; len <= MAX; len += base_len) {
switch (count[len]) {
case 0:
++result;
count[len] = 1;
break;
case 1:
--result;
count[len] = 2;
break;
default:
break;
}
}
}
}
return result;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.