Python을 사용하여 10가지 기본 정렬 알고리즘 구현(애니메이션 포함)

정렬 알고리즘은 데이터 구조 및 알고리즘에서 가장 기본적인 알고리즘 중 하나입니다.

정렬 알고리즘은 내부 정렬과 외부 정렬로 나눌 수 있는데 내부 정렬은 데이터 레코드를 메모리에 정렬하는 것을 의미하고 외부 정렬은 정렬된 데이터가 너무 커서 한 번에 모든 정렬 레코드를 수용할 수 없으며 외부 저장소가 필요하다는 것을 의미합니다. 정렬 프로세스 중에 액세스할 수 있습니다. 일반적인 내부 정렬 알고리즘은 삽입 정렬, 힐 정렬, 선택 정렬, 버블 정렬, 병합 정렬, 퀵 정렬, 힙 정렬, 기수 정렬 등입니다. 사진으로 요약하자면:

시간 복잡도 정보

  1. Square-order (O(n2)) 정렬 간단한 정렬의 다양한 유형: 직접 삽입, 직접 선택 및 버블 정렬.

  2. 선형 로그 순서(O(nlog2n)) 정렬 빠른 정렬, 힙 정렬 및 병합 정렬;

  3. O(n1+§)) 순서, 여기서 §는 0과 1 사이의 상수입니다. 힐 정렬

  4. 버킷 및 박스 정렬 외에 선형 순서(O(n)) 정렬 기수 정렬.

안정성에 대해

  • 정렬 후 2개의 동일한 키 순서는 정렬 전 순서와 동일합니다.

  • 안정적인 정렬 알고리즘: 버블 정렬, 삽입 정렬, 병합 정렬 및 기수 정렬.

  • 불안정한 정렬 알고리즘: 선택 정렬, 빠른 정렬, 힐 정렬, 힙 정렬.

용어 사전

  • n: 데이터 크기

  • k: "버킷"의 수

  • In-place: 일정한 메모리를 점유하고 추가 메모리를 점유하지 않음

  • Out-place: 추가 메모리를 차지합니다.

1. 버블 정렬

버블 정렬(Bubble Sort)도 간단하고 직관적인 정렬 알고리즘입니다. 정렬할 배열을 반복적으로 살펴보고 한 번에 두 요소를 비교하고 순서가 잘못된 경우 교체합니다. 시퀀스를 방문하는 작업은 교환할 필요가 없을 때까지, 즉 시퀀스가 ​​정렬될 때까지 반복됩니다. 이 알고리즘의 이름은 더 작은 요소가 교환을 통해 시퀀스의 맨 위로 천천히 "부동"한다는 사실에서 비롯됩니다.

가장 간단한 정렬 알고리즘 중 하나인 버블 정렬은 Abandon이 단어장에 등장한 것과 같은 느낌을 주는데 매번 첫 페이지에 첫 페이지가 나오므로 가장 친숙하다. 버블 정렬에는 플래그를 설정하는 최적화 알고리즘도 있습니다. 시퀀스 순회 중에 요소가 교환되지 않으면 시퀀스가 ​​이미 순서대로 있음을 증명합니다. 그러나 이러한 개선은 성능 향상에 그다지 도움이 되지 않습니다.

(1) 알고리즘 단계

  1. 인접한 요소를 비교합니다. 첫 번째가 두 번째보다 크면 둘 다 교환하십시오.

  2. 시작 부분의 첫 번째 쌍에서 끝 부분의 마지막 쌍까지 각 인접 요소 쌍에 대해 동일한 작업을 수행합니다. 이 단계가 완료되면 마지막 요소가 가장 큰 숫자가 됩니다.

  3. 마지막 요소를 제외한 모든 요소에 대해 위의 단계를 반복합니다.

  4. 비교할 숫자 쌍이 없을 때까지 매번 더 적은 수의 요소에 대해 위의 단계를 계속 반복합니다.

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

2. 선택 정렬

선택 정렬은 간단하고 직관적인 정렬 알고리즘으로, 어떤 데이터가 들어가든 시간 복잡도는 O(n²)입니다. 따라서 사용할 때 데이터 크기는 작을수록 좋습니다. 유일한 장점은 추가 메모리 공간을 차지하지 않는다는 것입니다.

