ฉันจะกำหนดรันไทม์ของฟังก์ชันเรียกซ้ำสองครั้งได้อย่างไร


15

ด้วยฟังก์ชั่นซ้ำสองโดยพลการหนึ่งจะคำนวณเวลาการทำงานของมันได้อย่างไร

ตัวอย่าง (ใน pseudocode):

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

หรือบางสิ่งบางอย่างตามเส้นเหล่านั้น

วิธีการใดที่สามารถหรือควรใช้เพื่อกำหนดสิ่งนี้


2
เป็นการบ้านหรือไม่?
เบอร์นาร์ด

5
ไม่ถึงเวลาฤดูร้อนและฉันชอบที่จะเรียนรู้ ฉันคิดไปข้างหน้าแทนที่จะปล่อยให้สมองของฉันหันไปหาข้าวต้ม
if_zero_equals_one

11
โอเคเข้าใจแล้ว สำหรับผู้ที่ลงคะแนนเพื่อย้ายข้อมูลนี้ไปยัง Stack Overflow: นี่เป็นหัวข้อที่นี่และเป็นหัวข้อนอกสำหรับ Stack Overflow Programmers.SE สำหรับคำถามเชิงแนวคิด, ไวท์บอร์ด - y; Stack Overflow สำหรับคำถามเกี่ยวกับการนำไปใช้ปัญหาขณะเขียนโค้ด

3
ขอบคุณนั่นคือเหตุผลที่ฉันทำที่นี่ตั้งแต่แรก นอกจากนี้ยังเป็นการดีกว่าที่จะรู้วิธีตกปลามากกว่ารับปลา
if_zero_equals_one

1
ในกรณีพิเศษนี้โดยทั่วไปแล้วการเรียกซ้ำแบบไม่สิ้นสุดเนื่องจาก b (a (0)) เรียกใช้คำศัพท์ b (a (0)) อีกมากมายอย่างไม่มีที่สิ้นสุด มันจะแตกต่างกันถ้าเป็นสูตรคณิตศาสตร์ หากการตั้งค่าของคุณแตกต่างออกไป เช่นเดียวกับในคณิตศาสตร์ใน cs ปัญหาบางอย่างมีวิธีแก้ปัญหาบางอย่างไม่มีบางอย่างมีวิธีง่าย ๆ บางคนไม่มี มีหลายกรณีที่เกิดร่วมกันซ้ำซึ่งโซลูชันจะมีอยู่ บางครั้งเพื่อที่จะไม่ระเบิดกองหนึ่งจะต้องใช้รูปแบบผ้าใบ
งาน

คำตอบ:


11

คุณยังคงเปลี่ยนฟังก์ชั่นของคุณ แต่ให้เลือกคนที่จะทำงานตลอดไปโดยไม่ต้องแปลง ..

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

หากการติดตามผ่านให้คำตอบคุณควรลองหารูปแบบหรือความสัมพันธ์ที่เกิดซ้ำระหว่างคำตอบ เมื่อคุณมีสิ่งนั้นแล้วคุณสามารถลองหารันไทม์ของมัน การคิดออกอาจซับซ้อนมาก แต่เรามีผลลัพธ์เช่นทฤษฎีบทหลักที่ให้เราหาคำตอบในหลาย ๆ กรณี

ระวังว่าถึงแม้จะมีการเรียกซ้ำครั้งเดียวมันก็ง่ายที่จะเกิดขึ้นกับฟังก์ชั่นที่รันไทม์เราไม่ทราบวิธีการคำนวณ ตัวอย่างเช่นพิจารณาดังต่อไปนี้:

def recursive (n):
    if 0 == n%2:
        return 1 + recursive(n/2)
    elif 1 == n:
        return 0
    else:
        return recursive(3*n + 1)

มันเป็นอยู่ในปัจจุบันที่ไม่รู้จักไม่ว่าจะเป็นฟังก์ชั่นนี้อยู่เสมอที่ดีที่กำหนดให้อยู่คนเดียวสิ่งรันไทม์ของมันคือ


