การคำนวณรูตที่ซ้อนใน C


9

ฉันถูกขอให้คำนวณนิพจน์รูทที่ซ้อนกันต่อไปนี้โดยใช้การเรียกซ้ำเท่านั้น

ป้อนคำอธิบายรูปภาพที่นี่

ฉันเขียนโค้ดด้านล่างที่ใช้งานได้ แต่พวกเขาอนุญาตให้เราใช้เพียงฟังก์ชั่นเดียวและ 1 อินพุตnเพื่อวัตถุประสงค์และไม่ใช่ 2 อย่างที่ฉันใช้ ใครสามารถช่วยฉันแปลงรหัสนี้เป็นฟังก์ชั่นเดียวที่จะคำนวณการแสดงออก? ลาดเทใช้ห้องสมุดใด ๆ <math.h>ยกเว้นจากฟังก์ชั่น

เอาต์พุตสำหรับ 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);
}

ใครสามารถช่วยฉันแปลงรหัสนี้เป็นฟังก์ชั่นเดียวที่จะคำนวณการแสดงออก? อะไร? ช่วยให้คุณกำจัดhelper?
4386427

หากการโต้เถียงผิดฉันจะโทรabort()(จาก<stdlib.h>) ไม่ส่งคืนอย่างเงียบ ๆ 0
Kaz

1
@chqrlieforyellowblockquotes Twisied @pastaleg แล้วการเรียกซ้ำที่ไร้ประโยชน์เป็นอย่างไร 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 - Reinstate Monica

1
@ chux-ReinstateMonica: ใช่การละเมิดกฎที่ง่ายกว่า
chqrlie

2
@Oppen: หากเป้าหมายของการมอบหมายคือการให้เงินทุนในการแสดงออกของฟังก์ชั่นที่ไม่เกิดซ้ำมันอาจจะไม่ขอให้แก้ไขปัญหาโดยใช้ "การเรียกซ้ำเท่านั้น" แน่นอนว่าลูปแบบง่ายจะคำนวณผลลัพธ์ได้อย่างง่ายดาย แม้ว่าโดยทั่วไปฉันจะสงสัยเมื่อมีการโพสต์คำถามเหล่านี้ใน Stack Overflow โดยไม่มีข้อความจริงของการมอบหมาย
Eric Postpischil

คำตอบ:


7

ใช้บิตส่วนบนของnตัวนับ:

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;
}

ธรรมชาติ, ทำงานผิดปกติว่าเมื่อเริ่มต้นnเป็นRหรือมากกว่า นี่เป็นรุ่นที่มีความซับซ้อนมากขึ้นว่าการทำงานสำหรับการใด ๆ nค่าบวกของ มันได้ผล:

  • เมื่อnเป็นลบจะทำงานเหมือนเวอร์ชันด้านบนโดยใช้บิตบนเพื่อนับ
  • เมื่อnเป็นบวกหากน้อยกว่าRจะเรียกตัวเอง-nเพื่อประเมินฟังก์ชั่นดังกล่าวข้างต้น มิฉะนั้นจะเรียกตัวเองด้วยR-1เมื่อตะกี้ R-1ฟังก์ชั่นนี้ประเมินราวกับว่ามันถูกเรียกว่ามี สิ่งนี้สร้างผลลัพธ์ที่ถูกต้องเพราะซีรีส์หยุดการเปลี่ยนแปลงในรูปแบบจุดลอยตัวหลังจากทำซ้ำเพียงไม่กี่โหล - รากที่สองของจำนวนที่ลึกกว่าจะลดลงจนไม่มีผล ดังนั้นฟังก์ชั่นนี้จึงมีค่าเท่ากันสำหรับทุกnๆ เกณฑ์ที่น้อย
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;
}

คิดที่ดี แต่ถือว่า ints 32 บิต :)
chqrlie

1
@chqrlieforyellowblockquotes: ไม่นั่นเป็นเหตุผลที่Rแยกจากกันดังนั้นจึงสามารถปรับได้ ก่อนที่จะnถึง 32 ค่าส่งกลับหยุดการเปลี่ยนแปลงสำหรับ IEEE-754 binary64 และก่อนที่จะถึง 256 doubleค่าส่งกลับหยุดการเปลี่ยนแปลงรูปแบบที่เหมาะสมสำหรับ ดังนั้นฉันกำลังพิจารณารุ่นอื่นที่แปลงอินพุตแคลมป์ด้านบนRแต่จำเป็นต้องใช้เครื่องหมายบิตและฉันยังคงใช้งานมันอยู่
Eric Postpischil

มีฟังก์ชั่นการจับคู่อื่น ๆ ที่คุณสามารถใช้ได้ แต่ไม่มีฟังก์ชั่นการใช้งานที่ง่ายเหมือนของคุณ ข้อได้เปรียบหลักของพวกเขาคือโดยทั่วไปแล้วพวกเขาทำงานด้วยความแม่นยำโดยพลการ แต่ OP ไม่เคยกล่าวถึงว่าเป็นข้อกำหนด
Ruud Helderman

@chqrlieforyellowblockquotes: เสร็จสิ้น ตอนนี้ผลิตคำตอบที่ถูกต้องสำหรับการบวกใด ๆโดยไม่คำนึงถึงความกว้างของn int
Eric Postpischil

