排序算法学习笔记一

0 概述

如下图所示,给出了常见排序算法,本文主要总结基于比较排序算法。
在这里插入图片描述

1 基于比较排序算法

基于比较排原地序算法常见的主要有冒泡排序、选择排序、插入排序。

1.1 冒泡排序
  • 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。冒泡排序包含两种基本操作:比较和交换。最好情况时间复杂度O(n),平均和最坏的时间复杂度为O(n2),是稳定排序。

    /**
     * 一次冒泡会让至少一个元素移动到它应该在的位置,所以最坏情况需要扫描n次
     * 只会操作相邻的两个数据,内循环是比较相邻的两个元素
     */
    public static void bubbleSort(int arr[]) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int len = arr.length;
        //是否存在交换的标志位
        boolean existSwap = false;
        //需要n次冒泡
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len - i - 1; j++) {
                //交换
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = tmp;
                    
                    existSwap = true;
                }
            }
            // 没有数据交换,提前退出
            if (!existSwap) {
                break;
            }

        }
    }
1.2 插入排序

插入排序算法基本思想将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。插入排序基本操作:比较和移动。最好情况时间复杂度O(n),平均和最坏的时间复杂度为O(n2),是稳定排序。

    /**
     * 插入排序
     *
     */
    public static void insertSort(int arr[]) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int len = arr.length;
        //
        for (int i = 1; i < len; i++) {
            int value = arr[i];
            int j = i - 1;
            // 和前面进行比较,数据后移动
            for (; j >= 0; j--) {
                //数据移动,如果已经有序直接break
                if (arr[j] > value) {
                    arr[j + 1] = arr[j];
                } else {
                    break;
                }
            }
            //插入合适的数据
            arr[j + 1] = value;
        }
    }

1.3 选择排序

选择排序基本思想:第一次从待排序的数据元素选择最小(或最大)的一个元素,放到第一位置。第二次从待排序的数据元素选择最小(或最大)的一个元素,放到第二个位置。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。最好、平均和最坏的时间复杂度为O(n2)。

   /**
     * 选择排序
     *
     */
    public static void selectSort(int arr[]) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        int len = arr.length;
        // 每次选择一个元素
        for (int i = 0; i < len; i++) {
            int minIndex = i;
            for (int j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            //交换选择出来的最小值,放到相应的位置
            int tmp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = tmp;
        }
    }

排利用的也是分治思想。乍看起来,它有点像归并排序,但是思路其实完全不一样。我们待会会讲两者的区别。现在,我们先来看下快排的核心思想。快排的思想是这样的:如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

1.4 归并排序

归并排序使用的分治思想;就是将一个大问题逐步分解成小的子问题来解决,可以使用递归来实现归并排序。
下面给出递归公式&递归结束条件

mergeSort(a[],p,r)=merge(mergeSort(a[],p,q)+mergeSort(a[],q+1,r));
递归终止条件
p>=r
    public static void mergeSort(int arr[]) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        mergeSortUseRecursion(arr, 0, arr.length-1);
    }

    private static void mergeSortUseRecursion(int arr[], int p, int r) {
        //递归结束条件
        if (p >= r) {
            return;
        }
        //取中间位置
        int q = p + (r - p) / 2;
        mergeSortUseRecursion(arr, p, q);
        mergeSortUseRecursion(arr, q + 1, r);
        //数据做merge(p,r)
        int[] left = new int[q - p + 2];
        int[] right = new int[r - q + 1];

        for (int i = 0; i <= q - p; i++) {
            left[i] = arr[p + i];
        }
        for (int i = 0; i < r - q; i++) {
            right[i] = arr[q + 1 + i];
        }
        // 第一个数组添加哨兵(最大值)
        left[q - p + 1] = Integer.MAX_VALUE;
        // 第二个数组添加哨兵(最大值)
        right[r - q] = Integer.MAX_VALUE;
        int i = 0;
        int j = 0;
        int k = p;
        while (k <= r) {
            // 当左边数组到达哨兵值时,i不再增加,直到右边数组读取完剩余值,同理右边数组也一样
            if (left[i] <= right[j]) {
                arr[k++] = left[i++];
            } else {
                arr[k++] = right[j++];
            }
        }

    }
1.5 快速排序

快速排序用的也是分治的思想,乍一看可能和归并有点相似,其实思路有很大差异的。其基本思想:如果要排序数组中下标从 p 到 r 之间的一组数据,我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,数组 p 到 r 之间的数据就被分成了三个部分,前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,后面的 q+1 到 r 之间是大于 pivot 的。

quickSort(a[],p,r)=quickSort(a[],p,q)+quickSort(a[],q+1,r);
递归终止条件
p>=r
   
  void  quickSort(int [] arr,int low,int high) {
        if(low>=high) {
            return;
        }
        int p=partition(arr,low,high);
        quickSort(arr,low,p-1);
        quickSort(arr,p+1,high);

    }
    int partition(int []arr,int low,int high) {
        int pivot=arr[low];
        int left=low,right=high;
        while (left <right) {
            //要先从右边找,然后再从左边找顺序不要错了,原因哨兵值在左边开始
            while (arr[right]>=pivot && left<right) --right;
            while (arr[left] <= pivot && left < right) left++;
            if(left<right) {
               int tmp=arr[left];
               arr[left]=arr[right];
               arr[right]=tmp;
            }
        }
        arr[low]=arr[left];
        arr[left]=pivot;
        return left;
    }

根据快速排序思想,求k大元素。

   public static int kSmall(int arr[], int k) {
        if (arr == null || arr.length < k) {
            return -1;
        }
        int p = partition(arr, 0, arr.length - 1);
        while (p + 1 != k) {
            if (p < k) {
                p = partition(arr, p + 1, arr.length - 1);
            } else {
                p = partition(arr, 0, p);
            }
        }
        return arr[p];
    }

2 总结

虽然冒泡排序和插入排序的时间复杂度都是 O(n2),都是稳定排序算法,但是从代码上可以看出,插入排序是优于冒泡排序的,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。

排序算法是否是稳定排序最好、平均、最坏空间复杂度
冒泡O(n)、O(n2)、O(n2)O(1)
插入O(n)、O(n2)、O(n2)O(1)
选择O(n2)、O(n2、O(n2)O(1)
归并O(nlogn)、O(nlogn)、O(nlogn)O(n)
快速O(nlogn)、O(nlogn)、O(n2)O(logn)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值