5

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


ไม่ได้มองหาฟังก์ชั่นเฉพาะที่นี่ ฉันกำลังมองหาวิธีทั่วไปในการค้นหาเวลาทำงานของฟังก์ชั่นวนซ้ำที่เรียกซึ่งกันและกัน
if_zero_equals_one

1
ฉันไม่แน่ใจว่ามีวิธีแก้ปัญหาในกรณีทั่วไป เพื่อให้ Big-O เข้าท่าคุณต้องรู้ว่าอัลกอริทึมจะหยุดชะงักหรือไม่ มีอัลกอริทึมแบบเรียกซ้ำบางส่วนที่คุณต้องเรียกใช้การคำนวณก่อนที่คุณจะรู้ว่าต้องใช้เวลานานเท่าใด (เช่นการพิจารณาว่าจุดเป็นของ Mandlebrot ที่ตั้งไว้หรือไม่)
jimreed

ไม่เสมอaโทรเฉพาะbถ้าหมายเลขที่ส่งเป็น> = 0 แต่ใช่มีการวนซ้ำไม่สิ้นสุด
btilly

1
@btilly ตัวอย่างมีการเปลี่ยนแปลงหลังจากที่ฉันโพสต์คำตอบของฉัน
jimreed

1
@ jimreed: และมันก็เปลี่ยนไปอีกครั้ง ฉันจะลบความคิดเห็นของฉันถ้าทำได้
btilly

4

วิธีที่ชัดเจนคือการเรียกใช้ฟังก์ชันและวัดระยะเวลาที่ใช้ สิ่งนี้จะบอกคุณว่าต้องใช้เวลานานแค่ไหนในอินพุตเฉพาะ และถ้าคุณไม่ทราบล่วงหน้าว่าฟังก์ชั่นนั้นสิ้นสุดลงยาก: ไม่มีวิธีเชิงกลที่จะคิดออกว่าฟังก์ชั่นนั้นจะจบลงหรือไม่นั่นเป็นปัญหาการหยุดทำงานและไม่สามารถตัดสินใจได้

หาเวลาการทำงานของฟังก์ชั่นเป็นที่ตัดสินไม่เหมือนกันโดยทฤษฎีบทข้าว ในความเป็นจริงทฤษฎีบทของไรซ์แสดงให้เห็นว่าแม้แต่การตัดสินใจว่าฟังก์ชั่นทำงานในO(f(n))เวลานั้นไม่สามารถตัดสินใจได้หรือไม่

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

T_a(x) = if x ≤ 0 then 1 else T_b(x-1) + T_a(x-1)
T_b(x) = if x ≤ -5 then 1 else T_b(T_a(x-1))

อะไรต่อไป ตอนนี้คุณมีปัญหาทางคณิตศาสตร์: คุณต้องแก้สมการการทำงานเหล่านี้ วิธีการที่มักจะทำงานคือการเปิดสมการเหล่านี้ในการทำงานจำนวนเต็มเข้าไปในสมการในการทำงานการวิเคราะห์และการใช้แคลคูลัสที่จะแก้ปัญหาเหล่านี้ตีความฟังก์ชั่นT_aและT_bเป็นฟังก์ชั่นการสร้าง

ในการสร้างฟังก์ชั่นและหัวข้อทางคณิตศาสตร์ที่ไม่ต่อเนื่องอื่น ๆ ฉันขอแนะนำหนังสือคณิตศาสตร์คณิตศาสตร์โดย Ronald Graham, Donald Knuth และ Oren Patashnik


1

ดังที่คนอื่น ๆ ชี้ให้เห็นการวิเคราะห์การเรียกซ้ำสามารถทำได้ยากมากอย่างรวดเร็ว นี่เป็นอีกตัวอย่างของสิ่งนั้น: http://rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences มันยากที่จะคำนวณคำตอบและเวลาทำงานสำหรับสิ่งเหล่านี้ นี่เป็นเพราะฟังก์ชั่นที่เกิดซ้ำซึ่งกันและกันเหล่านี้มี "รูปแบบที่ยาก"

