JavaScript算法通关手册

算法通过手册:05 数组十大经典排序算法

2021-12-20  本文已影响0人  ITCharge
05 数组十大经典排序算法.png

1. 冒泡排序算法

1.1 算法思想

冒泡排序(Bubble Sort) 基本思想:

i (i = 1, 2, …) 趟排序时从序列中前 n - i + 1 个元素的第 1 个元素开始,相邻两个元素进行比较,若前者大于后者,两者交换位置,否则不交换。

1.2 算法步骤

  1. 先将序列中第 1 个元素与第 2 个元素进行比较,若前者大于后者,则两者交换位置,否则不交换;
  2. 然后将第 2 个元素与第 3 个元素比较,若前者大于后者,则两者交换位置,否则不交换;
  3. 依次类推,直到第 n - 1 个元素与第 n 个元素比较(或交换)为止。经过如此一趟排序,使得 n 个元素中值最大元素被安置在序列的第 n 个位置上。
  4. 此后,再对前 n - 1 个元素进行同样过程,使得该 n - 1 个元素中值最大元素被安置在第 n - 1 个位置上。
  5. 然后再对前 n - 2 个元素重复上述过程,直到某一趟排序过程中不出现元素交换位置的动作,排序结束。

1.3 动画演示

img

1.4 算法分析

1.5 代码实现

class Solution:
    def bubbleSort(self, arr):
        for i in range(len(arr)):
            for j in range(len(arr) - i - 1):
                # 如果前者大于后者,则两者交换位置
                if arr[j] > arr[j + 1]:
                    arr[j], arr[j + 1] = arr[j + 1], arr[j]

        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.bubbleSort(nums)

2. 选择排序算法

2.1 算法思想

选择排序(Selection Sort) 基本思想:

i 趟排序从序列的后 n − i + 1 (i = 1, 2, …, n − 1) 个元素中选择一个值最小的元素与该 n - i + 1 个元素的最前面那个元素交换位置,即与整个序列的第 i 个位置上的元素交换位置。如此下去,直到 i == n − 1,排序结束。

可以简述为:每一趟排序中,从剩余未排序元素中选择一个最小的元素,与未排好序的元素最前面的那个元素交换位置。

2.2 算法步骤

  1. 在算法中设置整型变量 i,既可以作为排序趟数的计算,同时也作为执行第 i 趟排序时,参加排序的后 n − i + 1 个元素的第 1 个元素的位置。
  2. 整型变量 min_i 记录这 n − i + 1 个元素中值最小元素的位置。
  3. 每一趟排序开始,先另 min_i = i (即暂时假设序列的第 i 个元素为值最小者,以后经过比较后视实际情况再正式确定最小值元素的位置)。
  4. i 趟排序比较结束时,这 n − i + 1 个元素中真正的值最小元素为下标 min_i 对应的元素。此时,若有 min_i == i,说明值最小元素就是这 n − i + 1 个元素的第 1 个元素,意味着此趟排序不必进行元素交换。

2.3 动画演示

image

2.4 算法分析

对于具有 n 个元素的序列采用选择排序方法要经过 n - 1 趟排序。

2.5 代码实现

class Solution:
    def selectionSort(self, arr):
        for i in range(len(arr) - 1):
            # 记录未排序序列中最小数的索引
            min_i = i
            for j in range(i + 1, len(arr)):
                if arr[j] < arr[min_i]:
                    min_i = j
            # 如果找到最小数,将 i 位置上元素与最小数位置上元素进行交换
            if i != min_i:
                arr[i], arr[min_i] = arr[min_i], arr[i]
        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.selectionSort(nums)

3. 插入排序算法

3.1 算法思想

插入排序(Insertion Sort) 基本思想:

将整个序列切分为两部分:前 i - 1 个元素是有序序列,后 n - i + 1 个元素是无序序列。每一次排序,将无序序列的首元素,在有序序列中找到相应的位置并插入。

可以简述为:每一趟排序中,将剩余无序序列的第一个元素,插入到有序序列的适当位置上。

3.2 算法步骤

  1. 将第 1 个元素作为一个有序序列,将第 2 ~ n - 1 个元素作为无序序列。
  2. 从无序序列中取出第一个元素,在已经排好序的有序序列中从后向前扫描。
  3. 如果扫描到的元素大于取出元素,则将扫描到的元素向右移动 1 位,然后继续向前移动,直到找到有序序列中小于或者等于取出元素的适当位置。
  4. 将取出元素插入到适当位置上。
  5. 重复 2 ~ 4 步,直到元素全部变为有序序列。

