【五】排序算法

一、排序算法的概念

1、排序算法的介绍

2、排序的分类

3、算法的时间复杂度

3.1、度量一个程序(算法)执行时间的两种方法

3.2、时间频度

3.3、时间复杂度

3.4、常见的时间复杂度

3.5、平均时间复杂度和最坏时间复杂度

二、排序算法

1、冒泡排序

1.1、基本介绍

1.2、演示冒泡过程的例子(图解)

1.3、冒泡排序应用实例

2、选择排序

2.1、选择排序思想

2.2、选择排序思路分析图

2.3、选择排序应用实例

3、插入排序

3.1、插入排序法思想

3.2、插入排序思路图

3.3、插入排序法应用实例

4、希尔排序

4.1、简单插入排序存在的问题

4.2、希尔排序法介绍

4.3、希尔排序法基本思想

4.4、希尔排序法的示意图

4.5、希尔排序法应用实例

5、快速排序

5.1、快速排序法介绍

5.2、快速排序法示意图

5.3、快速排序法应用实例


一、排序算法的概念

1、排序算法的介绍

排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。

2、排序的分类

3、算法的时间复杂度

3.1、度量一个程序(算法)执行时间的两种方法

1) 事后统计的方法
        这种方法可行, 但是有两个问题:一是(程序可运行)要想对设计的算法的运行性能进行评测,需要实际运行该程序;二是(计算机状况一样)所得时间的统计量依赖于计算机的硬件、软件等环境因素, 这种方式,要在同一台计算机的相同状态下运行,才能比较那个算法速度更快。

2) 事前估算的方法
       通过分析某个算法的时间复杂度来判断哪个算法更优.

3.2、时间频度

  • 基本介绍

时间频度:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为 T(n)。

  • 举例说明-基本案例

  • 举例说明-忽略常数项

  • 举例说明-忽略低次项

  • 举例说明-忽略低次项

  • 举例说明-忽略系数

3.3、时间复杂度

1) 一般情况下,算法中的基本操作语句的重复执行次数是问题规模 n 的某个函数,用 T(n)表示,若有某个辅助函数 f(n),使得当 n 趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称 f(n)是 T(n)的同数量级函数。记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。

2) T(n) 不同,但时间复杂度可能相同。 如:T(n)=n2+7n+6 与 T(n)=3n2+2n+2 它们的 T(n) 不同,但时间复杂
度相同,都为 O(n2)。

3) 计算时间复杂度的方法:

  • 用常数 1 代替运行时间中的所有加法常数 T(n)=n2+7n+6=> T(n)=n2+7n+1
  • 修改后的运行次数函数中,只保留最高阶项 T(n)=n2+7n+1 => T(n) = n2
  • 去除最高阶项的系数 T(n) = n2 => T(n) = n2 => O(n2)

3.4、常见的时间复杂度

  • 常数阶 O(1)
  • 对数阶 O(log2n)
  • 线性阶 O(n)
  • 线性对数阶 O(nlog2n)
  • 平方阶 O(n^2)
  • 立方阶 O(n^3)
  • k 次方阶 O(n^k)
  • 指数阶 O(2^n)

说明:

  • 常见的算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n^2)<Ο(n^3)< Ο(n^k) <Ο(2^n) ,随着问题规模 n 的不断增大,上述时间复杂度不断增大,算法的执行效率越低
  • 从图中可见,我们应该尽可能避免使用指数阶的算法
  • 常数阶 O(1)

  • 对数阶 O(log2n)

  • 线性阶 O(n)

  • 线性对数阶 O(nlogN)

  • 平方阶 O(n2)

3.5、平均时间复杂度和最坏时间复杂度

1) 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。

2) 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。

3) 平均时间复杂度和最坏时间复杂度是否一致,和算法有关(如图:)

二、排序算法

1、冒泡排序

1.1、基本介绍

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。

优化:

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志 flag 判断元素是否进行过交换。从而减少不必要的比较。

1.2、演示冒泡过程的例子(图解)

小结上面的图解过程:

(1) 一共进行 数组的大小-1 次 大的循环
(2)每一趟排序的次数在逐渐的减少
(3) 如果我们发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序。这个就是优化

