数据结构与算法-7排序算法思想一插入&希尔&归并

排序算法一插入&希尔&归并

1 排序算法简介

排序算法是将一组数据按照特定的顺序进行排列的算法。常见的排序算法包括快速排序冒泡排序希尔排序二分排序(归并)桶排序堆排序基数排序插入排序选择排序等。不同的排序算法具有不同的时间复杂度和空间复杂度,适用于不同的场景。

2 排序算法评价指标

  1. 时间效率

  2. 空间复杂度

  3. 比较次数和交换次数

在排序算法中,比较次数和交换次数是两个重要的操作指标。几乎所有的排序算法都会涉及到比较操作,以确定元素的相对大小。而某些排序算法(如冒泡排序、选择排序等)则需要进行交换操作,以改变元素的相对位置。

  1. 稳定性

稳定性是排序算法的一个重要特性,它指的是在排序过程中,具有相同值的元素在排序后的相对位置是否保持不变。如果排序算法是稳定的,那么具有相同值的元素在排序后的相对位置与排序前相同。反之,如果排序算法是不稳定的,那么具有相同值的元素在排序后的相对位置可能会发生变化。

示例

对于给定的输入序列 1 9 3 5 3,我们有两种可能的排序结果:

  • 第一种:1 3 3 5 9
  • 第二种:1 3 3 5 9

从这两种结果可以看出,它们都是正确的排序结果。但是,如果我们考虑稳定性,那么第二种排序结果都是稳定的,因为两个值为 3 的元素在排序后的相对位置没有发生变化。

说明:快速排序通常被认为是不稳定的,而冒泡排序和插入排序则是稳定的。因此,在选择排序算法时,除了考虑时间效率和空间复杂度外,还需要考虑算法的稳定性是否满足实际需求。

3 稳定性的应用

问题描述,订单需要首先按照金额从小到大排序,当金额相同时,需要按照下单时间进行排序。当订单从订单中心传输过来时已经按照时间排好序时,我们需要考虑如何在不破坏时间顺序的前提下,按照金额进行排序。

排序需求

  • 主要排序依据:订单金额(从小到大)
  • 次要排序依据:下单时间(当金额相同时)
  • 前提条件:订单已按时间排好序

排序算法选择

不稳定排序算法

  • 缺点:如果不选择稳定的排序算法,那么在按照金额排序时,可能会破坏已经按照时间排好的顺序。这意味着在排序过程中,我们可能需要同时比较金额和下单时间两个字段,增加了算法的复杂性和执行时间。

稳定排序算法

  • 优点:选择稳定的排序算法可以确保在按照金额排序时,不会破坏已经按照时间排好的顺序。这意味着我们只需要比较金额字段,当下金额相同时,由于订单已经按照时间排好序,所以它们的相对位置不会改变。
  • 推荐算法:如插入排序、归并排序、冒泡排序(虽然效率较低但稳定)或基于比较的稳定排序算法变体。

4 插入排序简介

在这里插入图片描述

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,直到整个序列有序。

示例说明

打扑克。分成两部分:一部分是你手里的牌(已经排好序),一部分是要拿的牌(无序)。把一个无序的数列一个个插入到有序数列中。

插入排序的步骤如下:

  1. 从第一个元素开始,该元素可以认为已经被排序。
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置。
  4. 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置。
  5. 将新元素插入到该位置后。
  6. 重复步骤 2~5。

插入排序分析

  1. 时间复杂度 :O(N^2)
  2. 空间复杂度 :O(n)
  3. 稳定性 :稳定

看以下这个例子:对7 8 9 0 4 3进行插入排序

  1. 初始状态:

    7 8 9 0 4 3
    
  2. 第一步:从第二个元素开始(索引为1),将其与前面的元素比较并插入到正确的位置。此时,7是已排序的序列,8是待插入的元素。

    7 8 9 0 4 3  (没有变化,因为8 > 7)
    
  3. 第二步:继续向后,将9与前面的元素比较并插入到正确的位置。此时,7 8是已排序的序列,9是待插入的元素。

    7 8 9 0 4 3  (没有变化,因为9 > 8)
    
  4. 第三步:继续向后,将0与前面的元素比较并插入到正确的位置。此时,7 8 9是已排序的序列,0是待插入的元素。

    0 7 8 9 4 3
    

    (注意,此时0被插入到了正确的位置,即序列的开头)

  5. 第四步:继续向后,将4与前面的元素比较并插入到正确的位置。此时,0 7 8 9是已排序的序列,4是待插入的元素。

    0 4 7 8 9 3
    

    (注意,此时4被插入到了0和7之间)

  6. 第五步:继续向后,将3与前面的元素比较并插入到正确的位置。此时,0 4 7 8 9是已排序的序列,3是待插入的元素。

    0 3 4 7 8 9
    

    (注意,此时3被插入到了0和4之间)