(1) 알고리즘 단계

  1. 먼저 정렬되지 않은 시퀀스에서 가장 작은(가장 큰) 요소를 찾아 정렬된 시퀀스의 시작 부분에 저장합니다.

  2. 그런 다음 나머지 정렬되지 않은 요소에서 가장 작은(가장 큰) 요소를 계속 찾은 다음 정렬된 시퀀스의 끝에 놓습니다.

  3. 모든 요소가 정렬될 때까지 두 번째 단계를 반복합니다.

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def selectionSort(arr):
    for i in range(len(arr) - 1):
        # 记录最小数的索引
        minIndex = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[minIndex]:
                minIndex = j
        # i 不是最小数时,将 i 和最小数进行交换
        if i != minIndex:
            arr[i], arr[minIndex] = arr[minIndex], arr[i]
    return arr

3. 삽입정렬

삽입정렬의 코드 구현이 버블정렬이나 선택정렬만큼 간단하고 투박하지는 않지만 그 원리는 포커를 해본 사람이라면 누구나 몇 초 안에 이해할 수 있어야 하기 때문에 가장 이해하기 쉬울 것입니다. 삽입정렬은 가장 간단하고 직관적인 정렬 알고리즘으로 정렬된 시퀀스를 구성하여 작동하며, 정렬되지 않은 데이터는 정렬된 시퀀스에서 뒤에서 앞으로 스캔하여 해당 위치를 찾아 삽입합니다.

삽입 정렬은 버블 정렬과 마찬가지로 split half insertion이라는 최적화 알고리즘도 있습니다.

(1) 알고리즘 단계

  1. 정렬할 첫 번째 시퀀스의 첫 번째 요소를 정렬된 시퀀스로 처리하고 두 번째 요소부터 마지막 ​​요소까지 정렬되지 않은 시퀀스로 처리합니다.

  2. 정렬되지 않은 시퀀스를 처음부터 끝까지 순차적으로 스캔하고 스캔된 각 요소를 정렬된 시퀀스의 적절한 위치에 삽입합니다. (삽입할 요소가 정렬된 순서의 요소와 같은 경우 삽입할 요소는 같은 요소 뒤에 삽입됩니다.)

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

4. 힐 정렬

감소-증분 정렬 알고리즘이라고도 하는 힐 정렬은 삽입 정렬의 보다 효율적이고 개선된 버전입니다. 그러나 Hill 정렬은 불안정한 정렬 알고리즘입니다.

힐 정렬은 삽입 정렬의 다음 두 가지 속성을 기반으로 개선된 방법입니다.

  • 삽입 정렬은 거의 정렬된 데이터에서 작동할 때 매우 효율적입니다. 즉, 선형 정렬의 효율성을 달성할 수 있습니다.

  • 그러나 삽입 정렬은 한 번에 한 비트씩만 데이터를 이동할 수 있기 때문에 삽입 정렬은 일반적으로 비효율적입니다.

힐 정렬의 기본 개념은 직접 삽입 정렬을 위해 먼저 정렬할 전체 레코드 시퀀스를 여러 개의 하위 시퀀스로 나누고 전체 시퀀스의 레코드가 "기본적으로 순서대로" 있을 때 모든 레코드에 대해 직접 삽입 정렬을 수행하는 것입니다. 차례로.

(1) 알고리즘 단계

  1. 증분 시퀀스 t1, t2, ..., tk를 선택합니다. 여기서 ti > tj, tk = 1입니다.

  2. 증분 시퀀스 번호 k에 따라 시퀀스를 k번 정렬합니다.

  3. 각 정렬에 대해 해당 증분 ti에 따라 정렬할 열을 길이 m인 여러 개의 하위 시퀀스로 나누고 각 하위 목록에 대해 직접 삽입 정렬을 각각 수행합니다. 증가 인자가 1인 경우에만 전체 시퀀스를 테이블로 취급하고 테이블의 길이는 전체 시퀀스의 길이입니다.

(2) 파이썬 코드

def shellSort(arr):
    import math
    gap=1
    while(gap < len(arr)/3):
        gap = gap*3+1
    while gap > 0:
        for i in range(gap,len(arr)):
            temp = arr[i]
            j = i-gap
            while j >=0 and arr[j] > temp:
                arr[j+gap]=arr[j]
                j-=gap
            arr[j+gap] = temp
        gap = math.floor(gap/3)
    return arr

