คืออะไรเวลา pseudopolynomial ? แตกต่างจากพหุนามเวลาอย่างไร? อัลกอริทึมบางอย่างที่ทำงานในเวลาหลอกมี runtimes เช่น O (nW) (สำหรับปัญหา 0/1 Knapsack ) หรือ O (√n) (สำหรับการแบ่งการทดลอง ) ทำไมไม่นับว่าเป็นพหุนามเวลา?
คืออะไรเวลา pseudopolynomial ? แตกต่างจากพหุนามเวลาอย่างไร? อัลกอริทึมบางอย่างที่ทำงานในเวลาหลอกมี runtimes เช่น O (nW) (สำหรับปัญหา 0/1 Knapsack ) หรือ O (√n) (สำหรับการแบ่งการทดลอง ) ทำไมไม่นับว่าเป็นพหุนามเวลา?
คำตอบ:
เพื่อให้เข้าใจถึงความแตกต่างระหว่างเวลาพหุนามและเวลาเทียมเราต้องเริ่มต้นด้วยการกำหนดความหมายของ "เวลาพหุนาม" อย่างเป็นทางการ
สัญชาตญาณทั่วไปสำหรับเวลาพหุนามคือ "เวลา O (n k ) สำหรับบาง k" ตัวอย่างเช่นการเรียงลำดับการเลือกจะทำงานในเวลา O (n 2 ) ซึ่งเป็นเวลาแบบพหุนามในขณะที่การแก้TSPแบบ brute-force จะใช้เวลา O (n · n!) ซึ่งไม่ใช่เวลาพหุนาม
เวลาทำงานเหล่านี้ล้วนอ้างถึงตัวแปร n บางตัวที่ติดตามขนาดของอินพุต ตัวอย่างเช่นในการเรียงลำดับการเลือก n หมายถึงจำนวนองค์ประกอบในอาร์เรย์ในขณะที่ใน TSP n หมายถึงจำนวนโหนดในกราฟ เพื่อสร้างมาตรฐานของคำจำกัดความของความหมายที่แท้จริงในบริบทนี้นิยามอย่างเป็นทางการของความซับซ้อนของเวลากำหนด "ขนาด" ของปัญหาดังนี้:
ขนาดของอินพุตสำหรับปัญหาคือจำนวนบิตที่ต้องใช้ในการเขียนอินพุตนั้น
ตัวอย่างเช่นหากอินพุตไปยังอัลกอริทึมการเรียงลำดับเป็นอาร์เรย์ของจำนวนเต็ม 32 บิตขนาดของอินพุตจะเป็น 32n โดยที่ n คือจำนวนรายการในอาร์เรย์ ในกราฟที่มีโหนดโหนดและขอบ m อินพุตอาจถูกระบุเป็นรายการของโหนดทั้งหมดตามด้วยรายการของขอบทั้งหมดซึ่งจะต้องใช้บิตΩ (n + m)
จากคำจำกัดความนี้คำจำกัดความอย่างเป็นทางการของเวลาพหุนามมีดังต่อไปนี้:
อัลกอริทึมทำงานในเวลาพหุนามถ้ารันไทม์เป็น O (x k ) สำหรับค่าคงที่ k โดยที่ x หมายถึงจำนวนบิตของอินพุตที่กำหนดให้กับอัลกอริทึม
เมื่อทำงานกับอัลกอริทึมที่ประมวลผลกราฟรายการต้นไม้ ฯลฯ คำจำกัดความนี้จะเห็นด้วยกับนิยามทั่วไปไม่มากก็น้อย ตัวอย่างเช่นสมมติว่าคุณมีอัลกอริทึมการเรียงลำดับที่จัดเรียงอาร์เรย์ของจำนวนเต็ม 32 บิต หากคุณใช้บางอย่างเช่นการเรียงลำดับการเลือกเพื่อทำสิ่งนี้รันไทม์ซึ่งเป็นฟังก์ชันของจำนวนองค์ประกอบอินพุตในอาร์เรย์จะเป็น O (n 2 ) แต่ n จำนวนองค์ประกอบในอาร์เรย์อินพุตสอดคล้องกับจำนวนบิตอินพุตอย่างไร? ดังที่ได้กล่าวไว้ก่อนหน้านี้จำนวนบิตของอินพุตจะเป็น x = 32n ดังนั้นหากเราแสดงรันไทม์ของอัลกอริทึมในรูปของ x แทนที่จะเป็น n เราจะได้ว่ารันไทม์คือ O (x 2 ) ดังนั้นอัลกอริทึมจึงทำงานในเวลาพหุนาม
ในทำนองเดียวกันสมมติว่าคุณทำการค้นหาเชิงลึกก่อนบนกราฟซึ่งใช้เวลา O (m + n) โดยที่ m คือจำนวนขอบในกราฟและ n คือจำนวนโหนด สิ่งนี้เกี่ยวข้องกับจำนวนบิตอินพุตที่กำหนดอย่างไร? ถ้าเราสมมติว่าอินพุตถูกระบุเป็นรายการ adjacency (รายการของโหนดและขอบทั้งหมด) ดังที่กล่าวไว้ก่อนหน้านี้จำนวนบิตอินพุตจะเป็น x = Ω (m + n) ดังนั้นรันไทม์จะเป็น O (x) ดังนั้นอัลกอริทึมจึงทำงานในเวลาพหุนาม
อย่างไรก็ตามสิ่งต่าง ๆ พังทลายลงเมื่อเราเริ่มพูดถึงอัลกอริทึมที่ทำงานกับตัวเลข ลองพิจารณาปัญหาในการทดสอบว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ ด้วยตัวเลข n คุณสามารถทดสอบว่า n เป็นไพรม์หรือไม่โดยใช้อัลกอริทึมต่อไปนี้:
function isPrime(n):
for i from 2 to n - 1:
if (n mod i) = 0, return false
return true
แล้วความซับซ้อนของเวลาของรหัสนี้คืออะไร? วงในนั้นจะรัน O (n) ครั้งและแต่ละครั้งจะทำงานจำนวนหนึ่งเพื่อคำนวณ n mod i (ในฐานะขอบเขตบนที่อนุรักษ์นิยมจริงๆสิ่งนี้สามารถทำได้ในเวลา O (n 3 )) ดังนั้นอัลกอริทึมโดยรวมนี้จะทำงานในเวลา O (n 4 ) และอาจเร็วกว่ามาก
ในปี 2004 นักวิทยาศาสตร์คอมพิวเตอร์สามคนได้ตีพิมพ์บทความชื่อPRIMES is in P โดยให้อัลกอริธึมเวลาพหุนามสำหรับการทดสอบว่าตัวเลขเป็นจำนวนเฉพาะหรือไม่ ถือว่าเป็นผลลัพธ์ที่สำคัญ แล้วเรื่องใหญ่ล่ะ? เราไม่มีอัลกอริทึมเวลาพหุนามสำหรับสิ่งนี้หรือไม่?
น่าเสียดายที่เราไม่ จำไว้ว่าคำจำกัดความอย่างเป็นทางการของความซับซ้อนของเวลาพูดถึงความซับซ้อนของอัลกอริทึมซึ่งเป็นฟังก์ชันของจำนวนบิตของอินพุต อัลกอริทึมของเราทำงานในเวลา O (n 4 ) แต่นั่นคือฟังก์ชันของจำนวนบิตอินพุตคืออะไร? การเขียนจำนวน n ใช้ O (log n) บิต ดังนั้นถ้าเราให้ x เป็นจำนวนบิตที่ต้องใช้ในการเขียนอินพุต n รันไทม์ของอัลกอริทึมนี้คือ O (2 4x ) ซึ่งไม่ใช่พหุนามใน x
นี่คือหัวใจของความแตกต่างระหว่างเวลาพหุนามกับเวลาเทียม ในแง่หนึ่งอัลกอริทึมของเราคือ O (n 4 ) ซึ่งดูเหมือนพหุนาม แต่ในทางกลับกันภายใต้นิยามอย่างเป็นทางการของเวลาพหุนามไม่ใช่เวลาพหุนาม
หากต้องการทราบสัญชาตญาณว่าเหตุใดอัลกอริทึมจึงไม่ใช่อัลกอริทึมเวลาพหุนามให้คิดถึงสิ่งต่อไปนี้ สมมติว่าฉันต้องการให้อัลกอริทึมต้องทำงานมาก ๆ ถ้าฉันเขียนข้อมูลเช่นนี้:
10001010101011
จากนั้นจะใช้เวลาในกรณีที่เลวร้ายที่สุดพูดT
เพื่อให้เสร็จสมบูรณ์ ถ้าตอนนี้ฉันเพิ่มบิตเดียวต่อท้ายตัวเลขดังนี้:
100010101010111
รันไทม์ตอนนี้ (ในกรณีที่แย่ที่สุด) จะเป็น 2T ฉันสามารถเพิ่มจำนวนงานเป็นสองเท่าที่อัลกอริทึมทำได้เพียงแค่เพิ่มอีกหนึ่งบิต!
อัลกอริทึมทำงานในpseudopolynomial timeหากรันไทม์เป็นพหุนามบางค่าในค่าตัวเลขของอินพุตแทนที่จะเป็นจำนวนบิตที่ต้องการเพื่อแสดง อัลกอริธึมการทดสอบที่สำคัญของเราคืออัลกอริธึมเวลาเทียมเนื่องจากรันในเวลา O (n 4 ) แต่ไม่ใช่อัลกอริธึมเวลาพหุนามเนื่องจากเป็นฟังก์ชันของจำนวนบิต x ที่จำเป็นในการเขียนอินพุตรันไทม์จึงเป็น O (2 4x ) เหตุผลที่กระดาษ "PRIMES อยู่ใน P" มีความสำคัญมากก็คือรันไทม์ (โดยประมาณ) O (log 12 n) ซึ่งตามฟังก์ชันของจำนวนบิตคือ O (x 12 )
แล้วทำไมเรื่องนี้? เรามีอัลกอริธึมเวลาหลอกหลายตัวสำหรับการหาตัวประกอบจำนวนเต็ม อย่างไรก็ตามอัลกอริทึมเหล่านี้เป็นอัลกอริธึมเวลาเอ็กซ์โปเนนเชียลที่พูดในทางเทคนิค สิ่งนี้มีประโยชน์มากสำหรับการเข้ารหัส: หากคุณต้องการใช้การเข้ารหัส RSA คุณต้องวางใจได้ว่าเราไม่สามารถแยกตัวประกอบตัวเลขได้อย่างง่ายดาย ด้วยการเพิ่มจำนวนบิตในตัวเลขให้มีค่ามาก (เช่น 1024 บิต) คุณสามารถกำหนดระยะเวลาที่อัลกอริธึมการแยกตัวประกอบเวลาเทียมเทียมต้องใช้เวลามากจนจะไม่สามารถแยกตัวประกอบได้อย่างสมบูรณ์และไม่เป็นไปได้ ตัวเลข ในทางกลับกันถ้าเราสามารถหาอัลกอริธึมการแยกตัวประกอบพหุนาม - เวลาได้ก็ไม่จำเป็นต้องเป็นเช่นนั้น การเพิ่มบิตมากขึ้นอาจทำให้งานเพิ่มขึ้นมาก แต่การเติบโตจะเป็นการเติบโตแบบพหุนามเท่านั้นไม่ใช่การเติบโตแบบเอ็กซ์โปเนนเชียล
ที่กล่าวว่าในหลาย ๆ กรณีอัลกอริธึมเวลาเทียมเทียมนั้นดีอย่างสมบูรณ์แบบเนื่องจากขนาดของตัวเลขจะไม่ใหญ่เกินไป ตัวอย่างเช่นการเรียงลำดับการนับมีรันไทม์ O (n + U) โดยที่ U คือจำนวนที่มากที่สุดในอาร์เรย์ นี่คือเวลา pseudopolynomial (เนื่องจากค่าตัวเลขของ U ต้องการบิต O (log U) ในการเขียนดังนั้นรันไทม์จึงเป็นเลขชี้กำลังในขนาดอินพุต) ถ้าเรา จำกัด U เพื่อไม่ให้ U มีขนาดใหญ่เกินไป (เช่นถ้าเราปล่อยให้ U เป็น 2) รันไทม์คือ O (n) ซึ่งจริงๆแล้วคือเวลาพหุนาม นี่คือวิธีการทำงานของการเรียงลำดับ radix : โดยการประมวลผลตัวเลขทีละบิตรันไทม์ของแต่ละรอบคือ O (n) ดังนั้นรันไทม์โดยรวมจึงเป็น O (n log U) นี่คือความจริง เวลาพหุนามเนื่องจากการเขียนตัวเลข n เพื่อเรียงลำดับจะใช้Ω (n) บิตและค่าของ log U เป็นสัดส่วนโดยตรงกับจำนวนบิตที่ต้องใช้ในการเขียนค่าสูงสุดในอาร์เรย์
หวังว่านี่จะช่วยได้!
isPrime
ความซับซ้อนจึงประมาณเป็น O (n ^ 4) ไม่ใช่แค่ O (n) ฉันไม่เข้าใจ เว้นแต่ความซับซ้อนของn mod i
จะเป็น O (n ^ 3) .... ซึ่งไม่แน่นอน
n mod i
เป็นการอนุรักษ์นิยมมากเกินไป เวลาของmod
เป็นฟังก์ชันของจำนวนบิตในn
ไม่ใช่n
ตัวเองดังนั้นจึงควรเป็น O ((log n) ^ 3)
ความซับซ้อนของเวลาพหุนามหลอกหมายถึงพหุนามในค่า / ขนาดของอินพุต แต่เป็นเลขชี้กำลังในขนาดของอินพุต
ตามขนาดเราหมายถึงจำนวนบิตที่ต้องใช้ในการเขียนอินพุต
จากรหัสหลอกของกระเป๋าเป้เราสามารถหาความซับซ้อนของเวลาเป็น O (nW) ได้
// Input:
// Values (stored in array v)
// Weights (stored in array w)
// Number of distinct items (n) //
Knapsack capacity (W)
for w from 0 to W
do m[0, w] := 0
end for
for i from 1 to n do
for j from 0 to W do
if j >= w[i] then
m[i, j] := max(m[i-1, j], m[i-1, j-w[i]] + v[i])
else
m[i, j] := m[i-1, j]
end if
end for
end for
ในที่นี้ W ไม่ใช่พหุนามในความยาวของอินพุตซึ่งเป็นสิ่งที่ทำให้เป็นพหุนามหลอก
ให้ s เป็นจำนวนบิตที่ต้องการแทน W
i.e. size of input= s =log(W) (log= log base 2)
-> 2^(s)=2^(log(W))
-> 2^(s)=W (because 2^(log(x)) = x)
ตอนนี้running time of knapsack
= O (nW) = O (n * 2 ^ s) ซึ่งไม่ใช่พหุนาม