จะอธิบายอัลกอริทึมพิสูจน์และวิเคราะห์ได้อย่างไร


20

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

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

มันเป็นความขัดแย้งของผู้เขียนที่เราเข้าใจจริง ๆ ว่าทำไมอัลกอริธึมจึงถูกต้องเฉพาะเมื่อเราไปถึงจุดที่ความคิดของเราเติมเต็มโดยนัยในการยืนยันทั้งหมดตามที่ทำในรูปที่ 4

ฉันไม่เคยเจอเรื่องแบบนี้เลย ข้อดีอีกอย่างคือเราสามารถนับจำนวนครั้งที่ดำเนินการแต่ละขั้นตอน ง่ายต่อการตรวจสอบกับกฎข้อแรกของ Kirchhoff ฉันไม่ได้วิเคราะห์เวลาทำงานอย่างแน่นอนดังนั้นบางส่วนอาจถูกตัดออกเมื่อฉันประเมินเวลาทำงาน±1

การวิเคราะห์คำสั่งของการเติบโตบางครั้งก็ไร้ประโยชน์ ตัวอย่างเช่นเราไม่สามารถแยกแยะ quicksort จาก heapsort ได้เนื่องจากทั้งหมดคือโดยที่คือจำนวนสุ่มตัวแปรคาดหวังดังนั้นเราควรวิเคราะห์ค่าคงที่พูดและE (T_2 (n)) = A_2 \ lg n + B_2n + O (\ log n)ดังนั้นเราสามารถเปรียบเทียบT_1และT_2ดีกว่า และบางครั้งเราควรเปรียบเทียบปริมาณอื่น ๆ เช่นความแปรปรวน การวิเคราะห์คำสั่งซื้อของการเจริญเติบโตของเวลาทำงานเท่านั้นไม่เพียงพอ ในฐานะที่เป็นTAOCPE X X E ( T 1 ( n ) ) = A 1 n lg n + B 1 n + O ( log n ) E ( T 2 ( n ) ) = 2 LG n + B 2 n + O (E(T(n))=Θ(nเข้าสู่ระบบn)EXXE(T1(n))=A1nlgn+B1n+O(logn)T 1 T 2E(T2(n))=A2lgn+B2n+O(logn)T1T2 แปลอัลกอริธึมเป็นภาษาแอสเซมบลีและคำนวณเวลาทำงานมันยากเกินไปสำหรับฉันดังนั้นฉันต้องการทราบเทคนิคบางอย่างในการวิเคราะห์เวลาทำงานอีกเล็กน้อยซึ่งมีประโยชน์สำหรับภาษาระดับสูงเช่น C, C ++ หรือรหัสเทียม

และฉันต้องการที่จะรู้ว่าลักษณะของคำอธิบายส่วนใหญ่ที่ใช้ในงานวิจัยและวิธีการจัดการกับปัญหาเหล่านี้


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

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

1
@Raphael ของฉันในทางปฏิบัติหมายความว่าในการปฏิบัติของงานวิจัยที่ไม่ได้เขียนโปรแกรม
Yai0Phah

@ Frank คุณหมายถึงอะไรจากความแปรปรวน ? การทดสอบประสิทธิภาพของฉันทำให้ฉันมีช่วงเวลาที่แปรปรวน
edA-qa mort-ora-y

@ ราฟาเอลจุดแรกของคุณนี่ไม่เป็นความจริงอีกต่อไป ชิปที่ทันสมัยเรียงลำดับชุดประกอบของคุณอีกครั้งทำการจัดเก็บ / โหลดที่ล้าสมัยและคาดการณ์การทำงานและการโหลด สำหรับการเห็นพ้องด้วยและประเด็นก่อนหน้านี้การวิเคราะห์อย่างละเอียดเป็นสิ่งจำเป็นจริง ๆ แต่ฉันไม่ได้ทำในรูปแบบที่เป็นทางการ
edA-qa mort-ora-y

คำตอบ:


18

มีวิธีการที่เป็นไปได้มากมายหลากหลาย สิ่งที่เหมาะสมที่สุดขึ้นอยู่กับ

  • สิ่งที่คุณพยายามแสดง
  • คุณต้องการหรือต้องการรายละเอียดมากน้อยเพียงใด

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

ฉันจะให้คุณสามตัวอย่างสำหรับอัลกอริทึมที่รู้จักกันดีการผสานซึ่งหวังว่าจะแสดงสิ่งนี้

ระดับสูง

อัลกอริทึมที่ผสานรับรายการแบ่งออกเป็นสองส่วน (ประมาณ) ส่วนเท่า ๆ กันยาวซ้ำในรายการบางส่วนเหล่านั้นและผสานผลลัพธ์ (เรียงลำดับ) เพื่อผสานผลลัพธ์สุดท้าย ในรายการเดี่ยวหรือเปล่ามันจะส่งกลับเข้า

เห็นได้ชัดว่าอัลกอริทึมนี้เป็นอัลกอริทึมการเรียงลำดับที่ถูกต้อง การแยกรายการและการรวมมันสามารถทำได้ในเวลาซึ่งทำให้เราเกิดซ้ำสำหรับกรณีที่เลวร้ายที่สุดT ( n ) = 2 T ( n)Θ(n)) โดยทฤษฎีบทโทประเมินนี้เพื่อT(n)Θ(nlogn)T(n)=2T(n2)+Θ(n)T(n)Θ(nlogn)