1.3、冒泡排序应用实例

我们举一个具体的案例来说明冒泡法。我们将五个无序的数:3, 9, -1, 10, -2 使用冒泡排序法将其排成一个从小到大的有序数列。

代码实现:

package com.narwal.sort;

import java.util.Arrays;

/**
 * 第i趟就把第i大的数放到倒数第i个位置
 */
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {-1, 2, -3, 5, 0};
//        int[] arr = {-3, 2, -1};
        bubbleSort(arr);
    }

    private static void bubbleSort(int[] arr) {
        int temp;
        boolean change = false; // 默认是没有交换
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    change = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if (change) {
                change = false;  // 重置flag,进行下次判断
            } else {
                break;
            }
        }
        System.out.println(Arrays.toString(arr));
        /*
        int temp;
        // 第一趟;把最大的放在最后面
        for(int i=0; i<arr.length-1; i++){
            if(arr[i]>arr[i+1]){
                temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
            }
        }
        System.out.println(Arrays.toString(arr));
        // 第二趟;把第二大的放在倒数第二个位置
        for(int i=0; i<arr.length-1-1; i++){
            temp = arr[i];
            arr[i] = arr[i+1];
            arr[i+1] = temp;
        }
        System.out.println(Arrays.toString(arr));
    }*/
    }
}

2、选择排序

2.1、选择排序思想

选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从 arr[0]~arr[n-1]中选取最小值,与 arr[0]交换,第二次从 arr[1]~arr[n-1]中选取最小值,与 arr[1]交换,第三次从 arr[2]~arr[n-1]中选取最小值,与 arr[2]交换,...,第 i 次从 arr[i-1]~arr[n-1]中选取最小值,与 arr[i-1]交换,..., 第 n-1 次从 arr[n-2]~arr[n-1]中选取最小值,与 arr[n-2]交换,总共通过 n-1 次,得到一个按排序码从小到大排列的有序序列。

2.2、选择排序思路分析图

2.3、选择排序应用实例

有一群牛 , 颜值分别是 101, 34, 119, 1 请使用选择排序从低到高进行排序 [101, 34, 119, 1]

代码实现:

package com.narwal.sort;

import java.util.Arrays;

/**
 * 第i轮就将倒数第i大的数据放在第i个位置
 */
public class SelectSort {
    public static void main(String[] args) {
        int[] arr = {101, 34, 119, 1};
        selectSort(arr);
    }

    private static void selectSort(int[] arr) {

        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;  // 假定刚开始的位置就是最小值
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) { // 从后面取数与前面的相比
                if (min > arr[j]) {
                    minIndex = j;
                    min = arr[j];
                }
            }
            if (minIndex != i) {
                arr[minIndex] = arr[i];
                arr[i] = min;
            }
        }
        System.out.println(Arrays.toString(arr));
        /*
        // 使用逐步推到的方式
        // 原始的数组:101, 34, 119, 1
        // 第一轮排序:1, 34, 119, 101

        // 第一轮:将最小的放在最前面
        int minIndex = 0;
        int min = arr[0]; // 假定第一个就是最小值
        for (int j = 1; j < arr.length; j++) {
            if (min > arr[j]) {  // 说明假定的最小值,并不是最小值
                min = arr[j]; // 重置min
                minIndex = j; // 重置minIndex

            }
        }
        if (minIndex != 0) {
            arr[minIndex] = arr[0];
            arr[0] = min;
        }
        System.out.println(Arrays.toString(arr));
        // 第二轮:将倒数第二小的放在第二个位置
        minIndex = 1;
        min = arr[1];
        for (int j = 1 + 1; j < arr.length; j++) {
            if (min > arr[j]) {
                minIndex = j;
                min = arr[j];
            }
        }
        if (minIndex != 1) {
            arr[minIndex] = arr[1];
            arr[1] = min;
        }
        System.out.println(Arrays.toString(arr));
        */
    }
}

3、插入排序

3.1、插入排序法思想

插入排序(Insertion Sorting)的基本思想是:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。

3.2、插入排序思路图

3.3、插入排序法应用实例