5. 병합 정렬

병합 정렬(Merge sort)은 병합 연산을 기반으로 한 효과적인 정렬 알고리즘이다. 이 알고리즘은 Divide and Conquer의 매우 일반적인 응용 프로그램입니다.

분할 정복의 일반적인 알고리즘 적용으로 병합 정렬을 구현하는 두 가지 방법이 있습니다.

  • 하향식 재귀(모든 재귀 메서드는 반복으로 다시 작성할 수 있으므로 두 번째 메서드가 있음)

  • 상향식 반복;

선택 정렬과 마찬가지로 병합 정렬의 성능은 입력 데이터의 영향을 받지 않지만 항상 O(nlogn) 시간 복잡도이기 때문에 성능이 선택 정렬보다 훨씬 좋습니다. 비용은 추가 메모리 공간이 필요하다는 것입니다.

(1) 알고리즘 단계

  1. 크기가 병합된 시퀀스를 저장하는 데 사용되는 두 개의 정렬된 시퀀스의 합이 되도록 공간을 적용합니다.

  2. 두 개의 포인터를 설정하십시오. 초기 위치는 각각 두 개의 정렬된 시퀀스의 시작 위치입니다.

  3. 두 포인터가 가리키는 요소를 비교하고 상대적으로 작은 요소를 선택하여 병합 공간에 넣고 포인터를 다음 위치로 이동합니다.

  4. 포인터가 시퀀스 끝에 도달할 때까지 3단계를 반복합니다.

  5. 다른 시퀀스의 나머지 모든 요소를 ​​병합된 시퀀스의 끝에 직접 복사합니다.

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def mergeSort(arr):
    import math
    if(len(arr)<2):
        return arr
    middle = math.floor(len(arr)/2)
    left, right = arr[0:middle], arr[middle:]
    return merge(mergeSort(left), mergeSort(right))

def merge(left,right):
    result = []
    while left and right:
        if left[0] <= right[0]:
            result.append(left.pop(0));
        else:
            result.append(right.pop(0));
    while left:
        result.append(left.pop(0));
    while right:
        result.append(right.pop(0));
    return result

6. 퀵 정렬

Quicksort는 Tony Hall이 개발한 정렬 알고리즘입니다. 평균적으로 n 항목을 정렬하려면 O(nlogn) 비교가 필요합니다. 최악의 경우 Ο(n2) 비교가 필요하지만 일반적이지 않습니다. 실제로 퀵 정렬은 내부 루프가 대부분의 아키텍처에서 효율적으로 구현될 수 있기 때문에 일반적으로 다른 Ο(nlogn) 알고리즘보다 훨씬 빠릅니다.

Quicksort는 분할 및 정복 전략을 사용하여 목록을 두 개의 하위 목록으로 나눕니다.

퀵 정렬은 정렬 알고리즘에서 분할 정복이라는 아이디어를 적용한 또 다른 일반적인 응용 프로그램입니다. 본질적으로 퀵 정렬은 버블 정렬을 기반으로 한 재귀적인 분할 정복 방법으로 간주되어야 합니다.

퀵 정렬의 이름은 간단하고 무례합니다. 이름을 듣자마자 빠르고 효율적인 존재의 의미를 알기 때문입니다! 빅 데이터를 위한 가장 빠른 정렬 알고리즘 중 하나입니다. Worst Case의 시간복잡도가 O(n²)에 이르지만 매우 우수하며, 대부분의 경우 평균 시간복잡도가 O(nlogn)인 정렬 알고리즘보다 성능이 좋은데 이유는 저도 모르겠습니다. 다행히 강박장애는 다시 재발했고, N개 이상의 자료를 확인한 끝에 드디어 "알고리즘 예술과 정보학 경진대회"에서 만족스러운 답을 찾았습니다.

퀵 정렬의 최악의 실행 사례는 순차 배열의 퀵 정렬과 같은 O(n²)입니다. 그러나 상각 예상 시간은 O(nlogn)이고 O(nlogn) 표기법에 내포된 상수 계수는 작으며 복잡도가 O(nlogn)에서 안정적인 병합 정렬보다 훨씬 작습니다. 따라서 순서가 약한 대부분의 난수 시퀀스의 경우 빠른 정렬이 항상 병합 정렬보다 낫습니다.

