วิธีทั่วไปในการแปลงลูป (ในขณะ / สำหรับ) การเรียกซ้ำหรือจากการวนซ้ำเป็นลูป?


23

ปัญหานี้เน้นไปที่อัลกอริทึมเป็นหลักซึ่งอาจเป็นสิ่งที่เป็นนามธรรมและเป็นวิชาการ

ตัวอย่างคือการเสนอความคิดฉันต้องการวิธีการทั่วไปดังนั้นตัวอย่างจะใช้เพื่อทำให้เราชัดเจนยิ่งขึ้นเกี่ยวกับความคิดของคุณ

โดยทั่วไปแล้วการวนซ้ำสามารถแปลงเป็นแบบวนซ้ำได้

เช่น:

for(int i=1;i<=100;++i){sum+=i;}

และ recursive ที่เกี่ยวข้องคือ:

int GetTotal(int number)
{
   if (number==1) return 1;   //The end number
   return number+GetTotal(number-1); //The inner recursive
}

และท้ายที่สุดเพื่อลดความซับซ้อนนี้จำเป็นต้องเรียกซ้ำแบบหาง:

int GetTotal (int number, int sum)
{
    if(number==1) return sum;
    return GetTotal(number-1,sum+number);
}

อย่างไรก็ตามกรณีส่วนใหญ่นั้นไม่ใช่เรื่องง่ายที่จะตอบและวิเคราะห์ สิ่งที่ฉันอยากรู้คือ:

1) เราสามารถหา "วิธีการทั่วไปทั่วไป" ในการแปลงลูป (สำหรับ / ในขณะที่……) เป็นแบบเรียกซ้ำได้หรือไม่? และเราควรใส่ใจกับสิ่งใดบ้างในขณะทำการเปลี่ยนใจเลื่อมใส? มันจะเป็นการดีกว่าถ้าคุณเขียนข้อมูลโดยละเอียดกับตัวอย่างและทฤษฎี persudo ของคุณรวมถึงกระบวนการแปลง

2) "Recursive" มีสองรูปแบบ: Linely recursive และ Tail-Recursive แล้วแปลงไหนดีกว่ากัน? "กฎ" อะไรที่เราควรเชี่ยวชาญ

3) บางครั้งเราจำเป็นต้องรักษา "ประวัติศาสตร์" ของการเรียกซ้ำสิ่งนี้สามารถทำได้อย่างง่ายดายในคำสั่งวนซ้ำ:

เช่น:

List<string> history = new List<string>();
int sum=0;
for (int i=1;i<=100;++i)
{
   if(i==1) history.Add(i.ToString()+"'s result is:1.");
   else
   {
     StringBuilder sub = new StringBuilder();

      for(int j=1;j<=i;++j)
      {
          if(j==i) sbu.Append(j.ToString());
          else
          {
            sub.Append(j.ToString()+"+");
          }
      }
    sum +=i;
    sbu.Append("'s result is:"+sum+Environment.NewLine);
   }
}

ผลลัพธ์ด้านล่างคือ:

ผลลัพธ์ 1 คือ 1

ผลลัพธ์ของ 1 + 2 คือ 3

1 + 2 + 3 ผลลัพธ์คือ 6 …………

อย่างไรก็ตามฉันคิดว่าเป็นการยากที่จะเก็บประวัติไว้ในแบบเรียกซ้ำเนื่องจากอัลกอริทึมแบบเรียกซ้ำมุ่งเน้นไปที่การรับผลลัพธ์สุดท้ายและทำการโทรกลับ ดังนั้นสิ่งเหล่านี้จะทำผ่านสแต็กที่ดูแลโดยภาษาการเขียนโปรแกรมกำหนดหน่วยความจำในรูปแบบของสแต็คโดยอัตโนมัติ และวิธีที่เรา "ด้วยตนเอง" นำแต่ละ "ค่าสแต็ก" ออกและคืนค่าหลายค่าผ่านอัลกอริทึมแบบเรียกซ้ำได้อย่างไร

และสิ่งที่เกี่ยวกับ "จากอัลกอริทึมแบบเรียกซ้ำไปยังวนซ้ำ" พวกเขาสามารถแปลงแต่ละอื่น ๆ (ฉันคิดว่ามันควรจะทำในทางทฤษฎี แต่ฉันต้องการสิ่งที่ถูกต้องมากขึ้นเพื่อพิสูจน์ความคิดของฉัน)


"persudo" หมายถึงอะไร
ริ้น

คำตอบ:


30

ที่จริงคุณควรแยกฟังก์ชันลงก่อน:

ห่วงมีบางส่วน:

  1. ส่วนหัวและการประมวลผลก่อนที่จะห่วง อาจประกาศตัวแปรใหม่บางอย่าง

  2. เงื่อนไขเมื่อหยุดลูป

  3. ร่างกายของวงจริง มันเปลี่ยนตัวแปรบางส่วนของส่วนหัวและ / หรือพารามิเตอร์ที่ส่งผ่าน

  4. หาง; จะเกิดอะไรขึ้นหลังจากลูปและส่งคืนผลลัพธ์

หรือเขียนมันออกมา:

foo_iterative(params){
    header
    while(condition){
        loop_body
    }
    return tail
}

การใช้บล๊อกเหล่านี้เพื่อโทรแบบเรียกซ้ำจะค่อนข้างตรงไปตรงมา:

foo_recursive(params){
    header
    return foo_recursion(params, header_vars)
}

foo_recursion(params, header_vars){
    if(!condition){
        return tail
    }

    loop_body
    return foo_recursion(params, modified_header_vars)
}

ฯลฯ หางวนซ้ำแบบวนซ้ำ breaks และcontinues ในตัวลูปจะยังคงต้องถูกแทนที่ด้วยreturn tailและกลับมาfoo_recursion(params, modified_header_vars)ตามต้องการ แต่มันก็ง่ายพอ


การไปทางอื่นนั้นซับซ้อนกว่า ส่วนหนึ่งเป็นเพราะอาจมีการเรียกซ้ำหลายครั้ง ซึ่งหมายความว่าทุกครั้งที่เราเปิดเฟรมสแต็กอาจมีหลายที่ที่เราต้องการดำเนินการต่อ นอกจากนี้อาจมีตัวแปรที่เราต้องบันทึกข้ามการเรียกซ้ำและพารามิเตอร์ดั้งเดิมของการโทร

เราสามารถใช้สวิตช์เพื่อแก้ไข:

bar_recurse(params){
    if(baseCase){
        finalize
        return
    }
    body1
    bar_recurse(mod_params)
    body2
    bar_recurse(mod_params)
    body3
}


bar_iterative(params){
    stack.push({init, params})

    while(!stack.empty){
        stackFrame = stack.pop()

        switch(stackFrame.resumPoint){
        case init:
            if(baseCase){
                finalize
                break;
            }
            body1
            stack.push({resum1, params, variables})
            stack.push({init, modified_params})
            break;
        case resum1:
            body2
            stack.push({resum2, params, variables})
            stack.push({init, modified_params})
            break;
        case resum2:
            body3
            break;
        }
    }
}

0

การติดตามคำตอบของ @ratchet freak ฉันได้สร้างตัวอย่างของวิธีการที่ฟีโบนัชชีฟังก์ชั่นนี้สามารถเขียนใหม่เป็นวงวนในขณะที่ Java โปรดทราบว่ามีวิธีที่ง่ายกว่ามาก (และมีประสิทธิภาพ) ในการเขียน Fibonacci อีกครั้งด้วยการวนรอบสักครู่

class CallContext { //this class is similar to the stack frame

    Object[] args;

    List<Object> vars = new LinkedList<>();

    int resumePoint = 0;

    public CallContext(Object[] args) {
        this.args = args;
    }

}


static int fibonacci(int fibNumber) {
    Deque<CallContext> callStack = new LinkedList<>();
    callStack.add(new CallContext(new Object[]{fibNumber}));
    Object lastReturn = null; //value of last object returned (when stack frame was dropped)
    while (!callStack.isEmpty()) {
        CallContext callContext = callStack.peekLast();
        Object[] args = callContext.args;
        //actual logic starts here
        int arg = (int) args[0];
        if (arg == 0 || arg == 1) {
            lastReturn = arg;
            callStack.removeLast();
        } else {
            switch (callContext.resumePoint) {
                case 0: //calculate fib(n-1)
                    callStack.add(new CallContext(new Object[]{arg - 1}));
                    callContext.resumePoint++;
                    break;
                case 1: //calculate fib(n-2)
                    callContext.vars.add(lastReturn); //fib1
                    callStack.add(new CallContext(new Object[]{arg - 2}));
                    callContext.resumePoint++;
                    break;
                case 2: // fib(n-1) + fib(n-2)
                    callContext.vars.add(lastReturn); //fib2
                    lastReturn = (int) callContext.vars.get(0) + (int) callContext.vars.get(1);
                    callStack.removeLast();
                    break;
            }
        }
    }
    return (int) lastReturn;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.