排序是将一组数据,依指定的顺序进行排列的过程
分类
- 1)内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序
- 插入排序
- 1 直接插入排序(常用)
- 2 希尔排序
- 选择排序
- 3 简单选择排序(常用)
- 4 堆排序
- 交换排序
- 5 冒泡排序(常用)
- 6 快速排序
- 7 归并排序
- 8 基数排序
- 插入排序
- 2)外部排序法:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序
1. 冒泡排序
冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始)==,依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部==,就像水底的气泡一样逐渐向上冒。
推演过程
代码实现
main{
int[] arr = {3,9,-1,10,-2};
bubbleSort(arr);
System.out.println("排序后的数组");
System.out.println(Arrays.toString(arr));
}
public static void bubbleSort(int[] arr){
//冒泡排序 时间复杂度O(n²)
int temp = 0;//临时变量
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]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
System.out.println("内层排序后的数组为:j=="+j+"="+Arrays.toString(arr));
}
System.out.println("第"+(i+1)+"次排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
控制台输出,感受数组的变化过程
内层排序后的数组为:j==0=[3, 9, -1, 10, -2]
内层排序后的数组为:j==1=[3, -1, 9, 10, -2]
内层排序后的数组为:j==2=[3, -1, 9, 10, -2]
内层排序后的数组为:j==3=[3, -1, 9, -2, 10]
----------------第1次排序后的数组----------------
[3, -1, 9, -2, 10]
内层排序后的数组为:j==0=[-1, 3, 9, -2, 10]
内层排序后的数组为:j==1=[-1, 3, 9, -2, 10]
内层排序后的数组为:j==2=[-1, 3, -2, 9, 10]
----------------第2次排序后的数组----------------
[-1, 3, -2, 9, 10]
内层排序后的数组为:j==0=[-1, 3, -2, 9, 10]
内层排序后的数组为:j==1=[-1, -2, 3, 9, 10]
----------------第3次排序后的数组----------------
[-1, -2, 3, 9, 10]
内层排序后的数组为:j==0=[-2, -1, 3, 9, 10]
----------------第4次排序后的数组----------------
[-2, -1, 3, 9, 10]
冒泡排序的优化
由于排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换。从而减少不必要的比较。
public static void bubbleSort(int[] arr){
//优化
int temp = 0;//临时变量
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.println("第"+(i+1)+"次排序后的数组");
System.out.println(Arrays.toString(arr));
if (!flag){//没有发生过交换,表示数组已经有序排列
break;
}else {
flag = false;//进行下次判断
}
}
}
测试80000个数排序所需的时间
需要11-12s就可以完成
public static void main(String[] args) {
//测试80000个数据的排序时间
int[] array = new int[80000];
for (int i = 0; i < 80000; i++) {
array[i] = (int) (Math.random()*800000);//生成80000个0-800000的数
}
Date date1 = new Date();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date1Str = simpleDateFormat.format(date1);
System.out.println("排序前的时间是=="+date1Str);
bubbleSort(array);
Date date2 = new Date();
String date2Str = simpleDateFormat.format(date2);
System.out.println("排序后的时间是=="+date2Str);
}
2. 选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
时间复杂度T(n) = O(n²)
推演过程
代码实现
/**
* @author DSH
* @date 2020/4/30
* @description 选择排序
*/
public class SelectionSort {
public static void main(String[] args) {
int[] arr = {3,9,-1,10,-2};
selectionSort(arr);
System.out.println("排序后=="+Arrays.toString(arr));
}
public static void selectionSort(int[] arr){
// for (int i = 0; i < arr.length-1; i++) {
// int min = arr[i];
// int minIndex = i;
// for (int j = i+1; j < arr.length; j++) {
// if (arr[j]<min){
// min = arr[j];
// minIndex = j;
// }
// }
// if (minIndex!=i) {
// arr[minIndex] = arr[i];
// arr[i] = min;
// }
// }
for (int i = 0; i < arr.length-1; i++) {
System.out.println("-----------第"+(i+1)+"次排序------------");
int minIndex = i;
for (int j = i+1; j < arr.length; j++) {
if (arr[j]<arr[minIndex]){
minIndex = j;
}
System.out.println("内层循环 , 当前最小值为arr["+minIndex+"]="+arr[minIndex]);
}
if (minIndex!=i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
System.out.println("第"+(i+1)+"次排序后的数组");
System.out.println(Arrays.toString(arr));
}
}
}
控制台输出,感受内层循环最小值的变化
-----------第1次排序------------
内层循环 , 当前最小值为arr[0]=3
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[4]=-2
第1次排序后的数组
[-2, 9, -1, 10, 3]
-----------第2次排序------------
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
内层循环 , 当前最小值为arr[2]=-1
第2次排序后的数组
[-2, -1, 9, 10, 3]
-----------第3次排序------------
内层循环 , 当前最小值为arr[2]=9
内层循环 , 当前最小值为arr[4]=3
第3次排序后的数组
[-2, -1, 3, 10, 9]
-----------第4次排序------------
内层循环 , 当前最小值为arr[4]=9
第4次排序后的数组
[-2, -1, 3, 9, 10]
测试80000个数排序所需的时间
需要3s左右(mbp15)
3. 插入排序
插入排序(Insertion Sorting):把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
推导过程
//代码推导过程
public static void derivation(){
int arr[] = {17,3,25,14};
//第一轮 =>{3,17,25,14}
//定义待插入的数
int insertVal = arr[1];
int insertIndex = 1-1;//即arr[1]前面的数的坐标
//给insertValue找到插入的位置
//1 insertIndex>=0保证不越界
//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("第一轮插入后"+Arrays.toString(arr));//[3, 17, 25, 14]
//第二轮
insertVal = arr[2];
insertIndex = 2-1;//即arr[1]前面的数的坐标
while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1] = insertVal;
System.out.println("第二轮插入后"+Arrays.toString(arr));//[3, 17, 25, 14]
//第三轮
insertVal = arr[3];
insertIndex = 3-1;
while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
}
arr[insertIndex+1] = insertVal;
System.out.println("第三轮插入后"+Arrays.toString(arr));//[3, 14, 17, 25]
}
代码实现
public class InsertionSort {
public static void main(String[] args) {
int arr[] = {17,3,25,14,20,9};
insertionSort(arr);
}
public static void insertionSort(int[] arr) {
int insertVal = 0;
int insertIndex = 0;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];
insertIndex = i-1;//即arr[]前面的数的坐标
//给insertValue找到插入的位置
//1 insertIndex>=0保证不越界
//2 insertVal<arr[insertIndex 待插入的数还没有找到插入位置
//3 需要将arr[insertIndex]后移
System.out.println("待插入的数据为arr["+i+"]="+insertVal);
while(insertIndex>=0&&insertVal<arr[insertIndex]){//保证不越界
arr[insertIndex+1] = arr[insertIndex];
insertIndex--;
System.out.println("挪动后的数组为"+Arrays.toString(arr));
}
//当退出while循环时,说明插入的位置找到了,insertIndex+1;
//判断是否需要赋值
if (insertIndex+1 != i) {
arr[insertIndex + 1] = insertVal;
}
System.out.println("第"+i+"轮插入后,数组为");
System.out.println(Arrays.toString(arr));
System.out.println("-----------------------------------------");
}
}
}
控制台输出,感受数组的变化
待插入的数据为arr[1]=3
挪动后的数组为[17, 17, 25, 14, 20, 9]
第1轮插入后,数组为
[3, 17, 25, 14, 20, 9]
-----------------------------------------
待插入的数据为arr[2]=25
第2轮插入后,数组为
[3, 17, 25, 14, 20, 9]
-----------------------------------------
待插入的数据为arr[3]=14
挪动后的数组为[3, 17, 25, 25, 20, 9]
挪动后的数组为[3, 17, 17, 25, 20, 9]
第3轮插入后,数组为
[3, 14, 17, 25, 20, 9]
-----------------------------------------
待插入的数据为arr[4]=20
挪动后的数组为[3, 14, 17, 25, 25, 9]
第4轮插入后,数组为
[3, 14, 17, 20, 25, 9]
-----------------------------------------
待插入的数据为arr[5]=9
挪动后的数组为[3, 14, 17, 20, 25, 25]
挪动后的数组为[3, 14, 17, 20, 20, 25]
挪动后的数组为[3, 14, 17, 17, 20, 25]
挪动后的数组为[3, 14, 14, 17, 20, 25]
第5轮插入后,数组为
[3, 9, 14, 17, 20, 25]
-----------------------------------------
自己憋的双层for循环渣排序
//自己憋了一个小时写的插入排序,双层for循环,效率并不高
public static void myInsertionSort(int[] arr) {
int[] newArr = new int[arr.length];
//先给第一个元素赋值,然后从第二个开始比较
newArr[0] = arr[0];
for (int i = 1; i < arr.length; i++) {
newArr[i] = arr[i];
// System.out.println("-------第"+i+"次排序");
for (int j = 0; j < i; j++) {
if (newArr[j]>newArr[i]){
int temp = newArr[i];
newArr[i] = newArr[j];
newArr[j] = temp;
}
// System.out.println(Arrays.toString(newArr));
}
}
}
测试80000个数排序所需的时间
0.7-0.75s之间
InsertionSort.insertionSort(array);
双层for循环,3.5-3.8s之间
InsertionSort.myInsertionSort(array);
4. 希尔排序(缩小增量排序)
希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序.
基本思想
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止.
推导过程
public static void derivation(int[] arr){
//第一轮排序,将十个数分成了5组
int temp = 0;
for (int i = 5; i < arr.length; i++) {
//遍历各组中所有的元素(共5组,每组两个元素,步长是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("第一轮之后的数组为=="+ Arrays.toString(arr));
//第二轮排序,将10个数分成5/2 = 2组
for (int i = 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("第二轮之后的数组为=="+ Arrays.toString(arr));
//第三轮排序,将10个数分成2/2 一组
for (int i = 1; i < arr.length; i++) {
//遍历各组中所有的元素(共2组,每组5个元素,步长是2)
for (int j = i-1; j >=0; j --) {
//如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j]>arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
System.out.println("第三轮之后的数组为=="+ Arrays.toString(arr));
}
代码实现
交换式
public class ShellSort {
public static void main(String[] args) {
int arr[] = {8,9,1,7,2,3,5,4,6,0};
shellSort(arr);
// derivation(arr);
}
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
for (int gap = arr.length/2; gap >0 ; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历各组中所有的元素(共gap组,每组arr/gap个元素,步长是gap)
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("第"+(++count)+"轮之后的数组为=="+ Arrays.toString(arr));
}
}
}
移位式
//对交换式的希尔排序进行优化->移位法
public static void shellSort2(int[] arr) {
//增量gap,并逐步缩小增量
int count = 0;
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("第"+(++count)+"轮之后的数组为=="+ Arrays.toString(arr));
}
}
测试80000个数排序所需的时间
希尔排序(交换式)6-8s,7.7s左右
ShellSort.shellSort(array);
希尔排序(移位式)0.02s
ShellSort.shellSort2(array);