3.3 动画演示

img

3.4 算法分析

3.5 代码实现

class Solution:
    def insertionSort(self, arr):
        for i in range(1, len(arr)):
            # temp 为无序序列中取出的第 1 个元素
            temp = arr[i]
            j = i
            # 将扫描到的元素向右移动,并找到合适的插入位置
            while j > 0 and arr[j - 1] > temp:
                arr[j] = arr[j - 1]
                j -= 1
            # 将取出元素插入到合适位置
            arr[j] = temp

        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.insertionSort(nums)

4. 希尔排序算法

4.1 算法思想

希尔排序(Shell Sort) 基本思想:

将整个序列切按照一定的间隔取值划分为若干个子序列,每个子序列分别进行插入排序。然后逐渐缩小间隔进行下一轮划分子序列和插入排序。直至最后一轮排序间隔为 1,对整个序列进行插入排序。

4.2 算法步骤

  1. 首先确定一个元素间隔数 gap,然后将参加排序的序列按此间隔数从第 1 个元素开始一次分成若干个子序列,即分别将所有位置相隔为 gap 的元素视为一个子序列,在各个子序列中采用某种排序方法进行插入排序。
  2. 然后减少间隔数,并重新将整个序列按新的间隔数分成若干个子序列,再分别对各个子序列进行排序,如此下去,直到间隔数 gap = 1

4.3 图解演示

image

4.4 算法分析

4.5 代码实现

class Solution:
    def shellSort(self, arr):
        size = len(arr)
        gap = size // 2

        while gap > 0:
            # 分别对各个子序列进行排序
            for i in range(gap, size):
                temp = arr[i]
                j = i
                while j >= gap and arr[j - gap] > temp:
                    arr[j] = arr[j - gap]
                    j -= gap
                arr[j] = temp
            # 减少间隔数
            gap = gap // 2
        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.shellSort(nums)

5. 归并排序算法

5.1 算法思想

归并排序(Merge Sort) 基本思想:

采用经典的分治策略,先递归地将当前序列平均分成两半。然后将有序序列两两合并,最终合并成一个有序序列。

5.2 算法步骤

  1. 初始时,将待排序序列中的 n 个记录看成 n 个有序子序列(每个子序列总是有序的),每个子序列的长度均为 1
  2. 把当前序列组中有序子序列两两归并,完成一遍之后序列组里的排序序列个数减半,每个子序列的长度加倍。
  3. 对长度加倍的有序子序列重复上面的操作,最终得到一个长度为 n 的有序序列。

5.3 动画演示

image

5.4 算法分析

5.5 代码实现

class Solution:
    # 合并排序
    def merge(self, left_arr, right_arr):
        arr = []
        while left_arr and right_arr:
            if left_arr[0] <= right_arr[0]:
                arr.append(left_arr.pop(0))
            else:
                arr.append(right_arr.pop(0))
        while left_arr:
            arr.append(left_arr.pop(0))
        while right_arr:
            arr.append(right_arr.pop(0))
        return arr
       
    def mergeSort(self, arr):
        # 切分
        size = len(arr)
        if size < 2:
            return arr
        mid = len(arr) // 2
        left_arr, right_arr = arr[0: mid], arr[mid:]
        # 递归切分并合并
        return self.merge(self.mergeSort(left_arr), self.mergeSort(right_arr))

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.mergeSort(nums)

6. 快速排序算法

6.1 算法思想

快速排序(Quick Sort) 基本思想:

通过一趟排序将无序序列分为独立的两个序列,第一个序列的值均比第二个序列的值小。然后递归地排列两个子序列,以达到整个序列有序。

6.2 算法步骤

  1. 从数组中找到一个基准数。
  2. 然后将数组中比基准数大的元素移动到基准数右侧,比他小的元素移动到基准数左侧,从而把数组拆分为左右两个部分。
  3. 再对左右两个部分分别重复第二步,直到各个部分只有一个数,则排序结束。

6.3 动画演示

image

6.4 算法分析

6.5 代码实现

import random


