初级排序和高级排序的实现和特性

145 阅读6分钟

比较类排序

  • 交换排序
    • 冒泡排序
    • 快速排序
  • 插入排序
    • 简单插入排序
    • 希尔排序
  • 选择排序
    • 简单选择排序
    • 堆排序
  • 归并排序
    • 二路归并排序
    • 多路归并排序

选择排序

每次遍历数组 将最小的元素追加到数组的头部 平均复杂度是O(n^2)

插入排序

遍历原始列表 每次选中一个元素 加入到新的排序列表中

非比较类排序

  • 计数排序
  • 桶排序
  • 基数排序

冒泡排序

两层for循环 每次比较相邻的两个 只要 右边 小于左边 就交换位置

这样每次循环后 会将最大的值放到最右边

def solve(list_data):

    for loop_no in range(0, len(list_data)):
        for index in range(0, len(list_data) - 1):

            if index == len(list_data) - 1:
                break

            if list_data[index] > list_data[index+1]:
                list_data[index], list_data[index+1] = list_data[index+1], list_data[index]

    return list_data

list_data = [1, 33, 9, 2, 6, 4, 8, 2, 5, 8, 2, 1, 6, 77,0]
res = solve(list_data)
print(res)

快速排序

思路: 采用分治的思想, 将数组切割成2个子数组 然后每个子数组再拆分成2孙子数组, 然后继续拆分...

定义基准pivot 将小于pivot的放到左边 将大于pivot的放到右边。 这样左边的部分 一定比右边的部分小

然后依次对左边和右边的子数组进行快排即递归调用, 直到整体有序

java快速排序代码示例

// Java
// 调用方法 quickSort([xxx,xxx,xxx])

public static void quickSort(int[] array, int begin, int end) {
    if (end <= begin) return;
    // 找到标杆
    int pivot = partition(array, begin, end);
    // 左边进行排序
    quickSort(array, begin, pivot - 1);
     // 右边进行排序
    quickSort(array, pivot + 1, end);
}


static int partition(int[] a, int begin, int end) {
    // pivot: 标杆位置
    // counter: 小于pivot的元素的个数, [0:counter]必须是 小于pivot
    int pivot = end;
    int counter = begin;
    for (int i = begin; i < end; i++) {
    	// 如果当前元素小于标杆 则将当前元素移动至 [0:counter]的后面
        // 即将a[counter]a[i]进行对调
        if (a[i] < a[pivot]) {
            int temp = a[counter]; a[counter] = a[i]; a[i] = temp;
            counter++;
        }
    }
    // 最后将a[pivot] 挪动到[0:counter]的后边 这样就形成了 左侧元素小于a[pivot] 右侧元素大于 a[pivot]
    int temp = a[pivot]; a[pivot] = a[counter]; a[counter] = temp;
    
    // 返回; counter的下标就是a[pivot]所在的位置, 即新的标杆位置
    return counter;
}

python快速排序代码示例 不开辟新数组

def quick_sort(begin, end, nums):
    # 分治的模版

    # step1 中断条件
    if begin >= end:
        return nums

    # step2 当前层的处理逻辑
    # 找一个标杆
    # 将小于标杆的元素 放到左边; 将大于标杆的元素 放到右边
    pivot_index = partition(begin, end, nums)
    
    # step3 拆分子任务 继续下探
    # 左半部分排序
    quick_sort(begin, pivot_index-1, nums)
    # 右半部分排序
    quick_sort(pivot_index+1, end, nums)

    # 整个数据
    return nums


def partition(begin, end, nums):
    # 初始化标杆值 就找第一个值
    # 初始的标杆下标为begin
    pivot = nums[begin]

    # 标记比标杆值小的个数
    mark = begin

    for i in range(begin + 1, end + 1):
        # 当前值比标杆值小的话
        if nums[i] < pivot:
            mark += 1
            # 将当前值移动到pivot的左侧
            nums[mark], nums[i] = nums[i], nums[mark]

    # 最后将标杆值移动到 mark的右侧, 即形成了[0:mark]部分小于标杆值, [mark:end] 大于标杆值
    nums[begin], nums[mark] = nums[mark], nums[begin]
    return mark

