BLOG.CYBERCRATE.PL
Kreatywnie o technologii
Sortowanie z pythonem
Sortowanie bąbelkowe i jego wizualizacja
W drugim już artykule z serii o sortowaniu, chciałbym zaznajomić Cię z kolejnym rodzajem sortowania – sortowaniem bąbelkowym. Legenda którą kiedyś usłyszałem, głosi, że nazwa wywodzi się od bąbelków w złocistym gazowanym napoju, bowiem jak owa legenda głosi, bąbelki większe wypływają szybciej do góry, a w tym algorytmie wygląda to podobnie. Będziemy zamieniali elementy tak, że będzie wyglądało to jakby największe wypływały na powierzchnię.
Zainteresowanych tematem odsyłąm również do pierwszego artykułu z tej serii
Algorytm - sortowanie bąbelkowe
Spróbuję wytłumaczyć to w podobnie prosty sposób jak w poprzednim artykule. Wyobraźmy sobie, że na stole układamy, w linii, kartoniki z liczbami. Teraz, korzystając z dwóch rąk, rozpoczynamy sortowanie. Lewą rękę kładziesz na pierwszym kartoniku, a prawą na drugim. Podnosisz kartoniki i porównujesz czy, są we właściwej kolejności, jeśli nie – zamieniasz je miejscami i odkładasz, jeśli są w dobrej – po prostu odkładasz. Teraz przesuwasz się o jeden kartonik w prawo – lewa ręka wędruje na drugi kartonik, a prawa na trzeci i ponownie porównujemy. Robimy tak, aż nie dojdziemy prawą ręką do ostatniego kartonika. Teraz patrząc na tą linię kartoników, możemy zauważyć, że jeden jest już na pewno na swoim miejscu – ostatni. Możemy więc kontynuować naszą zabawę – zaczynamy od początku, lewa ręka na pierwszy, a prawa na drugi i porównujemy. Tylko tym razem zakończymy, nie gdy prawa ręka wyląduje na ostatnim, a na przedostatnim. Dlaczego? Odpowiedź jest dość prosta – ostatni jest już posortowany i “zapominamy o nim”. Gdy i tą rundę zakończymy, kolejna odbędzie się w bardzo podobny sposób. Kiedy skończymy? Proste! Ostatnia runda będzie wyglądała tak, że mamy dwa elementy – zamieniamy je, albo nie. Ostatniego nie ma co sortować, bo jak można zauważyć, jeden element jest zawsze posortowany.
No to teraz tylko szybki pomysł jak to zrobić, aby fajnie to w komputerze wyglądało – tak jak poprzednio do zobrazowania wartośc liczbowych, użyjemy słupków o różnych wysokościach. Poniżej również szybka animacja, tak aby zobaczyć jak to działa.
Kod w pythonie - sortowanie bąbelkowe
def sortowanie_babelkowe(tablica):
n = len(tablica)
for i in range(n): #ile elementów jest już posortowanych
for j in range(n-i-1):
print(tablica) # wizualizacja dla debugowania
itA = j # iterator A
itB = j+1 # iterator B
#print(f"Porównanie pary: tablica[{itA}] = {tablica[itA]}, tablica[{itB}] = {tablica[itB]}")
if tablica[itA]>tablica[itB]: # elementy nieprawidłowe - zamiana
tablica[itA],tablica[itB] = tablica[itB],tablica[itA]
print(tablica) # wizualizacja dla debugowania
print("Posortowano", i+1, "elementów")
print(tablica) # wizualizacja dla debugowania
Muszę się przyznać, że napisałem ten kod przed wymyśleniem analogii z rękoma, a więc chyba czas to troszeczkę przepisać, aby łatwiej było patrzeć na niego. Poniżej prezentuję owoc kilku minut pracy – wydaje mi się że dużo czytelniejszy dla początkujących i w 100% kompatybilny z opisem powyżej 😉
def sortowanie_babelkowe(tablica):
n = len(tablica)
posortowane = 0
lewa = 0
prawa = 0
while n-posortowane > 1:
lewa = 0
prawa = 1
ostatni = n-posortowane-1
# -1 powyżej jest dladego, że iteretory liczymy od zera
print(tablica) # wizualizacja dla debugowania
while prawa<=ostatni:
#print(f"Porównanie pary: tablica[{lewa}] = {tablica[lewa]}, tablica[{prawa}] = {tablica[prawa]}")
if tablica[lewa] > tablica[prawa]:
tablica[lewa], tablica[prawa] = tablica[prawa], tablica[lewa]
lewa+=1
prawa+=1
print(tablica) # wizualizacja dla debugowania
posortowane+=1
print("Posortowano", posortowane, "elementów")
print(tablica) # wizualizacja dla debugowania
Pod spodem, dorzucam jeszcze obrazek z tego jak ten kod działa. Tak na marginesie, widać że python nie umie polskiego 😀 na 4 próby, w 4 nie radzi sobie z odmianą “końcówków”
>>> sortowanie_babelkowe_2([5,4,3,2,1])
[5, 4, 3, 2, 1]
[4, 5, 3, 2, 1]
[4, 3, 5, 2, 1]
[4, 3, 2, 5, 1]
[4, 3, 2, 1, 5]
Posortowano 1 elementów
[4, 3, 2, 1, 5]
[3, 4, 2, 1, 5]
[3, 2, 4, 1, 5]
[3, 2, 1, 4, 5]
Posortowano 2 elementów
[3, 2, 1, 4, 5]
[2, 3, 1, 4, 5]
[2, 1, 3, 4, 5]
Posortowano 3 elementów
[2, 1, 3, 4, 5]
[1, 2, 3, 4, 5]
Posortowano 4 elementów
[1, 2, 3, 4, 5]
Trzeba jednak przyznać, że pierwszy kod jest troszkę krótszy, ale mimo to, dla lepszego zobrazowania jak wizualizacja algorytmu sortowania bąbelkowego jest realizowana w pygame, będę rzymał się drugiej implementacji. I tutaj warto też zauważyć, bardzo, ale to bardzo ważną rzecz – programu nie uczymy się na pamięć! Nawet tutaj, gdzie algorytm jest jasno określony, możemy napisać go na bardzo wiele sposobów, dlatego też nauczyć się możemy jedynie składni języka i jakichś sztuczek, ale płynność w posługiwaniu się nim, czyli pisaniu programów, musimy wypracować. I tutaj niestety jest jedna droga – pisać, pisać i jeszce raz pisać, popatrzeć na kod kogoś innego, nabrać inspiracji, pisać, pisać i jeszcze raz pisać.
Wizualizacja - pygame i sortowanie bąbelkowe
Elementy kodu wykorzystamy z poprzedniego projektu. O nich napiszę tylko kilka słów, dla ciekawskich, pozostawiam link do poprzedniego artykułu w którym te elementy zostały dokładniej opisane.
To zaczynamy standardowo od importów, takich jak w ostatnim programie, oraz funkcji która nas uratuje przed zawieszaniem się programu przy interakcji z okienkiem pygame.
import pygame
from pygame.locals import *
from random import randint
import time
def eventHandling():
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
return False
return True
Lubię sobie ułatwiać życie, więc dodam jeszcze enum, który określi mi aktualny stan naszej wizualizacji
class Stan(Enum):
Zwykly = 1
Zmiana = 2 # Zamieniamy miejscami
Ok = 3 # Kolejność jest ok
Kolejna rzecz, to funkcja determinująca kolor słupka, na podstawie pozycji “rąk”, oraz stanu wizualizacji
def kolor(i,lewa,prawa,stan: Stan):
kolorLewa = (255,165,0)
kolorPrawa = (255,165,0)
kolorZamiana = (255,0,0)
kolorOk = (0,255,0)
kolorZwykle = (0,0,0)
if stan == Stan.Zmiana and (i == lewa or i == prawa):
return kolorZamiana
if stan == Stan.Ok and (i == lewa or i == prawa):
return kolorOk
if i == lewa:
return kolorLewa
if i == prawa:
return kolorPrawa
return kolorZwykle
Kolejny element, to funkcja rysująca. Weźmiemy tą samą co w poprzednim projekcie i przystosujmy do nowego zadania
def rysuj(tabela,szer,wys,surface,lewa,prawa,stan):
eventHandling()
surface.fill((255, 255, 255))
maksimum = max(tabela)
wspolczynnik = wys//maksimum
szerokosc_slupka = szer//len(tabela)
for i in range(len(tabela)):
color=kolor(i,lewa,prawa,stan)
wartosc = tabela[i]
wysokosc_slupka = wartosc*wspolczynnik
margines_gorny = wys-wysokosc_slupka
margines_lewy = szerokosc_slupka*i
rectangle = pygame.Rect(margines_lewy,margines_gorny,szerokosc_slupka,wysokosc_slupka)
pygame.draw.rect(surface, color, rectangle)
pygame.display.flip()
Okazuje się, że zbyt wiele zmian nie było tutaj koniecznych. Kolejnym krokiem będzie połączenie funkcji sortującej z funkcją rysującą.
def sortowanie_babelkowe(tablica,szer,wys,surface, czekaj_ms):
n = len(tablica)
posortowane = 0
lewa = 0
prawa = 0
while n-posortowane > 1:
lewa = 0
prawa = 1
ostatni = n-posortowane-1
while prawa<=ostatni:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zwykly)
pygame.time.wait(czekaj_ms)
if tablica[lewa] > tablica[prawa]:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zmiana)
pygame.time.wait(czekaj_ms)
tablica[lewa], tablica[prawa] = tablica[prawa], tablica[lewa]
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Ok)
pygame.time.wait(czekaj_ms)
else:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Ok)
pygame.time.wait(czekaj_ms)
lewa+=1
prawa+=1
posortowane+=1
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zwykly)
Następnym elementem, będzie przygotowanie wrappera wszystkich procedur potrzebnych do uruchomienia naszego programu i obliczania czasu działania sortowania. Wrappera oraz jeszcze jedną funkcję – odpowiedzialną za wypisanie czasu działania, zapożyczymy również z poprzedniego projektu.
def wypiszCzas(surface, czas):
font = pygame.font.Font(pygame.font.get_default_font(), 15)
color=(255,0,0)
text_surface = font.render("Czas wykonania: "+str(round(czas,6)), True, color)
surface.blit(text_surface, dest=(10,10))
pygame.display.flip()
def run(tablica, szer, wys, czekaj_ms):
pygame.init()
surface = pygame.display.set_mode((szer,wys))
czas_start=time.time()
sortowanie_babelkowe(tablica, szer, wys, surface, czekaj_ms)
czas_stop=time.time()
czas=czas_stop-czas_start
wypiszCzas(surface,czas)
waiting = True
while waiting:
waiting=eventHandling()
return czas
Ostatni element, to tak jak poprzednio, funkcje dla leniwców – losowa tablica i opakowanie całości
def randomArray(rozmiar, minimalna, maksymalna):
wynik=[]
for i in range(rozmiar):
wynik.append(randint(minimalna,maksymalna))
return wynik
def sortujLosowaTablice(szer_okna, wys_okna, czekaj_ms, ilosc_elementow, minimalny, maksymalny):
return run(randomArray(ilosc_elementow, minimalny, maksymalny),szer_okna, wys_okna, czekaj_ms)
sortujLosowaTablice(400,400,60,40,0,100)
I tak właśnie prezentuje się nasz piękny, gotowy kod:
import pygame
from pygame.locals import *
from random import randint
import time
def eventHandling():
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
return False
return True
from enum import Enum
class Stan(Enum):
Zwykly = 1
Zmiana = 2
Ok = 3
def kolor(i,lewa,prawa,stan: Stan):
kolorLewa = (255,165,0)
kolorPrawa = (255,165,0)
kolorZamiana = (255,0,0)
kolorOk = (0,255,0)
kolorZwykle = (0,0,0)
if stan == Stan.Zmiana and (i == lewa or i == prawa):
return kolorZamiana
if stan == Stan.Ok and (i == lewa or i == prawa):
return kolorOk
if i == lewa:
return kolorLewa
if i == prawa:
return kolorPrawa
return kolorZwykle
def rysuj(tabela,szer,wys,surface,lewa,prawa,stan):
eventHandling()
surface.fill((255, 255, 255))
maksimum = max(tabela)
wspolczynnik = wys//maksimum
szerokosc_slupka = szer//len(tabela)
for i in range(len(tabela)):
color=kolor(i,lewa,prawa,stan)
wartosc = tabela[i]
wysokosc_slupka = wartosc*wspolczynnik
margines_gorny = wys-wysokosc_slupka
margines_lewy = szerokosc_slupka*i
rectangle = pygame.Rect(margines_lewy,margines_gorny,szerokosc_slupka,wysokosc_slupka)
pygame.draw.rect(surface, color, rectangle)
pygame.display.flip()
def sortowanie_babelkowe(tablica,szer,wys,surface, czekaj_ms):
n = len(tablica)
posortowane = 0
lewa = 0
prawa = 0
while n-posortowane > 1:
lewa = 0
prawa = 1
ostatni = n-posortowane-1
while prawa<=ostatni:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zwykly)
pygame.time.wait(czekaj_ms)
if tablica[lewa] > tablica[prawa]:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zmiana)
pygame.time.wait(czekaj_ms)
tablica[lewa], tablica[prawa] = tablica[prawa], tablica[lewa]
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Ok)
pygame.time.wait(czekaj_ms)
else:
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Ok)
pygame.time.wait(czekaj_ms)
lewa+=1
prawa+=1
posortowane+=1
rysuj(tablica,szer,wys,surface,lewa,prawa,Stan.Zwykly)
def wypiszCzas(surface, czas):
font = pygame.font.Font(pygame.font.get_default_font(), 15)
color=(255,0,0)
text_surface = font.render("Czas wykonania: "+str(round(czas,6)), True, color)
surface.blit(text_surface, dest=(10,10))
pygame.display.flip()
def run(tablica, szer, wys, czekaj_ms):
pygame.init()
surface = pygame.display.set_mode((szer,wys))
czas_start=time.time()
sortowanie_babelkowe(tablica, szer, wys, surface, czekaj_ms)
czas_stop=time.time()
czas=czas_stop-czas_start
wypiszCzas(surface,czas)
waiting = True
while waiting:
waiting=eventHandling()
return czas
def randomArray(rozmiar, minimalna, maksymalna):
wynik=[]
for i in range(rozmiar):
wynik.append(randint(minimalna,maksymalna))
return wynik
def sortujLosowaTablice(szer_okna, wys_okna, czekaj_ms, ilosc_elementow, minimalny, maksymalny):
return run(randomArray(ilosc_elementow, minimalny, maksymalny),szer_okna, wys_okna, czekaj_ms)
sortujLosowaTablice(400,400,90,40,0,100)
Autor: Jakub Skrzyński
Korekta językowa: Wojciech Łyk