经典排序算法
- 排序算法的介绍
排序也称排序算法(Sort Algorithm),排序是将一组数据,依指定的顺序进行排列的过程。 - 排序的分类
1)内部排序:
指将需要处理的所有数据都加载到内部存储器中进行排序。
2)外部排序法:
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
3)常见的排序算法分类(见右图):
1. 冒泡排序
- 基本介绍
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
优化:
因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。(这里说的优化,可以在冒泡排序写好后,再进行) - 演示冒泡过程的例子
小结上面的图解过程:
(1)一共进行数组的大小-1次大的循环
(2)每一趟排序的次数在逐渐的减少
(3)如果我们发现在某趟排序中,没有发生一次交换,可以提前结束冒泡排序。这个就是优化 - 代码实现
有一群牛,颜值分别是101,34,119,1请使用选择排序从低到高进行排序[101,34,119,1]/** * 使用逐步推导的方式来编写冒泡排序 * 冒泡排序并实现简单优化,时间复杂度O(n^2) * 80000数据进行排序大概需要:14秒 * @param arr 传入需要排序的数组 */ private void bubbleSort(int[] arr) { int temp; boolean flag = 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]) { flag = true; //优化部分 temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } // System.out.printf("第%d趟排序\n",i+1); // System.out.println(Arrays.toString(arr)); if (!flag) { //如果没有进行过交换,直接暂停返回 break; } else { flag = false; //重置flag } } /*//第一趟排序,将最大的数排在最后面(要去arr.length-1个数相比较) int temp2 = 0; for (int i = 0; i < arr.length - 1; i++) { if (arr[i] > arr[i+1]) { temp2 = arr[i]; arr[i] = arr[i+1]; arr[i+1] = temp2; } } System.out.println("第一趟排序后的数组"); System.out.println(Arrays.toString(arr)); //第二趟排序,将最大的数排在最后面(要去arr.length-2个数相比较) int temp3 = 0; for (int i = 0; i < arr.length - 1 - 1; i++) { if (arr[i] > arr[i+1]) { temp2 = arr[i]; arr[i] = arr[i+1]; arr[i+1] = temp2; } } System.out.println("第一趟排序后的数组"); System.out.println(Arrays.toString(arr));*/ }
2. 选择排序
- 基本介绍
选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。 - 选择排序思想
选择排序(select sorting)也是一种简单的排序方法。它的基本思想是:第一次从arr[0]-arr[n-1]中选取最小值,与arr[0]交换,第二次从arr[1]-arr[n-1]中选取最小值,与arr[1]交换,第三次从arr[2]-arr[n-1]中选取最小值,与ar[2]
交换,…,第i次从ar[i-1]-arr[n-1]中选取最小值,与ar[i-1]交换,……第n-1次从ar[n-2]-arr[n-1]中选取最小值,与arr[n-2]交换,总共通过n-1次,得到一个按排序码从小到大排列的有序序列。 - 选择排序思路分析图
- 思路
原始的数组:101,34,119,1
第一轮排序:1,34,119,101
第二轮排序:1,34,119,101
第三轮排序:1,34,101,119
- 说明:
- 1.选择排序一共有数组大小-1轮排序2.每1轮排序,又是一个循环,循环的规则(代)
- 2.1先假定当前这个数是最小数
- 2.2然后和后面的每个数进行比较,如果发现有比当前数更小的数,就重新确定最小数,并得到下标
- 2.3当遍历到数组的最后时,就得到本轮最小数和下标
- 2.4交换
- 代码实现
/**
* 使用逐步推导的方式来编写选择排序
* 选择排序:时间复杂度O(n^2)
* 80000数据进行排序大概需要:3秒
* @param arr 需要排序的数组
*/
private void selectSort(int[] arr) {
int minIndex; //数组中每一轮最小值的下标
int min; //数组中每一轮的最小值
for (int i = 0; i < arr.length - 1; i++) {
minIndex = i;
min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j]; //每一轮重置min的值,使其成为最小值
minIndex = j; //每一轮重置minIndex的值,找到最小值的那个数组下标
}
}
if (minIndex != i) {
//将最小值与前面的arr[i]进行替换
arr[minIndex] = arr[i];
arr[i] = min;
}
}
/*//第一轮
int minIndex = 0;
int min = arr[0];
//与后面的arr.length-1个数相比较,选出最小的数放到最前面
for (int i = 0 + 1; i < arr.length; i++) {
if (min > arr[i]) { //说明假定的最小值,并不是最小
min = arr[i]; //重置min
minIndex = i; //重置minIndex
}
}
//如果最小的数不是arr[0]就进行交换
if (minIndex != 0) {
//遍历过后min就是最小的值,i就是对应的最小值的坐标,与第一个位置进行交换
arr[minIndex] = arr[0];
arr[0] = min;
}
//第二轮
minIndex = 1;
min = arr[1];
//与后面的arr.length-1个数相比较,选出最小的数放到最前面
for (int i = 1 + 1; i < arr.length; i++) {
if (min > arr[i]) { //说明假定的最小值,并不是最小
min = arr[i]; //重置min
minIndex = i; //重置minIndex
}
}
//如果最小的数不是arr[0]就进行交换
if (minIndex != 1) {
//遍历过后min就是最小的值,i就是对应的最小值的坐标,与第一个位置进行交换
arr[minIndex] = arr[0];
arr[0] = min;
}*/
}
3. 插入排序
- 插入排序法介绍
插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。 - 插入排序法思想:
插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。 - 插入排序思路图:
- 代码实现
有一群小牛,考试成绩分别是101,34,119,1 请从小到大排序
private void insertSort(int[] arr) {
/*
* 实现思路参考下面的分布
* 使用逐步推导的方式来编写插入排序:使用Debug可以观察其变化
* 80000数据进行排序大概需要:1秒
*/
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];
insertIndex --;
}
//当退出while循环时,说明插入位置找到,insertIndex+1
if (insertIndex + 1 != i ) { //说明并没有发生上面的while循环置换(插入排序优化)
arr[insertIndex+1] = insertVal;
}
}
/*//第一轮{34,101,119,1}
//定义待插入的数
int insertVal = arr[1];
int insertIndex = 1 - 1; //即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];
insertIndex--;
}
//当退出while循环时,说明插入位置找到,insertIndex+1
arr[insertIndex+1] = insertVal;
System.out.println("第一轮排序");
System.out.println(Arrays.toString(arr));
//第二轮{34,101,119,1}
//定义待插入的数
int insertVal2 = arr[2];
int insertIndex2 = 2 - 1; //即arr[1]的前面这个数的下标
//给insertVal找到插入的位置
//说明
//1.insertIndex>=0保证在给insertVal找插入位置,不越界
// 2.insertVal<arr[insertIndex]待插入的数,还没有找到插入位置
//3.就需要将 arr[insertIndex]后移
while (insertIndex2 >= 0 && insertVal2 < arr[insertIndex2]) {
arr[insertIndex2+1] = arr[insertIndex2];
insertIndex2--;
}
//当退出while循环时,说明插入位置找到,insertIndex+1
arr[insertIndex2+1] = insertVal2;
System.out.println("第二轮排序");
System.out.println(Arrays.toString(arr));
//第三轮{1,34,101,119}
//定义待插入的数
int insertVal3 = arr[3];
int insertIndex3 = 3 - 1; //即arr[1]的前面这个数的下标
//给insertVal找到插入的位置
//说明
//1.insertIndex>=0保证在给insertVal找插入位置,不越界
// 2.insertVal<arr[insertIndex]待插入的数,还没有找到插入位置
//3.就需要将 arr[insertIndex]后移
while (insertIndex3 >= 0 && insertVal3 < arr[insertIndex3]) {
arr[insertIndex3+1] = arr[insertIndex3];
insertIndex3--;
}
//当退出while循环时,说明插入位置找到,insertIndex+1
arr[insertIndex3+1] = insertVal3;
System.out.println("第三轮排序");
System.out.println(Arrays.toString(arr));*/
}
4. 希尔排序
- 希尔排序法介绍
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。 - 希尔排序法基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止 - 希尔排序法的示意图
- 代码实现
有一群小牛,考试成绩分别是{8,9,1,7,2,3,5,4,6,0} 请从小到大排序,请分别使用
1)希尔排序时,对有序序列在插入时采用交换法,并测试排序速度
2)希尔排序时,对有序序列在插入时采用移动法,并测试排序速度
4.1 交换法实现希尔排序
private void shellSort(int[] arr) {
/**
* 使用逐步推导的方式来编写希尔排序
* 希尔排序:第一种方法:交换法
* 80000数据进行排序大概需要:12秒
*/
int temp = 0;
//显示每次分几次组(arr.length/2表示第一次分多少组)
for (int i = arr.length/2; i > 0; i /= 2) { //5,2,1
//i的值表示每一次分组所跨的步长
for (int j = i; j < arr.length; j++) { //i=2,j=2时,2,3,4,5,6,7,8,9
for (int k = j - i; k >= 0; k -= i) {
if (arr[k] >arr[k+i]) {
temp = arr[k];
arr[k] = arr[k+i];
arr[k+i] = temp;
}
}
}
}
// System.out.println(Arrays.toString(arr));
/*int temp = 0;
//希尔排序的第1轮排序
//因为第1轮排序,是将10个数据分成了5组
for (int i = 10/2; 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("希尔排序第一轮"); //[3, 5, 1, 6, 0, 8, 9, 4, 7, 2]
System.out.println(Arrays.toString(arr));
//希尔排序的第2轮排序
//因为第1轮排序,是将10个数据分成了5/2 = 2组
for (int i = 5/2; i < arr.length; i++) {
//遍历各组中所有的元素(共2组,每组有5个元素),步长为2
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("希尔排序第二轮"); //[0, 2, 1, 4, 3, 5, 7, 6, 9, 8]
System.out.println(Arrays.toString(arr));*/
}
4.2 移动法实现希尔排序
/*
* 使用移位法进行希尔排序:将希尔排序与插入排序相结合
* 80000数据进行排序大概需要:1秒
*/
private void shellSort(int[] arr) {
//增量gap
for (int gap = arr.length / 2;gap > 0;gap /= 2) {
//从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i = gap; i < arr.length; i++) {
//使用插入排序的算法
int j = i;
int temp = arr[j];
if (arr[j] < arr[j-gap]) {
while (j - gap >= 0 && temp < arr[j-gap]) {
//移动
arr[j] = arr[j-gap];
j -= gap;
}
//当while循环退出时,就给temp找到插入的位置
arr[j] = temp;
}
}
}
// System.out.println(Arrays.toString(arr));
}
5. 快速排序
-
快速排序法介绍
快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列 -
快速排序法示意图:
-
代码实现
要求:对[-9,78,0,23-567,70]进行从小到大的排序,要求使用快速排序法。【测试8w和800w】
说明[验证分析]:
1)如果取消左右递归,结果是 -9,-567,0,23,78,70
2)如果取消右递归,结果是 -567,-9,0,23,78,70
3)如果取消左递归,结果是 -9,-567,0,23,70,8/** * 快速排序:速度非常快,对8000000的大小数组进行排序只需要1秒钟 * 此方法是以中间的那个只为基准 * @param arr 排序的数组 * @param left 左索引 * @param right 右索引 */ private void quickSort(int[] arr,int left,int right) { int l = left; //左下标 int r = right; //右下标 //pivot 中轴值 int pirvot = arr[(left + right) / 2]; int temp = 0; //临时变量 //while循环的目的是让比pivot值小的放到左边,比pivot值大的放到右边 while (l <r) { //在pivot左边一直找,直到找到大于等于pivot的值,才能退出 while (arr[l] < pirvot) { l += 1; } //在pivot右边一直找,直到找到小于等于pivot的值,才能退出 while (arr[r] > pirvot) { r -= 1; } //对上面的两个循环条件进行终止(当都左右索引已经遍历超过中间时,进行break) if (l >= r) { break; } //交换 temp = arr[l]; arr[l] = arr[r]; arr[r] = temp; //如果交换完后,发现这个arr[l] == pivot值相等 r--,前移 if (arr[l] == pirvot) { r -= 1; } //如果交换完后,发现这个arr[r] == pivot值相等 l++,后移 if (arr[r] == pirvot) { 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 ); } }