新开辟数组的方案

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    
    pivot = arr[len(arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + middle + quick_sort(right)

list_data = [1,  33, 9, 2, 6, 4, 8, 2, 5, 8, 2, 1, 6, 77,0]
res = quick_sort(list_data)
print(res)

归并排序

  • 将数组一分为二
  • 将两个子序列进行归并排序
  • 将排序好的子序列 合并

归并排序是一种采用分治法(Divide and Conquer)的典型应用,它是一种稳定的排序方法。这种算法利用了分治的思想,将大问题拆解成为小问题,然后将这些小问题分解,再将分解后的问题进行解决。

归并排序的具体步骤如下:

  • 1 分解(Divide):首先将要排序的数组递归地分解为两个子序列,每个子序列包含原数组一半的元素。
  • 2 解决(Conquer):对每一个子序列分别使用归并排序进行排序。
  • 3 合并(Combine):其次,将已排序的两个子序列合并成一个有序的序列。

在合并过程中,由于两个子序列都是有序的,所以可以采用双指针的方式,

  • 一个指针指向左边子序列的起始元素,
  • 另一个指针指向右边子序列的起始元素,
  • 比较两个指针所指向的元素大小,将较小的元素放入新的数组中,然后移动对应的指针。
  • 重复此过程,直到所有元素都被放入新的数组中

归并排序代码示例

public static void mergeSort(int[] array, int left, int right) {
    if (right <= left) return;
    # 找到中间点
    int mid = (left + right) >> 1; // (left + right) / 2
	
    # 将两个子序列进行归并排序 
    # 左侧部分归并排序 左子数组变得有序
    mergeSort(array, left, mid);
    # 右侧部分归并排序 右子数组变得有序
    mergeSort(array, mid + 1, right);
    # 将两个有序的子数组进行合并
    merge(array, left, mid, right);
}

public static void merge(int[] arr, in intt left, mid, int right) {


	# left, mid 是有序的
        # mid, right 是有序的
        # 申请一段额外的内存空间 
        int[] temp = new int[right - left + 1]; // 中间数组
        # 定义两个下标 i, 表示左子数组的起始位置
        int i = left;
        # 定义两个下标 j, 表示右子数组的起始位置 
        int j = mid + 1;
        # 表示已经填入的元素个数
        int k = 0;

        while (i <= mid && j <= right) {
        
          	i 和 j的较小者 取出给temp, 较小者的指针++
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }

        while (i <= mid)   temp[k++] = arr[i++];
        while (j <= right) temp[k++] = arr[j++];

        for (int p = 0; p < temp.length; p++) {
            arr[left + p] = temp[p];
        }
        // 也可以用 System.arraycopy(a, start1, b, start2, length)
    }

python示例

def mergesort(nums, left, right):
    if right <= left:
        return
    mid = (left+right) >> 1
    mergesort(nums, left, mid)
    mergesort(nums, mid+1, right)
    merge(nums, left, mid, right)

def merge(nums, left, mid, right):
    temp = []
    i = left
    j = mid+1
    while i <= mid and j <= right:
        if nums[i] <= nums[j]:
            temp.append(nums[i])
            i +=1
        else:
            temp.append(nums[j])
            j +=1
    while i<=mid:
        temp.append(nums[i])
        i +=1
    while j<=right:
        temp.append(nums[j])
        j +=1
    nums[left:right+1] = temp
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(left, right):
    result = []
    i = j = 0

    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1

    result.extend(left[i:])
    result.extend(right[j:])

    return result

arr = [3, 2, 1, 5, 4]
sorted_arr = merge_sort(arr)
print(sorted_arr)