class Solution:
    # 随机选定基准数,并根据基准数将元素移动到正确位置上
    def randomPartition(self, arr: [int], low: int, high: int):
        i = random.randint(low, high)
        arr[i], arr[high] = arr[high], arr[i]
        return self.partition(arr, low, high)

    # 取高位为基准数,并根据基准数将元素移动到正确位置上
    def partition(self, arr: [int], low: int, high: int):
        i = low - 1
        pivot = arr[high]

        for j in range(low, high):
            if arr[j] <= pivot:
                i += 1
                arr[i], arr[j] = arr[j], arr[i]
        arr[i + 1], arr[high] = arr[high], arr[i + 1]
        return i + 1
    
    # 递归进行快速排序
    def quickSort(self, arr, low, high):
        if low < high:
            pi = self.randomPartition(arr, low, high)
            self.quickSort(arr, low, pi - 1)
            self.quickSort(arr, pi + 1, high)

        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.quickSort(nums, 0, len(nums) - 1)

7. 堆排序算法

7.1 算法思想

堆排序(Heap sort) 基本思想:

借用「堆结构」所设计的排序算法。将数组转化为大顶堆,重复从大顶堆中取出数值最大的节点,并让剩余的堆维持大顶堆性质。

7.2 堆的定义

堆:符合以下两个条件之一的完全二叉树:

7.3 算法步骤

  1. 首先将无序序列构造成第 1 个大顶堆(初始堆),使得 n 个元素的最大值处于序列的第 1 个位置。
  2. 然后交换序列的第 1 个元素(最大值元素)与最后一个元素的位置。
  3. 此后,再把序列的前 n - 1 个元素组成的子序列调整成一个新的大顶堆,这样又得到第 2 个最大值元素,把子序列的第 1 个元素(最大值元素)与第 n - 1 个元素交换位置。
  4. 此后再把序列的前 n - 2 个元素调整成一个新的大顶堆,……,如此下去,直到整个序列变换成一个有序序列。

可见堆排序算法主要涉及「初始堆建立方法」和「堆调整方法」。

7.3.1 堆调整方法

堆调整方法就是:把移走了最大值元素以后的剩余元素组成的序列再构造为一个新的堆积。具体步骤如下:

7.3.2 初始堆建立方法

7.4 动画演示

7.4.1 堆调整方法演示

image

7.4.2 初始堆建立方法演示

image

7.4.3 堆排序方法演示

image

7.5 算法分析

7.6 代码实现