(1) 알고리즘 단계

① 시퀀스에서 "pivot"(pivot)이라는 요소를 선택합니다.

② 순서를 재정렬하여 기준값보다 작은 요소는 모두 기준값 앞에 배치하고 기준값보다 큰 요소는 모두 기준값 뒤에 배치합니다(양쪽에 같은 번호가 올 수 있음). 이 파티션이 종료된 후 벤치마크는 시퀀스의 중간에 있습니다. 이를 분할 작업이라고 합니다.

③ 기준값보다 작은 요소의 하위 배열과 기준값보다 큰 요소의 하위 배열을 재귀적으로 정렬한다.

재귀의 아래쪽 사례는 시퀀스의 크기가 0 또는 1인 경우, 즉 항상 정렬된 경우입니다. 재귀적으로 진행되고 있지만 이 알고리즘은 각 반복(반복)에서 적어도 하나의 요소를 마지막 위치에 놓기 때문에 항상 종료됩니다.

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def quickSort(arr, left=None, right=None):
    left = 0 if not isinstance(left,(int, float)) else left
    right = len(arr)-1 if not isinstance(right,(int, float)) else right
    if left < right:
        partitionIndex = partition(arr, left, right)
        quickSort(arr, left, partitionIndex-1)
        quickSort(arr, partitionIndex+1, right)
    return arr

def partition(arr, left, right):
    pivot = left
    index = pivot+1
    i = index
    while  i <= right:
        if arr[i] < arr[pivot]:
            swap(arr, i, index)
            index+=1
        i+=1
    swap(arr,pivot,index-1)
    return index-1

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

7. 힙 정렬

힙소트(Heapsort)는 힙의 데이터 구조를 이용하여 설계된 정렬 알고리즘을 말한다. 스태킹은 완전한 이진 트리에 가까운 구조이면서 동시에 스태킹의 특성을 만족합니다. 즉, 자식 노드의 키 값 또는 인덱스는 항상 부모 노드보다 작거나 큽니다. 힙 정렬은 힙의 개념을 이용하여 정렬하는 선택 정렬이라고 할 수 있습니다. 두 가지 방법으로 나뉩니다.

  1. 큰 상단 힙: 각 노드의 값은 힙 정렬 알고리즘에서 오름차순으로 사용되는 자식 노드의 값보다 크거나 같습니다.

  2. 작은 상단 힙: 각 노드의 값은 하위 노드의 값보다 작거나 같습니다. 힙 정렬 알고리즘에서 내림차순으로 사용됩니다.

힙 정렬의 평균 시간 복잡도는 O(nlogn)입니다.

(1) 알고리즘 단계

  1. 힙 생성 H[0...n-1];

  2. 힙 헤드(최대값)와 힙 테일을 바꿉니다.

  3. 힙의 크기를 1로 줄이고 shift_down(0)을 호출합니다. 목적은 새 배열의 맨 위 데이터를 해당 위치로 조정하는 것입니다.

  4. 힙 크기가 1이 될 때까지 2단계를 반복합니다.

(2) 애니메이션 프레젠테이션

(3) 파이썬 코드

def buildMaxHeap(arr):
    import math
    for i in range(math.floor(len(arr)/2),-1,-1):
        heapify(arr,i)

def heapify(arr, i):
    left = 2*i+1
    right = 2*i+2
    largest = i
    if left < arrLen and arr[left] > arr[largest]:
        largest = left
    if right < arrLen and arr[right] > arr[largest]:
        largest = right

    if largest != i:
        swap(arr, i, largest)
        heapify(arr, largest)

def swap(arr, i, j):
    arr[i], arr[j] = arr[j], arr[i]

def heapSort(arr):
    global arrLen
    arrLen = len(arr)
    buildMaxHeap(arr)
    for i in range(len(arr)-1,0,-1):
        swap(arr,0,i)
        arrLen -=1
        heapify(arr, 0)
    return arr

8. 계산 및 분류

카운팅 정렬의 핵심은 입력 데이터 값을 키로 변환하여 추가 배열 공간에 저장하는 것입니다. 선형 시간 복잡도의 일종으로 카운팅 정렬은 입력 데이터가 특정 범위의 정수여야 합니다.

