Tính toán một gốc lồng nhau trong C


9

Tôi được yêu cầu tính toán biểu thức gốc lồng nhau sau chỉ sử dụng đệ quy .

nhập mô tả hình ảnh ở đây

Tôi đã viết mã dưới đây hoạt động, nhưng họ cho phép chúng tôi chỉ sử dụng một chức năng và 1 đầu vào ncho mục đích chứ không phải 2 như tôi đã sử dụng. Ai đó có thể giúp tôi chuyển đổi mã này thành một hàm sẽ tính biểu thức không? Không thể sử dụng bất kỳ thư viện ngoại trừ các chức năng từ <math.h>.

đầu ra cho n = 10: 1.757932

double rec_sqrt_series(int n, int m) {
    if (n <= 0)
        return 0;
    if (m > n)
        return 0;
    return sqrt(m + rec_sqrt_series(n, m + 1));
}

double helper(int n) {
    return rec_sqrt_series(n, 1);
}

Ai đó có thể giúp tôi chuyển đổi mã này thành một hàm sẽ tính biểu thức không? gì? Giúp bạn thoát khỏi helper?
4386427

Nếu các đối số sai, tôi sẽ gọi abort()(từ <stdlib.h>), không âm thầm trả về 0.
Kaz

1
@chqrlieforyellowblockquotes Twisied. @pastaleg Làm thế nào về đệ quy vô dụng? double nested_root(unsigned n) { double x = 0.0; if (n > 0) { x = nested_root(0); for (unsigned i = n; i > 0; i--) { x = sqrt(i + x); } } return x; }
chux - Tái lập Monica

1
@ chux-ReinstateMonica: có, việc lạm dụng các quy tắc đơn giản hơn.
chqrlie

2
@Oppen: Nếu mục tiêu của nhiệm vụ là tài trợ cho một biểu thức không đệ quy của hàm, có lẽ nó sẽ không yêu cầu giải quyết vấn đề chỉ bằng cách sử dụng đệ quy. Chắc chắn một vòng lặp đơn giản sẽ tính toán kết quả dễ dàng. Mặc dù tôi thường nghi ngờ khi những câu hỏi này được đăng lên Stack Overflow mà không có văn bản thực tế của bài tập.
Eric Postpischil

Câu trả lời:


7

Sử dụng các bit trên của nnhư một bộ đếm:

double rec_sqrt_series(int n)
{
    static const int R = 0x10000;
    return n/R < n%R ? sqrt(n/R+1 + rec_sqrt_series(n+R)) : 0;
}

Đương nhiên, đó là trục trặc khi ban đầu nRhoặc lớn hơn. Đây là một phiên bản phức tạp hơn hoạt động cho bất kỳ giá trị tích cực nào n. Nó hoạt động:

  • Khi nâm, nó hoạt động như phiên bản trên, sử dụng các bit trên để đếm.
  • Khi nào nlà dương, nếu nó nhỏ hơn R, nó gọi chính nó -nđể đánh giá hàm như trên. Nếu không, nó gọi chính nó với R-1phủ định. Điều này đánh giá chức năng như thể nó được gọi với R-1. Điều này tạo ra kết quả chính xác bởi vì chuỗi dừng thay đổi ở định dạng dấu phẩy động chỉ sau vài chục lần lặp lại căn bậc hai của các số sâu hơn bị pha loãng nên chúng không có hiệu lực. Vì vậy, hàm có cùng giá trị cho tất cả ntrên một ngưỡng nhỏ.
double rec_sqrt_series(int n)
{
    static const int R = 0x100;
    return
        0 < n ? n < R ? rec_sqrt_series(-n) : rec_sqrt_series(1-R)
              : n/R > n%R ? sqrt(-n/R+1 + rec_sqrt_series(n-R)) : 0;
}

Ý tưởng hay, nhưng giả sử ints 32 bit :)
chqrlie

1
@chqrlieforyellowblockquotes: Nah, đó là lý do tại sao Rlà riêng biệt, vì vậy nó có thể được điều chỉnh. Trước khi nđạt 32, giá trị trả về dừng thay đổi đối với nhị phân IEEE-754 và trước khi đạt 256, giá trị trả về dừng thay đổi đối với các định dạng hợp lý cho double. Vì vậy, tôi đang xem xét một phiên bản thay thế biến đổi các đầu vào kẹp ở trên R, nhưng nó cần sử dụng bit dấu, và tôi vẫn đang làm việc với nó.
Eric Postpischil

các chức năng ghép nối khác mà bạn có thể sử dụng, nhưng không có chức năng nào đơn giản như của bạn. Ưu điểm chính của họ thường là họ làm việc với độ chính xác tùy ý, nhưng OP không bao giờ đề cập đến điều đó như một yêu cầu.
Ruud Helderman

@chqrlieforyellowblockquotes: Xong. Bây giờ tạo ra câu trả lời chính xác cho bất kỳ tích cực nbất kể chiều rộng của int.
Eric Postpischil

5

Nếu không chuyển đổi công thức một cách toán học (tôi không biết nếu có thể), bạn thực sự không thể sử dụng chỉ một tham số, vì với mỗi phần tử bạn cần hai thông tin: bước hiện tại và bản gốc n. Tuy nhiên bạn có thể gian lận . Một cách là mã hóa hai số trong inttham số (như được hiển thị bởi Eric ).

Một cách khác là lưu trữ bản gốc ntrong một biến cục bộ tĩnh. Ở cuộc gọi đầu tiên chúng ta lưu ntrong biến tĩnh này, chúng ta bắt đầu đệ quy và ở bước cuối cùng, chúng ta đặt lại nó thành giá trị sentinel:

// fn(i) = sqrt(n + 1 - i + fn(i - 1))
// fn(1) = sqrt(n)
//
// note: has global state
double f(int i)
{
    static const int sentinel = -1;
    static int n = sentinel;

    // outside call
    if (n == sentinel)
    {
        n = i;
        return f(n);
    }

    // last step
    if (i == 1)
    {
        double r = sqrt(n);
        n = sentinel;
        return r;
    }

    return sqrt(n + 1 - i + f(i - 1));
}

Rõ ràng static int n = sentinelkhông phải là tiêu chuẩn C vì sentinelkhông phải là hằng số thời gian biên dịch trong C (thật kỳ lạ vì cả gcc và clang đều không phàn nàn, ngay cả với -pedantic)

Bạn có thể làm điều này thay vào đó:

enum Special { Sentinel = -1 };
static int n = Sentinel;

Cách tiếp cận thú vị, nhưng tôi e rằng trình khởi tạo static int n = sentinel;không hoàn toàn tuân thủ trong C vì sentinelkhông phải là biểu thức không đổi theo Tiêu chuẩn C. Nó hoạt động trong C ++ và nó biên dịch với các phiên bản hiện tại của gcc và clang ở chế độ C nhưng không phải là MSVC 2017, nhưng có lẽ bạn nên viết static int n = -1;xem godbolt.org/z/8pEMnz
chqrlie

1
@chqrlieforyellowblockquotes ish. Cảm ơn bạn đã chỉ ra điều này. Hành vi biên dịch thú vị. Tôi đã hỏi về điều này trong câu hỏi này: stackoverflow.com/q/61037093/2805305
bolov

5

Vấn đề này cầu xin cho các giải pháp méo mó.

Đây là một hàm sử dụng một hàm lấy một hoặc hai intđối số:

  • nếu đối số đầu tiên là dương, nó sẽ tính biểu thức cho giá trị đó
  • nếu đối số thứ nhất là âm, nó phải được theo sau bởi đối số thứ hai và thực hiện một bước duy nhất trong tính toán, đệ quy cho bước trước đó.
  • nó sử dụng <stdarg.h>mà có thể hoặc không được phép.

Đây là mã:

#include <math.h>
#include <stdarg.h>

double rec_sqrt_series(int n, ...) {
    if (n < 0) {
        va_arg ap;
        va_start(ap, n);
        int m = va_arg(ap, int);
        va_end(ap);
        if (m > -n) {
            return 0.0;
        } else {
            return sqrt(m + rec_sqrt_series(n, m + 1));
        }
    } else {
        return rec_sqrt_series(-n, 1);
    }
}

Đây là một giải pháp khác với một chức năng duy nhất, chỉ sử dụng <math.h>, nhưng lạm dụng các quy tắc theo một cách khác: sử dụng macro.

#include <math.h>

#define rec_sqrt_series(n)  (rec_sqrt_series)(n, 1)
double (rec_sqrt_series)(int n, int m) {
    if (m > n) {
        return 0.0;
    } else {
        return sqrt(m + (rec_sqrt_series)(n, m + 1));
    }
}

Một số khác, nói đúng là đệ quy , nhưng với cấp đệ quy đơn và không có thủ thuật nào khác. Như Eric đã nhận xét, nó sử dụng một forvòng lặp có thể không hợp lệ theo các ràng buộc của OP:

double rec_sqrt_series(int n) {
    if (n > 0) {
        return rec_sqrt_series(-n);
    } else {
        double x = 0.0;
        for (int i = -n; i > 0; i--) {
            x = sqrt(i + x);
        }
        return x;
    }
}

vâng nó hoạt động tôi đoán. cảm ơn bạn rất nhiều vì tất cả sự giúp đỡ
Ronen Dvorkin

Cuối cùng double rec_sqrt_series(int n), IMO, đáp ứng các mục tiêu của OP bằng cách sử dụng dấu hiệu làm cờ đệ quy. (Tôi sẽ bỏ cái elsekhông cần thiết như returntrong if.)
chux - Tái lập lại

1
@ chux-ReinstateMonica: elsetất nhiên là có thể nhưng tôi muốn đối xứng với cả hai nhánh của ifkết quả trả về, kiểu sắp xếp chức năng.
chqrlie

@ chux-ReinstateMonica: Tôi hy vọng yêu cầu của bài tập về đệ quy chỉ có thể ngăn chặn lặp đi lặp lại.
Eric Postpischil

@EricPostpischil: vâng, tôi cũng nghĩ như vậy, nhưng không nhận được phản hồi từ OP.
chqrlie

0

Đây là một cách tiếp cận khác.

Nó dựa vào int32 bit. Ý tưởng là sử dụng 32 bit trên của 64 bit intđể

1) Xem cuộc gọi có phải là cuộc gọi đệ quy (hoặc cuộc gọi từ "bên ngoài")

2) Lưu giá trị đích trong 32 bit trên trong quá trình đệ quy

// Calling convention:
// when calling this function 'n' must be a positive 32 bit integer value
// If 'n' is zero or less than zero the function have undefined behavior
double rec_sqrt_series(uint64_t n)
{
  if ((n >> 32) == 0)
  {
    // Not called by a recursive call
    // so start the recursion
    return rec_sqrt_series((n << 32) + 1);
  }

  // Called by a recursive call

  uint64_t rn = n & 0xffffffffU;

  if (rn == (n >> 32)) return sqrt(rn);      // Done - target reached

  return sqrt (rn + rec_sqrt_series(n+1));   // Do the recursive call
}
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.