Merge sort: dela, sortera, sammanfoga
Implementera merge sort rekursivt, följ divide-and-conquer-trädet och förklara varför algoritmen garanterar O(n log n) i alla fall.
Merge sort: dela, sortera, sammanfoga är en gratis lektion i Förberedelse inför kodningsintervjuer på CoddyKit. Detta är lektion 2 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Förberedelse inför kodningsintervjuer, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Intuition för divide and conquer
Mergesortering är en klassisk divide-and-conquer-algoritm: dela arrayen på mitten, sortera varje halva rekursivt och sammanfoga sedan de två sorterade halvorna till ett sorterat resultat. Insikten är att det tar O(n) att sammanfoga två sorterade arrayer — betydligt mindre än att sortera från grunden. Den här uppdelningen skapar ett rekursionsträd med log n nivåer, där varje nivå kräver O(n) arbete för sammanfogningen. Därmed uppnås den optimala gränsen O(n log n) för jämförelsebaserad sortering.
# High-level merge sort structure
def merge_sort(arr):
# Base case: 0 or 1 element already sorted
if len(arr) <= 1:
return arr
# Divide
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # sort left half
right = merge_sort(arr[mid:]) # sort right half
# Conquer (merge)
return merge(left, right)
print(merge_sort([38, 27, 43, 3, 9, 82, 10]))
# [3, 9, 10, 27, 38, 43, 82]Merge-steget förklarat
Sammanfoga två sorterade arrayer genom att hålla två pekare, en för varje halva. Jämför de första elementen, kopiera det mindre till utdatan och flytta motsvarande pekare framåt. När den ena halvan är slut kopierar ni resten av den andra halvan direkt. Detta tar O(n) tid och O(n) minne för utdatan. Merge-steget är mergesorteringens algoritmiska kärna — förstå det på djupet.
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]: # <= preserves stability
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# Append remaining elements
result.extend(left[i:])
result.extend(right[j:])
return result
print(merge([1,3,5,7], [2,4,6,8]))
# [1, 2, 3, 4, 5, 6, 7, 8]Fullständig implementation av merge sort
Att kombinera delning och sammanfogning: de rekursiva anropen halverar problemet tills enskilda element återstår (som är trivialt sorterade), varefter merge-anropen kombinerar dem igen. På varje nivå i rekursionsträdet sammanfogas totalt samma n element (fördelade över flera merge-operationer). Rekursionens djup är log₂(n), vilket ger den totala tidskomplexiteten O(n log n) och O(n) extra utrymme för arrayerna med merge-resultat, samt O(log n) djup i anropsstacken.
def merge_sort_full(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort_full(arr[:mid])
right = merge_sort_full(arr[mid:])
# Merge the two sorted halves
merged = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]: merged.append(left[i]); i += 1
else: merged.append(right[j]); j += 1
merged.extend(left[i:] + right[j:])
return merged
print(merge_sort_full([5,2,4,6,1,3,2,6]))
# [1, 2, 2, 3, 4, 5, 6, 6]Rekursionssträd för merge sort
Visualisera merge sorts rekursionsträd för n=8: Nivå 0 har en array med 8 element; nivå 1 har två arrayer med 4 element; nivå 2 har fyra arrayer med 2 element; nivå 3 har åtta enskilda element (basfall). När vi går upp igen sammanfogar nivå 3→2 totalt 8 element, nivå 2→1 totalt 8 element och nivå 1→0 totalt 8 element. Det blir 3 nivåer × 8 element = 24 operationer ≈ 8 × log₂(8) = 24. Detta bekräftar O(n log n).
# Trace the tree depth
level_work = []
def merge_sort_traced(arr, depth=0):
if depth >= len(level_work):
level_work.append(0)
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort_traced(arr[:mid], depth+1)
right = merge_sort_traced(arr[mid:], depth+1)
level_work[depth] += len(arr) # track merge work
merged = sorted(left + right) # simplified merge
return merged
merge_sort_traced(list(range(8, 0, -1)))
for d, work in enumerate(level_work):
print(f'Level {d}: {work} elements merged')Merge sort på plats
Den vanliga rekursiva merge sort-algoritmen allokerar O(n) extra utrymme för merge-resultatet. Det finns en in-place merge sort, men den är komplex och har stora konstanta faktorer – den efterfrågas sällan i intervjuer. Den vanliga följdfrågan i intervjuer är: 'Kan ni implementera merge sort med O(1) extra utrymme?' Det korrekta svaret är: 'I teorin ja, men praktiska implementationer offrar antingen O(n) utrymme eller tillför komplexitet; Pythons Timsort använder O(n) utrymme för merge-steget.'
# Bottom-up merge sort: iterative, avoids recursion stack
def merge_sort_bottomup(arr):
n = len(arr)
width = 1
while width < n:
for i in range(0, n, 2 * width):
left = arr[i:i+width]
right = arr[i+width:i+2*width]
# Merge and put back
merged = []
a, b = 0, 0
while a < len(left) and b < len(right):
if left[a] <= right[b]: merged.append(left[a]); a+=1
else: merged.append(right[b]); b+=1
merged += left[a:] + right[b:]
arr[i:i+len(merged)] = merged
width *= 2
return arr
print(merge_sort_bottomup([5,2,4,6,1,3]))
# [1, 2, 3, 4, 5, 6]Merge sort är stabil
Merge sort är stabil: lika element från den vänstra halvan kommer alltid före lika element från den högra halvan i det sammanfogade resultatet. Detta garanteras genom att använda <= (inte <) när det vänstra elementet väljs. Stabilitet är viktig vid sortering efter flera nycklar. Pythons inbyggda sorted() och list.sort() använder Timsort, som också är stabil och har O(n log n), vilket gör dem till det säkra valet för all produktionskod.
# Demonstrating stability: sort (value, original_index) pairs
items = [(3,'A'), (1,'B'), (3,'C'), (2,'D')]
# Sort by value only
result = merge_sort_full(items) # won't work directly
# Use Python's stable sort:
result = sorted(items, key=lambda x: x[0])
print(result)
# [(1,'B'),(2,'D'),(3,'A'),(3,'C')]
# 'A' comes before 'C' for value=3 (stable order)Sammanfoga k sorterade arrayer
Att sammanfoga k sorterade arrayer med totalt n element kan göras genom att upprepade gånger sammanfoga par (som i en turneringsstege), vilket tar O(n log k) tid. Varje sammanfogningsnivå bearbetar n element, och det finns log k nivåer. Alternativt kan ni använda en min-heap med storleken k: lägg in det minsta återstående elementet från varje array, ta bort minimumet och lägg sedan in nästa element från samma array. Heap-metoden har också komplexiteten O(n log k), men är mer minneseffektiv när k är mycket stort.
import heapq
def merge_k_sorted(arrays):
result = []
heap = []
# Push first element from each array with array index
for i, arr in enumerate(arrays):
if arr:
heapq.heappush(heap, (arr[0], i, 0))
while heap:
val, arr_i, elem_i = heapq.heappop(heap)
result.append(val)
if elem_i + 1 < len(arrays[arr_i]):
next_val = arrays[arr_i][elem_i + 1]
heapq.heappush(heap, (next_val, arr_i, elem_i+1))
return result
arrs = [[1,4,7],[2,5,8],[3,6,9]]
print(merge_k_sorted(arrs)) # [1,2,3,4,5,6,7,8,9]Räkna inversioner med merge sort
Att räkna inversioner (par där a[i] > a[j] och i < j) på O(n log n) görs med en modifierad merge sort. Under merge-steget bildar ett element från den högra delarrayen en inversion med varje återstående element i den vänstra delarrayen när det är mindre än det vänstra elementet. Lägg då till len(left) - i till antalet.
def count_inversions(arr):
if len(arr) <= 1:
return arr, 0
mid = len(arr) // 2
left, l_inv = count_inversions(arr[:mid])
right, r_inv = count_inversions(arr[mid:])
merged = []
inversions = l_inv + r_inv
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
merged.append(left[i]); i += 1
else:
merged.append(right[j]); j += 1
inversions += len(left) - i # all remaining left elements > right[j]
merged.extend(left[i:] + right[j:])
return merged, inversions
_, inv = count_inversions([3, 1, 2])
print(inv) # 2: (3,1) and (3,2)Merge sort jämfört med quick sort
Merge sort garanterar O(n log n) i alla fall, är stabil och är det bättre valet för länkade listor och extern sortering. Quick sort har O(n log n) i genomsnitt men O(n²) i värsta fall, arbetar på plats (O(log n) stackutrymme) och är ofta snabbare i praktiken tack vare cacheeffektivitet för arrayer. Pythons inbyggda sortering använder Timsort (en variant av merge sort) – alltid det rätta standardvalet.
# Head-to-head complexity comparison:
# Algorithm | Best | Avg | Worst | Space | Stable
# Bubble sort | O(n) | O(n^2) | O(n^2) | O(1) | Yes
# Insertion sort| O(n) | O(n^2) | O(n^2) | O(1) | Yes
# Merge sort | O(nlogn)| O(nlogn)| O(nlogn)| O(n) | Yes
# Quick sort | O(nlogn)| O(nlogn)| O(n^2) | O(logn)| No
# Heap sort | O(nlogn)| O(nlogn)| O(nlogn)| O(1) | No
print('Merge sort: stable, O(n log n) guaranteed, O(n) space')Extern sortering: merge sort i stor skala
Merge sort är algoritmen bakom extern sortering (sortering av data som är för stor för att få plats i RAM). Data läses i block, varje block sorteras i minnet och blocken sammanfogas från disk. Merge-steget läser ett element i taget från varje sorterad delsekvens och håller bara O(k) element i minnet åt gången (ett per delsekvens). Därför används merge sort i databaser, Hadoop MapReduce och klassiska bandsorteringsalgoritmer.
# Simulated external sort: sort in chunks then merge
def external_sort(data, chunk_size):
chunks = []
for i in range(0, len(data), chunk_size):
chunk = sorted(data[i:i+chunk_size]) # sort in-memory
chunks.append(chunk)
print(f'Created {len(chunks)} sorted chunks')
# Merge all chunks
import heapq
heap = [(c[0], i, 0) for i, c in enumerate(chunks) if c]
heapq.heapify(heap)
result = []
while heap:
val, ci, ei = heapq.heappop(heap)
result.append(val)
if ei + 1 < len(chunks[ci]):
heapq.heappush(heap, (chunks[ci][ei+1], ci, ei+1))
return result
print(external_sort(list(range(20,0,-1)), 5)[:10])Sammanfattning av merge sort och intervjutips
I intervjuer visar en ren implementation av merge sort att ni förstår rekursion, merge-steget och delning och härskande. Vanliga följdfrågor:
- Varför O(n log n) och inte O(n²)? (log n nivåer × n arbete per nivå)
- Är den stabil? (Ja, använd <= i merge-steget)
- Hur mycket utrymme krävs? (O(n) extra utrymme + O(log n) stack)
- Kan ni göra det iterativt? (Ja, med bottom-up merge sort)
- Hur skulle ni använda den för en länkad lista? (Enklare än för en array – ingen kostnad på O(n) för att skapa delarrayer; använd två pekare med olika hastighet för att hitta mittpunkten)
# One-shot merge sort for interview clarity:
def ms(a):
if len(a) <= 1: return a
m = len(a) // 2
l, r, res, i, j = ms(a[:m]), ms(a[m:]), [], 0, 0
while i < len(l) and j < len(r):
if l[i] <= r[j]: res.append(l[i]); i+=1
else: res.append(r[j]); j+=1
return res + l[i:] + r[j:]
print(ms([5,2,4,6,1,3])) # [1,2,3,4,5,6]Snabbtest
Testa er förståelse av begreppen Data Structures & Algorithms — Coding Interview Prep från den här lektionen.
Lektionssammanfattning
I den här lektionen lärde ni er att: merge sort delar arrayen vid mittpunkten, sorterar varje halva rekursivt och sammanfogar de två sorterade halvorna i O(n) – vilket ger en total körtid på O(n log n) över log n rekursionsnivåer, merge-steget använder <= för att välja det vänstra elementet vid lika värden, vilket garanterar stabilitet, och merge sort är den algoritm som bör väljas för länkade listor, extern sortering och när stabilitet krävs – medan quick sort föredras för arrayer i minnet när utrymmet är begränsat. Härnäst implementerar vi quick sort och utforskar strategier för pivotval.
Lär dig Förberedelse inför kodningsintervjuer med en AI-lärare – gratis
Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.
- Kurser
- 90
- Lektioner
- 360
Vanliga frågor
Är lektionen ”Merge sort: dela, sortera, sammanfoga” gratis?
Ja – hela texten till ”Merge sort: dela, sortera, sammanfoga” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Förberedelse inför kodningsintervjuer, kan Ni uppgradera till CoddyKit PRO. Kursen i Förberedelse inför kodningsintervjuer innehåller totalt 4 lektioner.
Vad lär jag mig i ”Merge sort: dela, sortera, sammanfoga”?
Implementera merge sort rekursivt, följ divide-and-conquer-trädet och förklara varför algoritmen garanterar O(n log n) i alla fall. Ni övar på Förberedelse inför kodningsintervjuer med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.
Behöver jag någon erfarenhet för att börja lära mig Förberedelse inför kodningsintervjuer?
Du behöver inga förkunskaper. Utbildningen i Förberedelse inför kodningsintervjuer på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 2 av 4.
Hur lång tid tar lektionen ”Merge sort: dela, sortera, sammanfoga”?
De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.
Kan jag skriva och köra kod i den här Förberedelse inför kodningsintervjuer-lektionen?
Ja. Varje Förberedelse inför kodningsintervjuer-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.
Alla lektioner i den här kursen
- Bubble sort och insertion sort
- Merge sort: dela, sortera, sammanfoga
- Quick sort och pivotval
- Icke-jämförande sorteringar och Pythons sort()