Điều gì là hiệu quả nhất cho GCD?


26

Tôi biết rằng thuật toán của Euclid là thuật toán tốt nhất để có được GCD (ước số chung lớn) của danh sách các số nguyên dương. Nhưng trong thực tế, bạn có thể mã hóa thuật toán này theo nhiều cách khác nhau. (Trong trường hợp của tôi, tôi đã quyết định sử dụng Java, nhưng C / C ++ có thể là một tùy chọn khác).

Tôi cần sử dụng mã hiệu quả nhất có thể trong chương trình của mình.

Trong chế độ đệ quy, bạn có thể viết:

static long gcd (long a, long b){
    a = Math.abs(a); b = Math.abs(b);
    return (b==0) ? a : gcd(b, a%b);
  }

Và trong chế độ lặp, nó trông như thế này:

static long gcd (long a, long b) {
  long r, i;
  while(b!=0){
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}

Ngoài ra còn có thuật toán nhị phân cho GCD, có thể được mã hóa đơn giản như thế này:

int gcd (int a, int b)
{
    while(b) b ^= a ^= b ^= a %= b;
    return a;
}

3
Tôi nghĩ rằng điều này là quá chủ quan, và có lẽ thậm chí phù hợp hơn với StackOverflow. "Hiệu quả nhất trong thực tế" phụ thuộc vào nhiều yếu tố (thậm chí không thể đoán trước), chẳng hạn như kiến ​​trúc cơ bản, phân cấp bộ nhớ, kích thước và hình thức của đầu vào, v.v.
Juho

5
Đây là thuật toán tương tự được thể hiện theo cách đệ quy và lặp. Tôi nghĩ rằng sự khác biệt của chúng là không đáng kể vì thuật toán Euclid hội tụ khá nhanh. Chọn một cái phù hợp với sở thích của bạn.
pad

6
Bạn có thể muốn thử hồ sơ hai. Vì phiên bản đệ quy là một cuộc gọi đuôi, không có khả năng trình biên dịch thực sự phát ra gần như cùng một mã.
Louis

1
cái này sai. nên trong khi b! = 0, và sau đó trả về a. Nếu không, nó lỗi trên chia cho số không. cũng không sử dụng đệ quy nếu bạn có gcds thực sự lớn .... bạn nhận được một đống stack và trạng thái hàm ... tại sao không đi lặp lại?
Cris Stringfellow

4
Lưu ý rằng có các thuật toán GCD nhanh hơn bất thường. Ví dụ: en.wikipedia.org/wiki/Binary_GCD_alacticm
Neal Young

Câu trả lời:


21

%aibii

ai+1=bibi+1=aimodbi

aibiabi

ai+1=bibi+1=aimodbi

ai=0ai=0

Phiên bản đệ quy chỉ thực hiện các cuộc gọi đệ quy đuôi . Hầu hết các trình biên dịch cho các ngôn ngữ bắt buộc không tối ưu hóa các ngôn ngữ này và do đó, có khả năng mã mà chúng tạo ra sẽ lãng phí một ít thời gian và bộ nhớ xây dựng một khung ngăn xếp ở mỗi lần lặp. Với trình biên dịch tối ưu hóa các cuộc gọi đuôi (trình biên dịch cho các ngôn ngữ chức năng hầu như luôn luôn làm như vậy), mã máy được tạo có thể giống nhau cho cả hai (giả sử bạn hài hòa các cuộc gọi đó abs).


8

Đối với các số nhỏ, thuật toán GCD nhị phân là đủ.

GMP, một thư viện được thử nghiệm và duy trì tốt trong thế giới thực, sẽ chuyển sang một thuật toán GCD nửa đặc biệt sau khi vượt qua một ngưỡng đặc biệt, khái quát về Thuật toán của Lehmer. Lehmer's sử dụng phép nhân ma trận để cải thiện các thuật toán Euclidian tiêu chuẩn. Theo các tài liệu, thời gian chạy tiệm cận của cả hai HGCD và GCD là O(M(N)*log(N)), nơi M(N)là thời gian để nhân hai số N-chi.

Chi tiết đầy đủ về thuật toán của họ có thể được tìm thấy ở đây .


Liên kết thực sự không cung cấp chi tiết đầy đủ và thậm chí không xác định "chân tay" là gì ...
einpoklum - phục hồi Monica


2

Như tôi biết Java không hỗ trợ tối ưu hóa đệ quy đuôi nói chung, nhưng bạn có thể kiểm tra việc triển khai Java của mình cho nó; nếu nó không hỗ trợ nó, một đơn giản for-loop sẽ nhanh hơn, nếu không thì đệ quy cũng phải nhanh như vậy. Mặt khác, đây là những tối ưu hóa bit, chọn mã bạn nghĩ là dễ dàng và dễ đọc hơn.

Tôi cũng cần lưu ý rằng thuật toán GCD nhanh nhất không phải là thuật toán của Euclid, thuật toán của Lehmer nhanh hơn một chút.


Bạn có nghĩa là như xa như tôi biết ? Bạn có nghĩa là đặc tả ngôn ngữ không bắt buộc tối ưu hóa này (nó sẽ gây ngạc nhiên nếu nó đã làm), hoặc hầu hết các triển khai không thực hiện nó?
PJTraill

1

Đầu tiên, không sử dụng đệ quy để thay thế một vòng lặp chặt chẽ. Nó chậm. Đừng dựa vào trình biên dịch để tối ưu hóa nó. Thứ hai, trong mã của bạn, bạn gọi Math.abs () trong mỗi cuộc gọi đệ quy, điều này là vô ích.

Trong vòng lặp của bạn, bạn có thể dễ dàng tránh các biến tạm thời và hoán đổi a và b mọi lúc.

int gcd(int a, int b){
    if( a<0 ) a = -a;
    if( b<0 ) b = -b;
    while( b!=0 ){
        a %= b;
        if( a==0 ) return b;
        b %= a;
    }
    return a;
}

Trao đổi bằng cách sử dụng a ^ = b ^ = a ^ = b làm cho nguồn ngắn hơn nhưng cần nhiều hướng dẫn để thực thi. Nó sẽ chậm hơn so với trao đổi nhàm chán với một biến tạm thời.


3
Tránh tránh đệ quy. Đó là chậm chậm - được trình bày như lời khuyên chung, đây là không có thật. Nó phụ thuộc vào trình biên dịch. Thông thường, ngay cả với các trình biên dịch không tối ưu hóa đệ quy, nó không chậm, chỉ tiêu tốn stack.
Gilles 'SO- ngừng trở nên xấu xa'

3
Nhưng đối với mã ngắn như thế này, sự khác biệt là đáng kể. Tiêu thụ ngăn xếp có nghĩa là viết và đọc từ bộ nhớ. Đó là chậm. Đoạn mã trên chạy trên 2 thanh ghi. Đệ quy cũng có nghĩa là thực hiện các cuộc gọi, dài hơn một bước nhảy có điều kiện. Một cuộc gọi đệ quy khó hơn nhiều cho dự đoán chi nhánh và khó hơn để nội tuyến.
Florian F

-2

Đối với số lượng nhỏ ,% là một hoạt động khá tốn kém, có lẽ là đệ quy đơn giản hơn

GCD[a,b] := Which[ 
   a==b , Return[a],
   b > a, Return[ GCD[a, b-a]],
   a > b, Return[ GCD[b, a-b]]
];

nhanh hơn (Xin lỗi, mã Mathicala chứ không phải C ++)


Nó không đúng. Với b == 1, nó sẽ trả về 1. Và GCD [2.1000000000] sẽ chậm.
Florian F

À, vâng, tôi đã làm sai. Đã sửa (tôi nghĩ), và làm rõ.
Mỗi Alexandersson

Thông thường, GCD [a, 0] cũng sẽ trả về a. Vòng lặp của bạn mãi mãi.
Florian F

Tôi đang bỏ qua vì câu trả lời của bạn chỉ chứa mã. Chúng tôi muốn tập trung vào ý tưởng trên trang web này. Ví dụ, tại sao% là một hoạt động đắt tiền? Theo tôi, suy đoán về một đoạn mã không thực sự là một câu trả lời hay cho trang web này.
Juho

1
Tôi nghĩ rằng ý tưởng rằng modulo chậm hơn phép trừ có thể được coi là văn hóa dân gian. Nó giữ cả hai số nguyên nhỏ (phép trừ thường mất một chu kỳ, hiếm khi modulo) và cho số nguyên lớn (phép trừ là tuyến tính, tôi không chắc độ phức tạp tốt nhất của modulo là gì nhưng nó chắc chắn tệ hơn thế). Tất nhiên bạn cũng cần xem xét số lần lặp cần thiết.
Gilles 'SO- ngừng trở nên xấu xa'

-2

Thuật toán Euclid hiệu quả nhất để tính GCD:

Gcd dài tĩnh (dài a, dài b)
{
nếu (b == 0)
trả lại a;
khác
trả về gcd (, a% b);
}

thí dụ:-

Đặt A = 16, B = 10.
GCD (16, 10) = GCD (10, 16% 10) = GCD (10, 6)
GCD (10, 6) = GCD (6, 10% 6) = GCD (6, 4)
GCD (6, 4) = GCD (4, 6% 4) = GCD (4, 2)
GCD (4, 2) = GCD (2, 4% 2) = GCD (2, 0)


Vì B = 0 nên GCD (2, 0) sẽ trả về 2. 

4
Điều này không trả lời câu hỏi. Người hỏi trình bày hai phiên bản của Euclid và hỏi cái nào nhanh hơn. Bạn dường như không nhận thấy điều đó và chỉ tuyên bố phiên bản đệ quy là thuật toán duy nhất của Euclid và khẳng định không có bằng chứng nào cho thấy nó nhanh hơn mọi thứ khác.
David Richerby
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.