一、排序算法的概念
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);
}
}
}