冒泡排序
冒泡排序的英文Bubble Sort ,是一种最基础的交换排序。之所以叫做冒泡排序,因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动。
冒泡排序的原理:
每一趟只能确定将一个数归位。即第一趟只能确定将末位上的数归位,第二趟只能将倒数第 2 位上的数归位,依次类推下去。如果有 n 个数进行排序,只需将 n-1 个数归位,也就是要进行 n-1 趟操作。
而 “每一趟 ” 都需要从第一位开始进行相邻的两个数的比较,将较大的数放后面,比较完毕之后向后挪一位继续比较下面两个相邻的两个数大小关系,重复此步骤,直到最后一个还没归位的数。
示例代码解读:
package com.etime09;
/**
* program : 冒泡排序
* datetime : 2022年7月21日
*/
package com.etime09;
/**
* program : 冒泡排序
* datetime : 2022年7月21日
*/
public class BubSort {
//调用方法main函数
public static void main(String[] args) {
//定义一个任意整形数组
int[] arr = {15, 88, 65, -45, 15, 98, -8};
//打印输出数组方便后面观察
for (int num : arr) {
System.out.print(num + "\t");
}
System.out.println();
//调用冒泡排序方法
BubSort b = new BubSort();
b.bubSort(arr);
for (int num : arr) {
System.out.print(num + "\t");
}
}
//简易冒泡排序方法
public void bubSort(int[] arr) {
int length = arr.length;
//第一层for遍历数组 因为比较是涉及j+1 所有循环范围 lenght-1
for (int i = 0; i < length - 1; i++) {
//第二层for 用于比较两两相邻的数组成员
for (int j = 0; j < length -i - 1; j++) {
//如果当前的数组成员j 大于 j+1 则向后移 交换位置
if (arr[j] > arr[j + 1]) {
//交换位置操作
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
}
插入排序
插入思路:
插入排序也是一种常见的排序算法,插入排序的思想是:将初始数据分为有序部分和无序部分,每一步将一个无序部分的数据插入到前面已经排好序的有序部分中,直到插完所有元素为止。
插入排序的步骤如下:
每次从无序部分中取出一个元素,与有序部分中的元素从后向前依次进行比较,并找到合适的位置,将该元素插到有序组当中。
假如有[5,2,3,9,4,7]六个元素,下面就以排序过程中的一个步骤(此时有序部分为[2,3,5,9],无序部分为[4,7],接下来要把无序部分的“4”元素插入到有序部分),来展示一下插入排序的运行过程。
首先,原始的数组元素是这样的。
其中,浅绿色代表有序部分,黄色代表无序部分。
在无序部分中挑出要插入到有序部分中的元素。
将要插入的元素与左边最近的有序部分的元素进行比较。由于4 < 9,所以9向后移,4向前移。
继续将要插入的元素与左边最近的有序部分的元素进行比较。由于4 < 5,所以5向后移,4继续向前移。
继续将4与3比较。由于4>3,所以不再向前比较,插入到当前位置。
此时有序部分,由[2,3,5,9]变成[2,3,4,5,9]。
package com.sortmethod;
import java.util.Arrays;
/**
* @Author 0401
* @Datetime 2022年7月29日
*/
public class InsertSort {
public static void main(String[] args) {
int[] arr = {2, 5, 1, 6, 9, 3, 4, 8, 9};
System.out.println(Arrays.toString(arr));
InsertSort(arr, arr.length);
System.out.println(Arrays.toString(arr));
}
static void InsertSort(int[] arr, int len) {
int tmp; //暂存定义
int i;
int j;
/**
* 第一轮循环
* 设第一个值为已排序
* 取值后一位 作为基准位
*/
for (i = 1; i < len; i++) {
//i=1 / i=2
tmp = arr[i]; //此处为基准值
/**
*int[] arr = {2, 5, 1, 6, 9, 3, 4, 8, 9};
* j = i-1; 循环为 从i分界,计较 arr[i]和其之前的值,
* 比它大的就直接赋值 给 arr[j+1] 也就是i的位置
* 循环时比较向前移 一层的i是他的界限 所以j=i-1 前一个值和i的原值(temp)作比较
*/
for (j = i - 1; j >= 0 && arr[j] > tmp; j--) {
//j = 0 / j=1
arr[j + 1] = arr[j]; //基准位 和 j交换
}
/**
*
* arr[j+1]是循环后的最后一个位置,且二层for已经不在循环
*
* 便将temp的值赋给arr[j+1]
*/
arr[j+1 ] = tmp;
System.out.println(Arrays.toString(arr));
}
}
}
快速排序
快速排序(Quick sort)是对冒泡排序的一种改进。快速排序由C. A. R. Hoare在1960年提出。
快速排序原理
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
1、首先设定一个分界值,通过该分界值将数组分成左右两部分。
2、将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
3、然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
4、重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
概括来说为 挖坑填数 + 分治法。
package com.etime09;
/**
* program : 快速排序
* datetime : 2022年7月21日
*/
public class QuickSortTest01 {
public static void main(String[] args) {
int[] arr = {15, 56, 21, -4, 58, 56, -45};
for (int num : arr) {
System.out.print(num + "\t");
}
System.out.println("-------------------");
QuickSortTest01 q = new QuickSortTest01();
q.quickSort(arr, 0, (arr.length - 1));
for (int num : arr) {
System.out.print(num + "\t");
}
System.out.println("-------------------");
}
public void quickSort(int[] arr, int left, int right) {
int a, b, t, standard;
//重点注意,打断循环
if (left >= right) {
return;
}
//取每次循环得到的起始点和结束点
a = left;
b = right;
standard = arr[left]; //记录arr[left]
while (a < b) {
//一定先从后 再从前面 b--限制这 a++ 如果不是b--先执行,a--课程超出界限
while (standard <= arr[b] && a < b) {
b--;
}
while (standard >= arr[a] && a < b) {
a++;
}
if (a < b) {
t = arr[a];
arr[a] = arr[b];
arr[b] = t;
}
}
arr[left] = arr[a];
arr[a] = standard; //在此之前就已经 记录了 arr[left]
quickSort(arr, left, a - 1);
quickSort(arr, a + 1, right);
}
}
选择排序
基本思想
简单选择排序是最简单直观的一种算法,每一趟从待排序的数据元素中选择最小(或最大)的一个元素作为首元素,直到所有元素排完为止,简单选择排序是不稳定排序。
在算法实现时,每一轮确定最小元素的时候会通过不断地比较交换来使得首位置为当前最小,交换是个比较耗时的操作。其实我们很容易发现,在还未完全确定当前最小元素之前,这些交换都是无意义的。
因此可以通过设置一个变量min,每一次比较出存储较小元素,并且记录当前元素的数组下标,当本轮循环结束之后,那这个变量min存储的就是当前最小元素的下标,此时再执行交换操作,以此确定本轮遍历的最小元素放到了数组前部。
package com.sortmethod;
import java.util.Arrays;
/**
* @Author 0401
* @Datetime 2022年7月29日
* program : Select Sort
* datetime : 2022年7月22日
*/
public class SelectSortTest01 {
public static void main(String[] args) {
int[] arr = {12, 56, -45, 15, 56, 34, 89};
System.out.println(Arrays.toString(arr));
runSort(arr);
System.out.println(Arrays.toString(arr));
}
public static void runSort(int[] arr) {
int len = arr.length;
/**
* @implNote 每循环一轮就能排序出一个最小或者最大的数
* 下一轮循环就跳过这个已知数 i++;
*/
for (int i = 0; i < len - 1; i++) {
int min = i;
/**
*
* @implNote 每循环一次就找出一个比基准值小的元素下标
* 记录元素下标 min = j
* 一轮循环后找出了数组中最小的元素
*/
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
/**
* 一轮for循环下来我们便确认了最小的数
* if判断是都是同一个元素 不是则交换位置
* 此步骤将最小或最大的值放到了我们的 i 位置上
* 及每次循环都会把首位 i 确定下来
*/
if (min != i) {
int t = arr[i];
arr[i] = arr[min];
arr[min] = t;
}
}
}
}
算法分析
简单选择排序通过上面优化之后,无论数组原始排列如何,比较次数是不变的;对于交换操作,在最好情况下也就是数组完全有序的时候,无需任何交换移动,在最差情况下,也就是数组倒序的时候,交换次数为n-1次。综合下来,时间复杂度为O(n2)
归并排序
分治法说明
归并排序的含义:
和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlog n)的时间复杂度。代价是需要额外的内存空间。归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个典型的应用。归并排序是一种稳定的排序方法。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述:
把长度为n的输入序列分成两个长度为n/2的子序列;
对这两个子序列分别采用归并排序;
将两个排序好的子序列合并成一个最终的排序序列。
代码实现:
package com.sortmethod;
import java.util.Arrays;
/**
* 归并排序 MergeSort
*
* @Author 0401
* @Date 2022年7月29日
*/
public class MergeSort {
public static void main(String[] args) {
//初始化 一个随机的数组
int[] arr = {12, 56, 32, 98, 45, -5, -6, 21, -48, 8};
System.out.println(Arrays.toString(arr)); //打印输出原数组方便后面的调试检查
System.out.println(Arrays.toString(mergeSort(arr))); //调用递归方法实现数组归并排序
}
public static int[] mergeSort(int[] arr) {
//打断递归方法
if (arr.length < 2) {
return arr;
}
/**
* 执行分治
* 将数组 通过copyOfRange分成两个部分 再将其执行排序 合并
*/
int mid = arr.length / 2;
//左边部分数组
int[] left = Arrays.copyOfRange(arr, 0, mid); //左闭右开故此mid没有被包含进去
//右边部分数组
int[] right = Arrays.copyOfRange(arr, mid, arr.length);//左闭右开 故此mid在其中 两个数组没有重复
//递归方法嵌套
return merge(mergeSort(left), mergeSort(right));
}
/**
* 该方法再递归中被重复调用 故此能够实现分治的排序任务
* 八位数组
* [9 8 7 6 -1 3 -4 6]
* [
*
* @param left 传递左部分无序数组 arr
* @param right 传递右部分无序数组 arr
* @return 返回排序好的数组
*/
public static int[] merge(int[] left, int[] right) {
//初始化temp的长度
int[] temp = new int[left.length + right.length];
for (int index = 0, i = 0, j = 0; index < temp.length; index++) {
/**
* @see index 为temp的下标 循环时自动递增直到达到数组定义长度
* @apiNote if①② 判断左边数组是都都已经放到temp数组中 true的话就将right依次放到temp中
* if③④ 判断left和right的下标值谁更大 按规则放入temp数组中
*/
if (i >= left.length) {
temp[index] = right[j++];
} else if (j >= right.length) {
temp[index] = left[i++];
} else if (left[i] >= right[j]) {
temp[index] = right[j++];
} else if (left[i] <= right[j]) {
temp[index] = left[i++];
}
}
//返回一个完整已排序数组
return temp;
}
}