ระดับปานกลาง

การรวมอัลกอริทึมได้รับจากรหัสหลอกต่อไปนี้:

procedure mergesort(l : List) {
  if ( l.length < 2 ) {
    return l
  }

  left  = mergesort(l.take(l.length / 2)
  right = mergesort(l.drop(l.length / 2)
  result = []

  while ( left.length > 0 || right.length > 0 ) {
    if ( right.length == 0 || (left.length > 0 && left.head <= right.head) ) {
      result = left.head :: result
      left = left.tail
    }
    else {
      result = right.head :: result
      right = right.tail
    }
  }

  return result.reverse
}

เราพิสูจน์ความถูกต้องโดยอุปนัย สำหรับรายการที่มีความยาวเป็นศูนย์หรืออย่างใดอย่างหนึ่งอัลกอริทึมนั้นถูกต้องเล็กน้อย ในฐานะที่เป็นสมมติฐานเหนี่ยวนำสมมติmergesortดำเนินการอย่างถูกต้องในรายชื่อที่มีความยาวมากที่สุดสำหรับพลบางส่วน แต่คงธรรมชาติn > 1 ตอนนี้ขอLเป็นรายการยาวn + 1 โดยสมมติฐานการเหนี่ยวนำและการจัดเรียงรุ่นแรกของ resp แรก ครึ่งหลังของLหลังจากการเรียกซ้ำ ดังนั้นการเลือกวงในทุกองค์ประกอบที่เล็กย้ำไม่ตรวจสอบและยังผนวกมัน; จึงเป็นรายการที่ไม่เรียงลำดับมากขึ้นที่มีองค์ประกอบทั้งหมดnn>1Ln+1leftrightLwhileresultresultleftrightและ ย้อนกลับเป็นรุ่นเรียงลำดับแบบไม่ลดลงซึ่งเป็นผลลัพธ์ที่ส่งคืนและที่ต้องการL

สำหรับรันไทม์ให้เรานับการเปรียบเทียบองค์ประกอบและการดำเนินการรายการ (ซึ่งครองไทม์ asymptotically) รายการความยาวน้อยกว่าสองสาเหตุไม่ สำหรับรายการของความยาวเรามีการดำเนินงานที่เกิดจากการจัดเตรียมปัจจัยการผลิตสำหรับการโทรซ้ำผู้ที่มาจากสาย recursive ตัวเองบวกห่วงและเป็นหนึ่งใน พารามิเตอร์แบบเรียกซ้ำทั้งสองสามารถคำนวณได้ด้วยการดำเนินรายการไม่เกินnรายการแต่ละรายการ การวนซ้ำถูกดำเนินการอย่างแน่นอนnครั้งและการวนซ้ำทุกครั้งจะทำให้เกิดการเปรียบเทียบองค์ประกอบหนึ่งรายการและการดำเนินการรายการสองรายการ สุดท้ายสามารถนำมาใช้เพื่อใช้2 nn>1whilereversenwhilenreverse2nการดำเนินการรายการ - ทุกองค์ประกอบจะถูกลบออกจากอินพุตและใส่ลงในรายการเอาท์พุท ดังนั้นการดำเนินการนับตอบสนองการเกิดซ้ำดังต่อไปนี้:

T(0)=T(1)=0T(n)T(n2)+T(n2)+7n

เนื่องจากเห็นได้ชัดว่าไม่ลดลงจึงเพียงพอที่จะพิจารณาn = 2 kสำหรับการเจริญเติบโตของซีมโทติค ในกรณีนี้การเกิดซ้ำจะง่ายขึ้นTn=2k

T(0)=T(1)=0T(n)2T(n2)+7n

โดยทฤษฎีบทปริญญาโทที่เราได้รับซึ่งทอดตัวรันไทม์ของTΘ(nlogn)mergesort

ระดับต่ำพิเศษ

พิจารณานี้ (ทั่วไป) การดำเนินงานของ mergesort ในอิสซาเบล / HOL :

types dataset  =  "nat * string"

fun leq :: "dataset \<Rightarrow> dataset \<Rightarrow> bool" where
   "leq (kx::nat, dx) (ky, dy) = (kx \<le> ky)"

fun merge :: "dataset list \<Rightarrow> dataset list \<Rightarrow> dataset list" where
"merge [] b = b" |
"merge a [] = a" |
"merge (a # as) (b # bs) = (if leq a b then a # merge as (b # bs) else b # merge (a # as) bs)"

function (sequential) msort :: "dataset list \<Rightarrow> dataset list" where
  "msort []  = []" |
  "msort [x] = [x]" |
  "msort l   = (let mid = length l div 2 in merge (msort (take mid l)) (msort (drop mid l)))"
by pat_completeness auto
  termination
  apply (relation "measure length")
by simp+

ซึ่งรวมถึงบทพิสูจน์ของความชัดเจนและการเลิกจ้าง ค้นหาหลักฐานถูกต้อง (เกือบ) สมบูรณ์ที่นี่

สำหรับ "รันไทม์" นั่นคือจำนวนการเปรียบเทียบการเกิดซ้ำที่คล้ายกับที่อยู่ในส่วนก่อนหน้านั้นสามารถตั้งค่าได้ แทนที่จะใช้ทฤษฎีบทต้นแบบและลืมค่าคงที่คุณสามารถวิเคราะห์เพื่อให้ได้ค่าประมาณที่เท่ากับเชิงปริมาณเท่ากับจำนวนจริง คุณสามารถค้นหาการวิเคราะห์แบบเต็มได้ใน [1]; นี่คือคร่าวๆ (มันไม่จำเป็นต้องพอดีกับรหัส Isabelle / HOL):

ดังกล่าวข้างต้นการเกิดซ้ำของจำนวนการเปรียบเทียบคือ

f0=f1=0fn=fn2+fn2+en

โดยที่คือจำนวนการเปรียบเทียบที่จำเป็นสำหรับการผสานผลลัพธ์บางส่วน² เพื่อกำจัดพื้นและเพดานเราดำเนินการแยกความแตกต่างของเคสว่าnคือ:enn

{f2m=2fm+e2mf2m+1=fm+fm+1+e2m+1

การใช้ความแตกต่างไปข้างหน้า / ถอยหลังแบบซ้อนของและe nเราได้สิ่งนั้นfnen

1k=1n1(nk)Δfk=fnnf1

ผลรวมตรงด้านขวามือของสูตร Perron ของ เรากำหนดชุดสร้าง Dirichletของเป็นΔfk

W(s)=k1Δfkks=112sk1Δekks=: (s)

ซึ่งพร้อมกับสูตรของ Perron ทำให้เรา

sfn=nf1+n2πi3i3+i(s)ns(12s)s(s+1)ds

การประเมินผลของขึ้นอยู่กับกรณีที่มีการวิเคราะห์ นอกเหนือจากนั้นเราสามารถ - หลังจากใช้เล่ห์เหลี่ยมบางอย่าง - ใช้ทฤษฎีที่เหลือเพื่อรับ(s)

fnnlog2(n)+nA(log2(n))+1

ที่เป็นฟังก์ชั่นระยะที่มีค่าใน[ - 1 , - 0.9 ]A[1,0.9]


  1. Mellin แปลงและ asymptotics: การเกิดซ้ำของการควบรวมกิจการโดย Flajolet และ Golin (1992)
  2. en=n2
    en=n1
    en=nn2n2+1n2n2+1

คำถามของฉันเกี่ยวกับการวิเคราะห์เวลาทำงานคือวิธีการตรวจสอบ α และ β อย่างแน่นอน T(n)=T(n/2)+T(n/2)+αn+βซึ่งใกล้เคียงกับการปฏิบัติ (เช่นมีประโยชน์ในการเปรียบเทียบการผสานเรียงลำดับและ qsort)
Yai0Phah

@Frank: คำตอบสั้น ๆ คือคุณไม่สามารถ ; ค่าคงที่นั้นขึ้นอยู่กับรายละเอียดการติดตั้งรวมถึงสถาปัตยกรรมของเครื่องภาษาและคอมไพเลอร์ซึ่งมีความสำคัญกับอัลกอริทึมพื้นฐาน
JeffE

@JeffE ฉันต้องอ้างว่า α และ βควรจะแน่นอนพอที่จะทำการเปรียบเทียบบางอย่าง แบบจำลองทางคณิตศาสตร์ที่สามารถทำงานได้มากมายโดยไม่ต้องใช้ภาษาเครื่องเพื่อกำหนดค่าคงที่
Yai0Phah

@JeffE ตัวอย่างเช่น MIX / MMIX ใน taocp คือ แต่มันยากเกินไปที่จะแปลอัลกอริทึมเป็นภาษาเครื่องดังกล่าว
Yai0Phah

@ Frankank วิทยาศาสตร์: เพื่อที่จะได้ใกล้ชิดกับการปฏิบัติคุณจะต้องนับการดำเนินการทั้งหมด (เช่น Knuth ทำ) จากนั้นคุณสามารถยกตัวอย่างผลลัพธ์ของคุณด้วยต้นทุนการดำเนินการเฉพาะเครื่องจักรเพื่อรับรันไทม์จริง โดยปกติผู้คนจะนับการดำเนินการบางอย่างเท่านั้นและในกรณีนั้นการแก้ไขα และ βไม่ได้บอกคุณมาก
Raphael

3

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

เมื่อเริ่มต้นในหนังสือเช่นนี้เราจะพบกับคำถามทันที: "ฉันจะใช้ภาษาการเขียนโปรแกรมภาษาใด" และนี่ไม่ใช่เป็นเพียงการนำเสนอเท่านั้น! สิ่งที่สำคัญที่สุด แต่ยังเป็นเครื่องมือที่เข้าใจได้ยากที่สุดคืออิทธิพลของมันต่อนิสัยของผู้ที่ฝึกฝนตัวเองในการใช้งาน หากเครื่องมือเป็นภาษาการเขียนโปรแกรมอิทธิพลนี้คือ - ไม่ว่าเราจะชอบหรือไม่ก็ตาม - มีอิทธิพลต่อพฤติกรรมการคิดของเรา จากการวิเคราะห์ที่มีอิทธิพลต่อความรู้ที่ดีที่สุดของฉันฉันได้ข้อสรุปว่าภาษาการเขียนโปรแกรมที่มีอยู่หรือส่วนย่อยของพวกเขาจะไม่เหมาะกับจุดประสงค์ของฉัน ในขณะที่ฉันรู้ว่าตัวเองยังไม่พร้อมสำหรับการออกแบบภาษาการเขียนโปรแกรมใหม่ที่ฉันได้ปฏิญาณว่าจะไม่ทำในอีกห้าปีข้างหน้าและฉันมีความรู้สึกที่ชัดเจนที่สุดว่าช่วงเวลานั้นยังไม่ผ่าน! (ก่อนหน้านั้นมีเอกสารอื่น ๆ อีกมากมายที่ต้องเขียน

หลังจากนั้นเขาอธิบายว่าตัวเองเล็กแค่ไหนที่จะได้รับภาษามินิของเขา

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

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