5 插入排序示例

/**
 * 插入排序
 * 1.从第一个元素开始,该元素可以认为已经被排序
 * 2.取出下一个元素,在已经排序的元素序列中从后向前扫描
 * 3.如果该元素(已排序)大于新元素,将该元素移到下一位置
 * 4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
 * 5.将新元素插入到该位置中
 * 6.重复步骤2~5
 */
public class InsertionSort {
    public static void main(String[] args)
    {
        InsertionSort insertionSort = new InsertionSort();
        int[] nums = {3,2,1,5,4,10};
        int[] res = insertionSort.insertionSort(nums);
        insertionSort.print(res);
        System.out.println();
        int[] res2 = insertionSort.insertionSort2(nums);
        insertionSort.print(res2);
    }

    /**
     * 插入排序:升序
     * @param nums
     * @return
     */
    public int[] insertionSort(int[] nums)
    {
        for (int i = 1; i < nums.length; i++) {
            int temp = nums[i];
            int j = i - 1;
            for ( ; j >= 0 ; j--) {
                if (temp < nums[j]){
                    nums[j + 1] = nums[j];
                }else{
                    break;
                }
            }
            nums[j + 1] = temp;
        }
        return nums;
    }

    /**
     * 插入排序:降序
     * @param nums
     * @return
     */
    public int[] insertionSort2(int[] nums)
    {
        for (int i = 1; i < nums.length; i++) {
            int temp = nums[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (nums[j] < temp){
                    nums[j + 1] = nums[j];
                }else{
                    break;
                }
            }
            nums[j + 1] = temp;
        }
        return nums;
    }

    public void print(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            System.out.print(nums[i] + " ");
        }
    }
}

6 希尔排序简介

在这里插入图片描述

希尔排序(Shell Sort)是一种插入排序的改进版本,也被称为“缩小增量排序”(Diminishing Increment Sort)。

在这里插入图片描述

  • 定义:希尔排序是基于插入排序的算法,通过比较相距一定间隔的元素来工作,这些间隔被称为“增量”。随着算法的进行,增量逐渐减少,直到只比较相邻元素的最后一趟排序为止。
  • 提出者:该算法由D.L.Shell于1959年提出,并因此得名。
  • 稳定性:希尔排序是非稳定排序算法,因为它在多次插入排序过程中可能会改变相同元素的相对顺序。

希尔排序的步骤如下

  1. 先取一个步长,然后进行分组,然后进行插入排序
  2. 步长为步长/2,然后进行分组,然后进行插入排序
  3. 重复2,直到步长为1

希尔排序分析

  1. 时间复杂度 :O(N^2)
  2. 空间复杂度 :O(n)
  3. 稳定性 :不稳定

7 希尔排序示例

package cn.zxc.demo.leetcode_demo.base_algorithm.sort;

/**
 * 希尔排序
 * 1.先取一个步长,然后进行分组,然后进行插入排序
 * 2.步长为步长/2,然后进行分组,然后进行插入排序
 * 3.重复2,直到步长为1
 */
public class ShellSorting {

    public static void main(String[] args) {
        int[] nums = {12, 89, 34, 78, 65, 43, 21,
                52, 98, 15, 37, 62, 85, 49, 28, 71, 94,
                39, 58, 19, 87, 41, 68, 26, 91, 55, 32,
                75, 17, 83, 46, 61, 97, 30, 79, 54, 25,
                81, 48, 67, 14, 95, 36, 73, 29, 86, 42};
        ShellSorting shellSorting = new ShellSorting();
        int[] sort_nums = shellSorting.shellSort(nums, (nums.length / 2));
        shellSorting.print(sort_nums);
        sort_nums = shellSorting.shellSort2(nums, (nums.length / 2));
        shellSorting.print(sort_nums);
    }

    /**
     * 希尔排序:升序
     * @param nums
     * @param step
     * @return
     */
    public int[] shellSort(int[] nums, int step)
    {
        if (step == 0){
            return nums;
        }
        // 以step为步长进行插入排序
        for (int i = step; i < nums.length; i+=step) {
            int temp = nums[i];
            int j = i - step;
            for (; j >=0; j-=step) {
                if (nums[j] > temp){
                    nums[j+step] = nums[j];
                }else{
                    break;
                }
            }
            nums[j+step] = temp;
        }
        // 递归:每一次递归,步长变为原来的一半
        return shellSort(nums, step/2);
    }