有一群小牛, 考试成绩分别是 101, 34, 119, 1请从小到大排序

代码实现:

package com.narwal.sort;

import java.util.Arrays;

/**
 * 插入排序(Insertion Sorting)的基本思想是:把 n 个待排序的元素看成为一个有序表和一个无序表,开始时有
 * 序表中只包含一个元素,无序表中包含有 n-1 个元素,排序过程中每次从无序表中取出第一个元素,把它的排
 * 序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
 */
public class InsertSort {  // 后面无需的插入到前面有序的
    public static void main(String[] args) {
        int[] arr = {101, 34, 119, 1};
        insertSort(arr);
    }

    public static void insertSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            int insertVal = arr[i];
            int insertIndex = i - 1;
            while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
                arr[insertIndex + 1] = arr[insertIndex];// insertVal已经保存了待插入的值;
                insertIndex--;
            }
            //当退出 while 循环时,说明插入的位置找到, insertIndex + 1
            arr[insertIndex + 1] = insertVal;
            System.out.println(Arrays.toString(arr));
        }
        /*// 第一轮{101, 34, 119, 1} => {34, 101, 119, 1}
        // 定义待插入的数
        int insertVal = arr[1];
        int insertIndex = 0; // 即arr[1]前面的位置,就是要插入的位置

        // 给insertVal找到插入的位置
        // 说明
        // 1、insertIndex>=0 保证在给insertVal找插入位置不越界
        // 2、insertVal<arr[insertIndex]前面还有数比它小;要插入到刚好比它大的数前面
        // 3、就需要将arr[insertIndex]后移
        while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
            arr[insertIndex + 1] = arr[insertIndex];// insertVal已经保存了待插入的值;
            insertIndex--;
        }
        //当退出 while 循环时,说明插入的位置找到, insertIndex + 1
        arr[insertIndex + 1] = insertVal;
        System.out.println("第 1 轮插入");
        System.out.println(Arrays.toString(arr));

        // 第二轮
        insertVal = arr[2];
        insertIndex = 1; // 即arr[1]前面的位置,就是要插入的位置
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
            insertIndex--;
        }
        arr[insertIndex + 1] = insertVal;
        System.out.println("第 2 轮插入");
        System.out.println(Arrays.toString(arr));

        // 第二轮
        insertVal = arr[3];
        insertIndex = 2; // 即arr[1]前面的位置,就是要插入的位置
        while (insertIndex >= 0 && insertVal < arr[insertIndex]){
            arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
            insertIndex--;
        }
        arr[insertIndex + 1] = insertVal;
        System.out.println("第 3 轮插入");
        System.out.println(Arrays.toString(arr));

    }*/
    }

}

4、希尔排序

4.1、简单插入排序存在的问题

我们看简单的插入排序可能存在的问题.
数组 arr = {2,3,4,5,6,1} 这时需要插入的数 1(最小), 这样的过程是:
{2,3,4,5,6,6}
{2,3,4,5,5,6}
{2,3,4,4,5,6}
{2,3,3,4,5,6}
{2,2,3,4,5,6}
{1,2,3,4,5,6}
结论: 当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响.

4.2、希尔排序法介绍

希尔排序是希尔(Donald Shell)于 1959 年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。

4.3、希尔排序法基本思想

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

4.4、希尔排序法的示意图

4.5、希尔排序法应用实例

有一群小牛, 考试成绩分别是 {8,9,1,7,2,3,5,4,6,0} 请从小到大排序. 

代码实现:

package com.narwal.sort;

import java.util.Arrays;

