[Chia để trị] Tính lũy thừa a^n

Giờ rảnh được chút, làm thêm một bài nữa về một thuật toán cơ bản: tính lũy thừa an bằng phương pháp chia để trị.
Chắc mọi người đa số hay dùng vòng lặp nhỉ (cứ ừ đại đi), bây giờ đổi gió xíu nhé

Vào luôn vấn đề, như mọi người đã biết, an được tính bằng cách nhân n lần số a (nói vậy cho gọn đi)
Mình lấy ví dụ:

a8 = a . a . a . a . a . a . a . a (8 chữ a lận nhé)

Ta nhận thấy rằng có thể chia nửa cái phép tính trên kia ra, như thế này:

a8 = a4 + 4 = a4 . a4

trong đó, a4 có thể được tính:

a4 = a2 . a2

Lại một lần nữa chia nhỏ :

a2 = a1 . a1 = a . a

Đến đây ra có thể dễ dàng đưa ra a (a cùng n là input của bài toán này).

Vậy ta nhận ra được phần base case của thuật đệ quy ở đây (hay nhiều sách tiếng Việt gọi là phần neo ấy, mình học trên Khan nên đọc quen rồi) chính là khi số mũ đưa về 1, ta có thể đưa thẳng ra giá trị a

  • Từ đó ta suy ra cách giải quyết bài toán thế này: chia nhỏ số mũ n ra cho đến khi n = 1

Vậy hàm pow với tham số : int pow(int a, int n)
Sẽ có phần base case:

if(n == 1)
    return a;

Thế còn recursive case (phần đệ quy) ? Thì theo như ví dụ ở trên, ta có công thức:

an = an/2 . an/2

Chuyển ra code:

return pow(a, n/2) * pow(a, n/2);

Thế nhưng nếu n là một số lẻ thì sao? Ví dụ như n = 3:

a5 = a . a . a . a . a (1)
Thì trong trường hợp này, n/2 sẽ bằng 5/2 = 2 (do n kiểu int nên số sẽ tự bỏ đi phần thập phân)
(1) = a2 . a2 . a
do là số lẻ nên khi khi mod 2 (chia lấy dư) thì số dư sẽ luôn luôn là 1
=> Với n là số lẻ, khi chia ra sẽ luôn thừa một số a

Vậy ta suy ra công thức:

an = an/2 . an/2 với n là số lẻ

hay chuyển ra code:

return pow(a, n/2) * pow(a, n/2) * a;

Tổng hợp lại công thức tổng quát:

an =

  • an/2 * an/2 nếu n chẵn
  • an/2 * an/2 * a nếu n lẻ

Và cuối cùng là code, dài dòng lê thê lắm rồi:

int pow(int a, int n)
{
    if(n == 1) {
        return a;
    } else { 
        if(n % 2 == 0)
            return pow(a, n/2) * pow(a, n/2);
        else
            return pow(a, n/2) * pow(a, n/2) * a;
    }
}

Thế nhưng, ta lại thấy code trên có 1 điểm không tốt, đó chính là trong phần đệ quy, hàm pow được tính 2 lần trong khi chúng giống hệt nhau:

return pow(a, n/2) * pow(a, n/2);

Vậy ta sẽ gán chúng vào một biến, như thế thì sẽ đỡ phải tính lại thêm 1 lần:

int pow(int a, int n)
{
    if(n == 1) {
        return a;
    } else {
        int temp = pow(a, n/2);
        if(n % 2 == 0)
            return temp * temp;
        else
            return temp * temp * a;
    }
}

Code ngắn gọn hơn xíu:

int pow(int a, int n)
{
    if(n == 1) return a;
    int tmp = pow(a, n/2);
    return (n & 1) ? tmp * tmp * a : tmp * tmp;
}

Cập nhật:

=> Dùng phương pháp chia để trị nhanh hơn là dùng vòng lặp

Kết thúc bài :smiley: chưa có kinh nghiệm nên viết bài có vẻ lê thê quá, mọi người thông cảm :slight_smile:

14 Likes

À đúng rồi anh @Gio , anh xem giùm em độ phức tạp khi sử dụng cách này so với khi dùng vòng lặp với. Để em bổ sung vô cho mọi người có cái nhìn trực quan

3 Likes

số mũ lẻ tính có chính xác không?


À, đúng rồi.
  • Dùng vòng for(i=0->n): dpt=O(n)
  • Dùng chia để trị: T(n)= T(n/2)+O(1) => dpt=O(logn)
1 Like

Vậy cái này lâu hơn hay nhanh hơn hả anh, em chưa học log nên cũng không biết nhiều

Tất nhiên là O(logn) sẽ nhanh hơn.

p/s: log_a(b)=x <=> a^x=b viết log(n) <=> log_2(n) VD: log(16)=4

2 Likes

Cám ơn anh :slight_smile:
Bữa em học bên Khan một chút về logab = x mà thấy nước ta toàn viết log(n), thấy ngộ ngộ, đâu mất phần base (em không biets nó gọi là gì nữa, logab người ta đọc là log base a of b)
Cám ơn anh :slight_smile:

@nhatlonggunz với anh @Gio nghiên cứu viết tut tính độ phức tạp thật toán đê, muốn tính cho bằng anh em mà không biết tính :joy:

Bên Khan có dạy mà em chưa học, để khi nảo rảnh em vừa học vừa dịch luôn thử.

1 Like

:+1: :+1: Mà cái độ phức tạp này ý nghĩa thực tế thế nào nhỉ, nhìn nó rất Toán, không biết có tính được ra tốc độ chạy code không

Theo em hiểu sơ sơ (không học nhưng cũng lướt lướt) thì cái đó tính thời gian chạy của thuật toán (ví dụ cho i lặp từ 1 đến n, mỗi lần lặp là 1 bước, n lần lặp là n bước), space (gọi thế nào cho chuẩn nhỉ, chắc là khoảng tài nguyên sử dụng), rồi còn 1 cái nữa là gì ấy em không biết.

Và theo em nhìn qua thì đúng là nó

1 Like

tính độ phức tạp này đâu có gì khó đâu :smile: mà “độ phức tạp” với “tốc độ chạy code” (thời gian) là khác nhau :imp:

2 Likes

Thể nó tính được gì ạ ? :smile:

Có lẽ là số bước ?

20 char

Log hay lẻ lắm, số bước thì chắc phải nguyên chứ nhỉ :blush:

Em đang viết bài về vụ này, có lẽ trong nay mai sẽ được thôi. Cái này rắc rối quá, đọc đi đọc lại nãy giờ. Giờ phải viết làm sao cho người ta hiểu.

1 Like

A, anh tìm được tài liệu tiếng việt rồi, nhưng cái này phải biết giới hạn (lim: 1 khái niệm toán nhá, lớp 12 học),

Em đang làm bài về Asymptotic Notation (tiệm cận), không biết nó có liên quan đến độ phức tạp (complexity không)

Cái này mang tính học thuật quá. Nói chung là ước lượng thời gian chạy: O lớn, o nhỏ, Omega… nói chung là để tính thời gian chạy trong TH tốt, xấu, trung bình…

1 Like

Thực ra là làm tròn lên, nhưng khi làm tròn nó chỉ dc cộng 1 số 0<=c< 1 nên bị bỏ qua.

1 Like
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?