สัญกรณ์ Big-O ตั้งแต่พื้นฐาน
ทำความเข้าใจเหตุผลที่ต้องสนใจการเติบโตเชิงเส้นกำกับ วิธีตัดค่าคงที่และพจน์อันดับต่ำกว่า รวมถึงวิธีอ่าน Big-O ได้อย่างรวดเร็ว
สัญกรณ์ Big-O ตั้งแต่พื้นฐาน เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
เหตุใดจึงต้องวัดประสิทธิภาพของอัลกอริทึม
โปรแกรมสองโปรแกรมอาจให้ผลลัพธ์ถูกต้องเหมือนกัน แต่โปรแกรมหนึ่งทำงานเสร็จในพริบตา ขณะที่อีกโปรแกรมใช้เวลาหลายชั่วโมง ความซับซ้อนด้านเวลา อธิบายว่าเวลาทำงานเพิ่มขึ้นอย่างไรเมื่อข้อมูลนำเข้ามีขนาดใหญ่ขึ้น
# O(n) approach
def find_max_linear(nums):
m = nums[0]
for n in nums:
if n > m: m = n
return m
# O(n^2) approach (unnecessary double loop)
def find_max_quadratic(nums):
for i in range(len(nums)):
is_max = all(nums[i] >= nums[j] for j in range(len(nums)))
if is_max: return nums[i]
print(find_max_linear([3, 1, 4, 1, 5, 9])) # 9Big-O: ขอบเขตบนเชิงเส้นกำกับ
Big-O อธิบายขอบเขตบนในกรณีเลวร้ายที่สุดของการเติบโตของต้นทุนการทำงาน เคล็ดลับคือให้ตัดค่าคงที่และพจน์ที่มีขนาดเล็กกว่าออก เพราะเมื่อขนาดข้อมูลเพิ่มขึ้น มีเพียงพจน์เด่นเท่านั้นที่สำคัญ ดูตัวอย่างในโค้ด
# T(n) = 3n^2 + 5n + 100 is O(n^2)
# because the n^2 term dominates for large n
# T(n) = 2n + 1000 is O(n)
# the constant 1000 becomes negligible
# Rule: drop constants and lower-order terms
# 5n^3 + 2n^2 + n + 1 => O(n^3)
# 100 * log(n) + n => O(n)
print('O(n^2) example: counting iterations')
n = 1000
count = sum(1 for i in range(n) for j in range(n))
print(count) # 1_000_000 = n^2คลาสความซับซ้อนที่พบบ่อย
จากเร็วที่สุดไปช้าที่สุด: O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n), O(n!) การรู้จักความซับซ้อนเหล่านี้ช่วยให้คุณเลือกแนวทางที่เหมาะสมได้ก่อนเขียนโค้ดแม้แต่บรรทัดเดียว
import math
n = 1000
print(f'O(1): {1}')
print(f'O(log n): {int(math.log2(n))}')
print(f'O(n): {n}')
print(f'O(n log n): {int(n * math.log2(n))}')
print(f'O(n^2): {n**2}')
# O(2^n) for n=1000 is astronomically large
# O(n!) even largerการตัดค่าคงที่: เหตุใดจึงสำคัญ
การทำงาน 5n ขั้นตอนหรือ 2n ขั้นตอนต่างก็เป็น O(n) — ค่าคงที่ขึ้นอยู่กับฮาร์ดแวร์ ไม่ใช่อัลกอริทึม Big-O จึงตัดค่าคงที่เหล่านี้ออก เพื่อให้เปรียบเทียบการเพิ่มขนาดของงานได้อย่างเท่าเทียม
# Both are O(n) — different constants
def count_a(n):
total = 0
for i in range(n): # n ops
total += 1
for i in range(n): # n ops
total += 1
return total # T(n) = 2n => O(n)
def count_b(n):
total = 0
for i in range(5 * n): # 5n ops
total += 1
return total # T(n) = 5n => O(n)
print(count_a(10), count_b(10)) # 20 50กรณีดีที่สุด กรณีเฉลี่ย และกรณีเลวร้ายที่สุด
Big-O คือ กรณีเลวร้ายที่สุด; Omega คือกรณีดีที่สุด; Theta คือขอบเขตที่รัดกุมของทั้งสองกรณี เมื่อผู้สัมภาษณ์ถามถึง "ความซับซ้อน" พวกเขาแทบจะหมายถึงกรณีเลวร้ายที่สุดเสมอ
def linear_search(nums, target):
for i, n in enumerate(nums):
if n == target:
return i # best case: target at index 0 => O(1)
return -1 # worst case: not found => O(n)
# Best case O(1): target is first element
print(linear_search([5,1,2,3], 5)) # 0
# Worst case O(n): target not in list
print(linear_search([1,2,3,4], 9)) # -1O(log n): การลดพื้นที่ค้นหาลงครึ่งหนึ่ง
อัลกอริทึมจะเป็น O(log n) เมื่อมันลดข้อมูลเข้าลงครึ่งหนึ่งในแต่ละขั้น เช่น การค้นหาแบบทวิภาค แม้จะมีข้อมูลถึงหนึ่งพันล้านรายการ ก็ใช้เพียงประมาณ 30 ขั้นตอนเท่านั้น — เร็วอย่างเหลือเชื่อ ดูโค้ดได้เลย
def binary_search(arr, target):
lo, hi = 0, len(arr) - 1
steps = 0
while lo <= hi:
steps += 1
mid = (lo + hi) // 2
if arr[mid] == target:
return mid, steps
elif arr[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1, steps
import math
arr = list(range(1000))
idx, s = binary_search(arr, 999)
print(f'Found at {idx} in {s} steps (log2(1000)~={math.log2(1000):.1f})')O(n log n): ขอบเขตล่างของการเรียงลำดับ
การเรียงลำดับแบบเปรียบเทียบใด ๆ ต้องใช้เวลาอย่างน้อย O(n log n) ในกรณีเลวร้ายที่สุด — นี่คือขอบเขตล่างทางคณิตศาสตร์ที่พิสูจน์ได้จริง ดังนั้นการเรียงลำดับก่อนแล้วค่อยสแกนจึงมีความซับซ้อนรวมเป็น O(n log n) ไม่ใช่ O(n^2) โค้ดนี้แสดงการเรียงลำดับแบบผสาน
# Merge sort: O(n log n)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(a, b):
res, i, j = [], 0, 0
while i < len(a) and j < len(b):
if a[i] <= b[j]: res.append(a[i]); i+=1
else: res.append(b[j]); j+=1
return res + a[i:] + b[j:]
print(merge_sort([5,2,8,1,9,3])) # [1,2,3,5,8,9]ความซับซ้อนแบบเฉลี่ยสะสม
การวิเคราะห์แบบ เฉลี่ยสะสมจะหาค่าใช้จ่ายเฉลี่ยจากการทำงานหลายครั้งรวมกัน append ของ Python มีความซับซ้อนแบบเฉลี่ยสะสมเป็น O(1): โดยปกติจะทำงานทันที แต่บางครั้งจะต้องปรับขนาดเป็น O(n) ซึ่งเมื่อนำไปเฉลี่ยกับการทำ append ทั้งหมดแล้ว ผลกระทบจะน้อยมาก
# Dynamic array append is O(1) amortised
import sys
lst = []
capacities = []
for i in range(16):
lst.append(i)
capacities.append(sys.getsizeof(lst))
# Size jumps show reallocation events
for i, c in enumerate(capacities):
if i > 0 and capacities[i] != capacities[i-1]:
print(f'Realloc at i={i}, new size={c} bytes')การมองความซับซ้อนจากโค้ด
กฎง่าย ๆ คือให้นับลูป ลูปหนึ่งชั้นเป็น O(n) ลูปซ้อนกันสองชั้นเป็น O(n^2) และลูปที่ลดข้อมูลลงครึ่งหนึ่งเป็น O(log n) การทำงานที่แยกจากกันให้ บวกกัน ส่วนลูปที่ซ้อนกันเท่านั้นที่ให้ คูณกัน ดูโค้ดได้เลย
# Two independent passes: O(n) + O(n) = O(n)
def two_passes(nums):
total = sum(nums) # O(n)
mean = total / len(nums)
diffs = [abs(n - mean) for n in nums] # O(n)
return max(diffs) # O(n)
# Overall: O(n) -- NOT O(n^2)
# Nested loops: O(n) * O(n) = O(n^2)
def all_pairs(nums):
pairs = []
for i in range(len(nums)): # O(n)
for j in range(i+1, len(nums)): # O(n)
pairs.append((nums[i], nums[j]))
return pairs # O(n^2)พื้นฐานความซับซ้อนด้านพื้นที่
ความซับซ้อนด้านพื้นที่ติดตามหน่วยความจำเพิ่มเติมที่คุณใช้ นอกเหนือจากข้อมูลเข้า การกลับลำดับแบบทำในข้อมูลเดิมมีความซับซ้อนเป็น O(1) ส่วนตารางแฮชมีความซับซ้อนเป็น O(n) เมื่อแลกเวลาเพื่อประหยัดพื้นที่หรือแลกพื้นที่เพื่อประหยัดเวลา ต้องระบุทั้งสองค่าเสมอ
# O(1) space: reverse in-place
def reverse_inplace(arr):
l, r = 0, len(arr) - 1
while l < r:
arr[l], arr[r] = arr[r], arr[l]
l += 1; r -= 1
# O(n) space: create reversed copy
def reverse_copy(arr):
return arr[::-1]
a = [1, 2, 3, 4, 5]
reverse_inplace(a)
print(a) # [5, 4, 3, 2, 1]การพูดถึงความซับซ้อนในการสัมภาษณ์
เสนอ ความซับซ้อนออกมาเองเสมอโดยไม่ต้องรอให้ถูกถาม: "วิธีนี้ใช้เวลา O(n log n) และใช้พื้นที่ O(n)" จากนั้นจึงเสนอทางเลือกที่เร็วกว่า นิสัยนี้แสดงให้เห็นถึงความเป็นมืออาชีพในระดับสูงอย่างแท้จริง
# Example of explaining complexity step by step
def two_sum(nums, target):
# O(n) time: one pass through nums
# O(n) space: hash map stores up to n elements
seen = {} # value -> index
for i, n in enumerate(nums):
complement = target - n
if complement in seen: # O(1) lookup
return [seen[complement], i]
seen[n] = i
return []
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]ตรวจสอบความเข้าใจอย่างรวดเร็ว
ตรวจสอบความเข้าใจอย่างรวดเร็ว — แสดงสิ่งที่คุณซึมซับเกี่ยวกับ Big-O และคลาสความซับซ้อน หนึ่งคำถามเท่านั้น คุณทำได้แน่นอน 🎯
ทบทวนบทเรียน
ทบทวน: Big-O แสดงการเติบโตในกรณีเลวร้ายที่สุดโดยตัดค่าคงที่ออก คุณรู้จักคลาสตั้งแต่ O(1) ถึง O(n!) แล้ว และรู้ว่าลูปที่แยกจากกันต้องบวกกัน ส่วนลูปที่ซ้อนกันต้องคูณกัน
คำถามที่พบบ่อย
บทเรียน “สัญกรณ์ Big-O ตั้งแต่พื้นฐาน” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “สัญกรณ์ Big-O ตั้งแต่พื้นฐาน” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “สัญกรณ์ Big-O ตั้งแต่พื้นฐาน”
ทำความเข้าใจเหตุผลที่ต้องสนใจการเติบโตเชิงเส้นกำกับ วิธีตัดค่าคงที่และพจน์อันดับต่ำกว่า รวมถึงวิธีอ่าน Big-O ได้อย่างรวดเร็ว คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน
บทเรียน “สัญกรณ์ Big-O ตั้งแต่พื้นฐาน” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม
ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- สัญกรณ์ Big-O ตั้งแต่พื้นฐาน
- วิเคราะห์ลูปและลูปซ้อน
- การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ
- ความซับซ้อนด้านพื้นที่และการแลกเปลี่ยน