ลองดูตัวอย่างง่ายๆนี้:

http://pramode.net/clojure/2010/05/08/clojure-trampoline/

(declare funa funb)
(defn funa [n]
  (if (= n 0)
    0
    (funb (dec n))))
(defn funb [n]
  (if (= n 0)
    0
    (funa (dec n))))

เริ่มจากการลองคำนวณfuna(m), m > 0:

funa(m) = funb(m - 1) = funa(m - 2) = ... funa(0) or funb(0) = 0 either way.

เวลาทำงานคือ:

R(funa(m)) = 1 + R(funb(m - 1)) = 2 + R(funa(m - 2)) = ... m + R(funa(0)) or m + R(funb(0)) = m + 1 steps either way

ตอนนี้ลองเลือกตัวอย่างที่ซับซ้อนกว่านี้อีกเล็กน้อย:

แรงบันดาลใจจากhttp://planetmath.org/encyclopedia/MutualRecursion.htmlซึ่งเป็นการอ่านที่ดีด้วยตัวเองลองดูที่: "" "ตัวเลข Fibonacci สามารถตีความได้ผ่านการสอบถามซ้ำ: F (0) = 1 และ G (0 ) = 1, ด้วย F (n + 1) = F (n) + G (n) และ G (n + 1) = F (n). "" "

แล้ว runtime ของ F คืออะไร? เราจะไปทางอื่น
ดี R (F (0)) = 1 = F (0); R (G (0)) = 1 = G (0)
ตอนนี้ R (F (1)) = R (F (0)) + R (G (0)) = F (0) + G (0) = F (1)
...
ไม่ยากเลยที่จะเห็นว่า R (F (m)) = F (m) - เช่นจำนวนการเรียกใช้ฟังก์ชันที่จำเป็นในการคำนวณหมายเลขฟีโบนักชีที่ดัชนีฉันเท่ากับค่าของหมายเลขฟีโบนักชี ที่ดัชนีฉัน นี่เป็นการสันนิษฐานว่าการรวมตัวเลขสองตัวเข้าด้วยกันนั้นเร็วกว่าการเรียกใช้ฟังก์ชันมาก หากไม่ใช่ในกรณีนี้จะเป็นจริง: R (F (1)) = R (F (0)) + 1 + R (G (0)) และการวิเคราะห์สิ่งนี้จะซับซ้อนกว่านี้ อาจไม่มีวิธีแก้ปัญหาแบบปิดง่าย

รูปแบบปิดสำหรับลำดับฟีโบนักชีนั้นไม่จำเป็นต้องง่ายต่อการผสมซ้ำไม่ต้องพูดถึงตัวอย่างที่ซับซ้อนมากขึ้น


0

สิ่งแรกที่ต้องทำคือการแสดงให้เห็นว่าฟังก์ชั่นที่คุณได้กำหนดไว้ยุติและค่าที่แน่นอน ในตัวอย่างที่คุณได้กำหนดไว้

int a(int x){
  if (x < = 0)
    return 1010;
  else
    return b(x-1) + a(x-1);
}
int b(int y){
  if (y <= -5)
    return -2;
  else
    return b(a(y-1));
}

bเพียงยุติสำหรับy <= -5เพราะถ้าคุณเสียบค่าอื่น ๆ b(a(y-1))แล้วคุณจะมีระยะเวลาของแบบฟอร์ม ถ้าคุณทำนิด ๆ หน่อย ๆ ขยายตัวคุณจะเห็นว่าในระยะของแบบฟอร์มb(a(y-1))ในที่สุดก็นำไปสู่คำb(1010)ซึ่งนำไปสู่คำอีกครั้งซึ่งจะนำไปสู่คำว่าb(a(1009)) b(1010)ซึ่งหมายความว่าคุณไม่สามารถเสียบค่าใด ๆ ลงในaสิ่งที่ไม่พึงx <= -4ประสงค์ได้เพราะหากคุณจบด้วยลูปแบบไม่สิ้นสุดที่ค่าที่จะคำนวณนั้นขึ้นอยู่กับค่าที่จะคำนวณ ดังนั้นตัวอย่างนี้จึงมีเวลาทำงานคงที่