class Solution:
    # 调整为大顶堆
    def heapify(self, arr: [int], index: int, end: int):
        left = index * 2 + 1
        right = left + 1
        while left <= end:
            # 当前节点为非叶子结点
            max_index = index
            if arr[left] > arr[max_index]:
                max_index = left
            if right <= end and arr[right] > arr[max_index]:
                max_index = right
            if index == max_index:
                # 如果不用交换,则说明已经交换结束
                break
            arr[index], arr[max_index] = arr[max_index], arr[index]
            # 继续调整子树
            index = max_index
            left = index * 2 + 1
            right = left + 1

    # 初始化大顶堆
    def buildMaxHeap(self, arr: [int]):
        size = len(arr)
        # (size-2) // 2 是最后一个非叶节点,叶节点不用调整
        for i in range((size - 2) // 2, -1, -1):
            self.heapify(arr, i, size - 1)
        return arr

    # 升序堆排序,思路如下:
    # 1. 先建立大顶堆
    # 2. 让堆顶最大元素与最后一个交换,然后调整第一个元素到倒数第二个元素,这一步获取最大值
    # 3. 再交换堆顶元素与倒数第二个元素,然后调整第一个元素到倒数第三个元素,这一步获取第二大值
    # 4. 以此类推,直到最后一个元素交换之后完毕。
    def maxHeapSort(self, arr: [int]):
        self.buildMaxHeap(arr)
        size = len(arr)
        for i in range(size):
            arr[0], arr[size - i - 1] = arr[size - i - 1], arr[0]
            self.heapify(arr, 0, size - i - 2)
        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.maxHeapSort(nums)

8. 计数排序算法

8.1 算法思想

计数排序(Counting Sort) 基本思想:

使用一个额外的数组 counts,其中第 i 个元素 counts[i] 是待排序数组 arr 中值等于 i 的元素个数。然后根据数组 counts 来将 arr 中的元素排到正确的位置。

8.2 算法步骤

  1. 找出待排序数组中最大值元素和最小值元素。
  2. 统计数组中每个值为 i 的元素出现的次数,存入数组的第 i 项。
  3. 对所有的计数累加(从 counts 中的第一个元素开始,每一项和前一项累加)。
  4. 反向填充目标数组:将每个元素 i 放在新数组的第 counts[i] 项,每放一个元素就要将 counts[i] -= 1

8.3 动画演示

image

8.4 算法分析

8.5 代码实现

class Solution:
    def countingSort(self, arr):
        # 待排序数组中最大值元素和最小值元素
        arr_min, arr_max = min(arr), max(arr)

        size = arr_max - arr_min + 1
        counts = [0 for _ in range(size)]
        # 统计数组中每个值为 `num` 的元素出现的次数,存入数组的第 `num - arr_min` 项
        for num in arr:
            counts[num - arr_min] += 1
        # 所有的计数累加
        for j in range(1, size):
            counts[j] += counts[j - 1]
        
        # 反向填充目标数组
        res = [0 for _ in range(len(arr))]
        for i in range(len(arr) - 1, -1, -1):
            res[counts[arr[i] - arr_min] - 1] = arr[i]
            counts[arr[i] - arr_min] -= 1

        return res

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.countingSort(nums)

9. 桶排序算法

9.1 算法思想

桶排序(Bucket Sort) 基本思想:

将未排序的数组分到若干个「桶」中,每个桶的元素再进行单独排序。

9.2 算法步骤

  1. 将区间划分为 n 个相同大小的子区间,每个区间称为一个桶。
  2. 遍历数组,将每个元素装入对应的桶中。
  3. 对每个桶内的元素单独排序(使用插入、归并、快排等算法)。
  4. 最后按照顺序将桶内的元素合并起来。

9.3 图解演示

9.3.1 划分子区间

image

9.3.2 将数组元素装入桶中,并对桶内元素单独排序

image

9.3.3 将桶内元素合并起来,完成排序

image

9.4 算法分析

9.5 代码实现

class Solution:
    # 插入排序
    def insertionSort(self, arr):
        for i in range(1, len(arr)):
            temp = arr[i]
            j = i
            while j > 0 and arr[j - 1] > temp:
                arr[j] = arr[j - 1]
                j -= 1
            arr[j] = temp

        return arr

    def bucketSort(self, arr, bucket_size=5):
        # 划分子区间,并创建桶
        arr_min, arr_max = min(arr), max(arr)
        bucket_count = (arr_max - arr_min) // bucket_size + 1
        buckets = [[] for _ in range(bucket_count)]
        
        # 将每个元素装入对应的桶中
        for num in arr:
            buckets[(num - arr_min) // bucket_size].append(num)
        
        res = []
        # 对桶内元素使用插入排序算法,并将排序后结果存入目标数组
        for bucket in buckets:
            self.insertionSort(bucket)
            res.extend(bucket)

        return res

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.bucketSort(nums)

10. 基数排序算法

10.1 算法思想

基数排序(Radix Sort) 基本思想:

将整数按位数切割成不同的数字,然后按每个位数分别比较进行排序。

10.2 算法步骤

基数排序算法可以采用「最低位优先法(Least Significant Digit First)」或者「最高位优先法(Most Significant Digit first)」。最常用的是「最低位优先法」。

下面我们以最低位优先法为例,讲解一下算法步骤。

  1. 遍历数组元素,获取数组最大值元素,并取得位数。
  2. 以个位元素为索引,对数组元素排序。
  3. 合并数组。
  4. 之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,进行排序,并合并数组,最终完成排序。

10.3 动画演示

image

10.4 算法分析

10.5 代码实现

class Solution:
    def radixSort(self, arr):
        # 计算位数最长的位数
        size = len(str(max(arr)))
        
        # 从个位到高位遍历位数
        for i in range(size):
            buckets = [[] for _ in range(10)]
            for num in arr:
                buckets[num // (10 ** i) % 10].append(num)
            arr.clear()
            # 生成新的数组
            for bucket in buckets:
                for num in bucket:
                    arr.append(num)

        return arr

    def sortArray(self, nums: List[int]) -> List[int]:
        return self.radixSort(nums)
上一篇 下一篇

猜你喜欢

热点阅读