5

โดยไม่ต้องเปลี่ยนทางคณิตศาสตร์สูตร (ผมไม่ทราบว่ามันเป็นไปได้) คุณจะไม่สามารถใช้อย่างแท้จริงเพียงหนึ่งพารามิเตอร์สำหรับแต่ละองค์ประกอบที่คุณต้องสองรายละเอียด: nขั้นตอนในปัจจุบันและเดิม อย่างไรก็ตามคุณสามารถโกง วิธีหนึ่งคือการเข้ารหัสตัวเลขสองตัวในintพารามิเตอร์ (ตามที่แสดงโดยEric )

อีกวิธีคือการเก็บต้นฉบับnไว้ในตัวแปรสแตติกท้องถิ่น ในการโทรครั้งแรกที่เราบันทึกไว้nในตัวแปรสแตติกนี้เราจะเริ่มต้นการสอบถามซ้ำและในขั้นตอนสุดท้ายเรารีเซ็ตเป็นค่า 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));
}

เห็นได้ชัดstatic int n = sentinelว่าไม่ได้มาตรฐาน C เพราะsentinelไม่ใช่ค่าคงที่เวลารวบรวมใน C (มันแปลกเพราะทั้ง gcc และเสียงดังกราวไม่บ่นแม้แต่กับ-pedantic)

คุณสามารถทำสิ่งนี้แทน:

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

วิธีการที่น่าสนใจ แต่ฉันเกรงว่า initializer static int n = sentinel;นั้นไม่สอดคล้องกันอย่างสมบูรณ์ใน C เนื่องจากsentinelไม่ใช่นิพจน์คงที่ตามมาตรฐาน C ใช้งานได้ใน C ++ และคอมไพล์กับ gcc และ clang เวอร์ชันปัจจุบันในโหมด C แต่ไม่ใช่ MSVC 2017 แต่คุณควรเขียนstatic int n = -1;ดูgodbolt.org/z/8pEMnz
chqrlie

1
@chqrlieforyellowblockquotes ish ขอบคุณที่ชี้นำสิ่งนี้ พฤติกรรมผู้แปลที่น่าสนใจ ฉันเคยถามเกี่ยวกับเรื่องนี้ในคำถามนี้: stackoverflow.com/q/61037093/2805305
bolov

5

ปัญหานี้ขอร้องสำหรับการแก้ปัญหาแบบต่างๆ

นี่คือฟังก์ชั่นเดียวที่ใช้หนึ่งหรือสองintอาร์กิวเมนต์:

  • ถ้าอาร์กิวเมนต์แรกเป็นค่าบวกจะคำนวณนิพจน์สำหรับค่านั้น
  • ถ้าอาร์กิวเมนต์แรกเป็นลบมันจะต้องตามด้วยอาร์กิวเมนต์ที่สองและดำเนินการขั้นตอนเดียวในการคำนวณซ้ำสำหรับขั้นตอนก่อนหน้า
  • มันใช้<stdarg.h>สิ่งที่อาจหรือไม่ได้รับอนุญาต

นี่คือรหัส:

#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);
    }
}

นี่เป็นอีกวิธีการหนึ่งที่มีฟังก์ชั่นเดียวใช้เพียงอย่างเดียว<math.h>แต่ใช้กฎในทางที่แตกต่าง: ใช้มาโคร

#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));
    }
}

อีกคนหนึ่งพูดซ้ำอย่างเคร่งครัดแต่ด้วยระดับการเรียกซ้ำเดียวและไม่มีลูกเล่นอื่น ตามที่เอริคแสดงความคิดเห็นมันใช้forลูปซึ่งอาจไม่ถูกต้องภายใต้ข้อ จำกัด ของ 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;
    }
}

ใช่มันใช้งานได้ฉันเดา ขอบคุณมากสำหรับความช่วยเหลือทั้งหมด
Ronen Dvorkin

สุดท้ายdouble rec_sqrt_series(int n)IMO เป็นไปตามเป้าหมายของ OP โดยใช้เครื่องหมายเป็นธงวนซ้ำ (ฉันจะทิ้งสิ่งelseที่ไม่จำเป็นอย่างที่returnเป็นif)
chux - Reinstate Monica

1
@ chux-ReinstateMonica: การวางelseเป็นไปได้แน่นอน แต่ฉันชอบที่จะสมมาตรของทั้งสองสาขาของการifส่งคืนผลลัพธ์เรียงลำดับของรูปแบบการเขียนโปรแกรมการทำงาน
chqrlie

@ chux-ReinstateMonica: ฉันคาดหวังความต้องการของการมอบหมาย "การเรียกซ้ำเท่านั้น" ห้ามการทำซ้ำ
Eric Postpischil

@EricPostpischil: ใช่ฉันคิดเหมือนกัน แต่ไม่ได้รับข้อเสนอแนะจาก OP
chqrlie

0

นี่คือวิธีการอื่น

มันขึ้นอยู่กับintการเป็น 32 บิต แนวคิดคือการใช้ 32 บิตบนของ 64 บิตintไป

1) ดูว่าการโทรเป็นการโทรซ้ำ (หรือการโทรจาก "ภายนอก")

2) บันทึกค่าเป้าหมายใน 32 บิตด้านบนในระหว่างการเรียกซ้ำ

// 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
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.