ดังนั้นคำตอบง่ายๆคือไม่มีวิธีทั่วไปในการกำหนดเวลาทำงานของฟังก์ชั่นวนซ้ำเนื่องจากไม่มีขั้นตอนทั่วไปที่กำหนดว่าฟังก์ชันที่กำหนดซ้ำจะสิ้นสุดลงหรือไม่


-5

รันไทม์เหมือนใน Big-O?

ง่ายมาก: O (N) - สมมติว่ามีเงื่อนไขการยกเลิก

การเรียกซ้ำเป็นเพียงการวนซ้ำและการวนรอบแบบง่ายคือO (N)ไม่ว่าคุณจะทำอะไรหลายอย่างในลูปนั้น (และการเรียกเมธอดอื่นเป็นเพียงขั้นตอนหนึ่งในการวนซ้ำ)

จุดที่น่าสนใจก็คือถ้าคุณมีการวนซ้ำภายในวิธีการวนซ้ำอย่างน้อยหนึ่งวิธี ในกรณีนั้นคุณจะต้องจบด้วยการยกกำลังแบบทวีคูณ (คูณด้วยO (N)ในแต่ละวิธีผ่าน)


2
คุณกำหนดประสิทธิภาพของ Big-O โดยใช้ลำดับสูงสุดของวิธีที่เรียกและคูณด้วยลำดับของวิธีการเรียกใช้ อย่างไรก็ตามเมื่อคุณเริ่มพูดคุยเกี่ยวกับประสิทธิภาพแบบเอ็กซ์โปแนนเชียลและแฟกทอเรียลคุณสามารถละเว้นการแสดงแบบพหุนาม ฉันเชื่อว่าการถือครองแบบเดียวกันเมื่อเปรียบเทียบเลขชี้กำลังและแฟคทอเรียล: แฟคทอเรียลชนะ ฉันไม่เคยวิเคราะห์ระบบที่มีทั้งเลขยกกำลังและแฟคทอเรียล
Anon

5
สิ่งนี้ไม่ถูกต้อง รูปแบบการคำนวณซ้ำของการคำนวณหมายเลขฟีโบนักชีและ Quicksort คือO(2^n)และO(n*log(n))ตามลำดับ
unpythonic

1
โดยไม่ต้องทำหลักฐานแฟนซีบางอย่างที่ฉันต้องการที่จะนำคุณไปยังamazon.com/Introduction-Algorithms-Second-Thomas-Cormen/dp/...และพยายามที่การดูที่เว็บไซต์นี้ SE cstheory.stackexchange.com
ไบรอันแฮร์ริงตัน

4
เหตุใดผู้คนจึงโหวตคำตอบที่ผิดอย่างมหันต์นี้ การเรียกเมธอดนั้นใช้เวลาตามสัดส่วนกับเวลาที่เมธอดนั้นใช้ ในกรณีนี้วิธีการaโทรbและbการโทรaเพื่อให้คุณไม่สามารถO(1)เพียงแค่คิดว่าทั้งสองวิธีนี้ต้องใช้เวลา
btilly

2
@Anon - โปสเตอร์กำลังขอฟังก์ชั่นการเรียกซ้ำซ้ำซ้อนตามอำเภอใจไม่เฉพาะที่แสดงไว้ด้านบน ฉันให้สองตัวอย่างของการเรียกซ้ำแบบง่ายซึ่งไม่ตรงกับคำอธิบายของคุณ เป็นการแปลงมาตรฐานเก่า ๆ ให้กลายเป็น "double-recursive" แบบธรรมดาซึ่งเป็นการอธิบายแบบเอกซ์โปเนนเชียล
unpythonic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.