    /**
     * 希尔排序:降序
     * @param nums
     * @param step
     * @return
     */
    public int[] shellSort2(int[] nums, int step)
    {
        if (step == 0){
            return nums;
        }
        for (int i = step; i < nums.length; i+=step) {
            int temp = nums[i];
            int j = i - step;
            for (; j >= 0; j-=step) {
                if (nums[j] < temp){
                    nums[j+step] = nums[j];
                }else{
                    break;
                }
            }
            nums[j+step] = temp;
        }
        return shellSort2(nums, step/2);
    }

    public void print(int[] nums)
    {
        System.out.print("排序后:");
        for (int i = 0; i < nums.length; i++) {
            System.out.print(nums[i] + " ");
        }
        System.out.println();
    }
}

8 归并排序简介

在这里插入图片描述

归并排序(Merge Sort)是一种基于分治思想的排序算法,它将待排序的数组分成两部分,分别对这两部分递归地进行排序,最后将两个有序子数组合并成一个有序数组。归并排序的基本思路是将待排序的数组分成两个部分,分别对这两部分进行排序,然后将排好序的两部分合并成一个有序数组。这个过程可以用递归来实现。

归并排序的步骤

  1. 分解:将待排序的数组不断分成两个子数组,直到每个子数组只有一个元素为止。这个过程可以使用递归来实现。
  2. 递归进行排序:对分解出的子数组进行归并排序。
  3. 合并:将相邻的两个已排序的子数组合并成一个有序数组,直到最后只剩下一个有序数组为止。合并的过程中,需要用到一个辅助数组来暂存合并后的有序数组。
    1. 创建一个临时数组来存放合并后的序列。
    2. 初始化两个指针,分别指向两个子序列的起始位置。
    3. 依次比较两个子序列中的元素,将较小的元素放入临时数组中,并将指向该元素的指针后移一位。
    4. 当其中一个子序列的指针移到末尾时,将另一个子序列中剩余的元素依次放入临时数组中。
    5. 将临时数组中的元素复制回原始序列的对应位置。

归并排序的分析

  1. 时间复杂度 :O(nlogn)
  2. 空间复杂度 :O(n),注意相比于插入排序,需要额外的O(n)的空间作为辅助
  3. 稳定性 :稳定

9 归并排序示例

import java.util.Arrays;

/**
 * 归并排序
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(n)
 * 实现思路:
 * 1.将数组拆分成两个子数组,然后递归的拆分,直到拆分成单个元素
 * 2.将两个子数组合并成一个有序数组
 * 3.将两个有序数组合并成一个有序数组
 */
public class MergeSort {

    public static void main(String[] args) {
        int[] nums = {1, 3, 5, 2,8,6,10, 4, 6};
        MergeSort mergeSort = new MergeSort();
        int[] sort = mergeSort.mergeSort(nums, 0, nums.length - 1);
        System.out.println(Arrays.toString(sort));
    }

    public int[] mergeSort(int[] nums, int left, int right)
    {
        if (right > left){
            int mid = (left + right) / 2;
            mergeSort(nums, left, mid);
            mergeSort(nums, mid + 1, right);
        }
        mergeNums(nums, left, right);
        return nums;
    }

    private void mergeNums(int[] nums, int left, int right) {
        // 为什么使用临时数据,不直接使用插入算法类型的数据插入的方式
        // 因为归并排序,前后两个数组都是有序的,所以只需要比较两个数组的元素,然后插入到临时数组中
        // 这样相比于数据插入的方式,需要的【数据比较】和【数据交换】的次数会减少很多
        int[] temp = new int[right - left + 1];
        int index = 0;
        int l_s = left;
        int r_s = (left + right) / 2 + 1;
        int l_e = (left + right) / 2;
        // 循环比较两个数组的元素,然后插入到临时数组中
        while (l_s <= l_e && r_s <= right ){
            if (nums[l_s] < nums[r_s]){
                temp[index] = nums[l_s];
                l_s++;
            }else {
                temp[index] = nums[r_s];
                r_s++;
            }
            index++;
        }
        // 如果左边数组还有剩余,则直接插入到临时数组中
        while (l_s <= l_e){
            temp[index] = nums[l_s];
            l_s++;
            index++;
        }
        // 如果右边数组还有剩余,则直接插入到临时数组中
        while (r_s <= right){
            temp[index] = nums[r_s];
            r_s++;
            index++;
        }
        // 将临时数组中的数据插入到原数组中
        for (int i = 0; i < temp.length; i++) {
            nums[left++] = temp[i];
        }
    }
}

  • 24
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值