/**
 * 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,
 * 每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止(组内排序)
 */
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
//        shellSort(arr);
        shellSort2(arr);
    }

    // 希尔排序时, 对有序序列在插入时采用交换法,
    public static void shellSort(int[] arr) {
        int temp;
        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                // 遍历各组中所有的元素(共5组,每组有2个元素),步长为5
                for (int j = i - gap; j >= 0; j -= gap) {
                    // 如果当前元素大于加上步长后的那个元素,说明可交换
                    if (arr[j] > arr[j + gap]) {
                        temp = arr[j];
                        arr[j] = arr[j + gap];
                        arr[j + gap] = temp;
                    }
                }
            }
        }
        System.out.println("希尔排序后=" + Arrays.toString(arr));
        /*// 希尔排序的第一轮排序
        // 因为第一轮排序是将10个数据分成了5组
        for (int i = 5; i < arr.length; i++) {
            // 遍历各组中所有的元素(共5组,每组有2个元素),步长为5
            for (int j = i - 5; j >= 0; j -= 5) {
                // 如果当前元素大于加上步长后的那个元素,说明可交换
                if (arr[j] > arr[j + 5]) {
                    temp = arr[j];
                    arr[j] = arr[j + 5];
                    arr[j + 5] = temp;
                }
            }
        }
        System.out.println("希尔排序 1 轮后=" + Arrays.toString(arr));
        // 第二轮
        // 因为第二轮是将10个数据分成了5/2=2组
        for (int i = 2; i < arr.length; i++) {
            for (int j = i - 2; j >= 0; j -= 2) {
                if (arr[j] > arr[j + 2]) {
                    temp = arr[j];
                    arr[j] = arr[j + 2];
                    arr[j + 2] = temp;
                }
            }
        }
        System.out.println("希尔排序 2 轮后=" + Arrays.toString(arr));
        // 第二轮
        // 因为第二轮是将10个数据分成了2/2=1组
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0; j -= 1) {
                if (arr[j] > arr[j + 1]) {
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        System.out.println("希尔排序 3 轮后=" + Arrays.toString(arr));*/
    }
    // 采用位移法
    public static void shellSort2(int[] arr) {

        for (int gap = arr.length / 2; gap > 0; gap /= 2) {
            for (int i = gap; i < arr.length; i++) {
                int j = i;
                int temp = arr[j];
                while (j - gap >= 0 && temp < arr[j - gap]) {
                    arr[j] = arr[j - gap];  // 后移
                    j -= gap;
                }
                arr[j] = temp;
            }
        }
        System.out.println("希尔排序后=" + Arrays.toString(arr));
    }
}

5、快速排序

5.1、快速排序法介绍

快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

5.2、快速排序法示意图

5.3、快速排序法应用实例

要求: 对 [-9,78,0,23,-567,70] 进行从小到大的排序,要求使用快速排序法。

代码实现

package com.narwal.sort;

import java.util.Arrays;

public class QuickSort {
    public static void main(String[] args) {
        int[] arr = {-9, 78, 0, 23, 0, 70, -1, 900, 4561};
        quickSort(arr, 0, 8);
        System.out.println(Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int left, int right) {
        int l = left; //左下标
        int r = right; //右下标
        //pivot 中轴值
        int pivot = arr[(left + right) / 2];
        int temp; //临时变量,作为交换时使用
        //while 循环的目的是让比 pivot 值小放到左边
        //比 pivot 值大放到右边
        while (l < r) {
            //在 pivot 的左边一直找,找到大于等于 pivot 值,才退出
            while (arr[l] < pivot) {
                l += 1;
            }
            //在 pivot 的右边一直找,找到小于等于 pivot 值,才退出
            while (arr[r] > pivot) {
                r -= 1;
            }
            //如果 l >= r 说明 pivot 的左右两的值,已经按照左边全部是
            //小于等于 pivot 值,右边全部是大于等于 pivot 值
            if (l >= r) {
                break;
            }
            //交换
            temp = arr[l];
            arr[l] = arr[r];
            arr[r] = temp;
            //如果交换完后,发现这个 arr[l] == pivot 值 相等 r--, 前移
            if (arr[l] == pivot) {
                r -= 1;
            }
            //如果交换完后,发现这个 arr[r] == pivot 值 相等 l++, 后移
            if (arr[r] == pivot) {
                l += 1;
            }
        }
        // 如果 l == r, 必须 l++, r--, 否则为出现栈溢出
        if (l == r) {
            l += 1;
            r -= 1;
        }
        //向左递归
        if (left < r) {
            quickSort(arr, left, r);
        }
        //向右递归
        if (right > l) {
            quickSort(arr, l, right);
        }
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值