(1) 애니메이션 프레젠테이션

(2) 파이썬 코드

def countingSort(arr, maxValue):
    bucketLen = maxValue+1
    bucket = [0]*bucketLen
    sortedIndex =0
    arrLen = len(arr)
    for i in range(arrLen):
        if not bucket[arr[i]]:
            bucket[arr[i]]=0
        bucket[arr[i]]+=1
    for j in range(bucketLen):
        while bucket[j]>0:
            arr[sortedIndex] = j
            sortedIndex+=1
            bucket[j]-=1
    return arr

9. 버킷 정렬

버킷 정렬은 카운팅 정렬의 업그레이드 버전입니다. 함수의 매핑 관계를 활용하며, 이 매핑 함수를 결정하는 것이 고효율의 핵심입니다. 버킷 정렬을 효율적으로 만들려면 다음 두 가지 작업을 수행해야 합니다.

  1. 여분의 공간이 충분한 경우 버킷 수를 늘리십시오.

  2. 사용된 매핑 함수는 입력 N 데이터를 K 버킷에 균등하게 분배할 수 있습니다.

동시에 버킷의 요소를 정렬하는 경우 비교 정렬 알고리즘을 선택하는 것이 성능에 매우 중요합니다.

언제가 가장 빠를까

입력 데이터가 각 버킷에 고르게 분산될 수 있는 경우.

언제가 가장 느릴까

입력 데이터가 동일한 버킷에 할당된 경우.

파이썬 코드

def bucket_sort(s):
    """桶排序"""
    min_num = min(s)
    max_num = max(s)
    # 桶的大小
    bucket_range = (max_num-min_num) / len(s)
    # 桶数组
    count_list = [ [] for i in range(len(s) + 1)]
    # 向桶数组填数
    for i in s:
        count_list[int((i-min_num)//bucket_range)].append(i)
    s.clear()
    # 回填,这里桶内部排序直接调用了sorted
    for i in count_list:
        for j in sorted(i):
            s.append(j)

if __name__ == __main__ :
    a = [3.2,6,8,4,2,6,7,3]
    bucket_sort(a)
    print(a) # [2, 3, 3.2, 4, 6, 6, 7, 8]

10. 기수 정렬

기수 정렬은 비비교 정수 정렬 알고리즘으로 정수를 자릿수에 따라 다른 숫자로 자른 다음 각 자릿수를 따로 비교하는 것이 원리입니다. 정수는 특정 형식의 문자열(예: 이름 또는 날짜) 및 부동 소수점 숫자를 나타낼 수도 있으므로 기수 정렬은 정수로 제한되지 않습니다.

기수 정렬 vs 카운트 정렬 vs 버킷 정렬

기수 정렬에는 두 가지 방법이 있습니다.

이 세 가지 정렬 알고리즘은 모두 버킷 개념을 사용하지만 버킷 사용에는 분명한 차이점이 있습니다.

  • 카디널리티 정렬: 버킷은 키 값의 각 숫자에 따라 할당됩니다.

  • 카운팅 정렬: 각 버킷은 단일 키 값만 저장합니다.

  • 버킷 정렬: 각 버킷은 특정 범위의 값을 저장합니다.

애니메이션 프레젠테이션

파이썬 코드

def RadixSort(list):
    i = 0                                    #初始为个位排序
    n = 1                                     #最小的位数置为1(包含0)
    max_num = max(list) #得到带排序数组中最大数
    while max_num > 10**n: #得到最大数是几位数
        n += 1
    while i < n:
        bucket = {} #用字典构建桶
        for x in range(10):
            bucket.setdefault(x, []) #将每个桶置空
        for x in list: #对每一位进行排序
            radix =int((x / (10**i)) % 10) #得到每位的基数
            bucket[radix].append(x) #将对应的数组元素加入到相 #应位基数的桶中
        j = 0
        for k in range(10):
            if len(bucket[k]) != 0: #若桶不为空
                for y in bucket[k]: #将该桶中每个元素
                    list[j] = y #放回到数组中
                    j += 1
        i += 1
return  list

Supongo que te gusta

Origin blog.csdn.net/veratata/article/details/128612229
Recomendado
Clasificación