一、冒泡排序
-
基本介绍
冒泡排序属于内部排序法,实现原理是重复扫描待排序的元素,并比较每一对相邻的元素,当该对元素顺序不正确时进行交换。一直重复这个过程,直到没有任何两个相邻元素可以交换,就表明完成了排序。 -
图例展示
-
代码实现
/**
* 冒泡排序(从小到大)
*/
public class BubbleSort {
public static void sort(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;
}
}
if (!flag) {
// 在一趟排序中,一次交换都没有发生过,退出循环
break;
} else {
// 重置flag,进行下次判断
flag = false;
}
}
}
}
二、选择排序
-
基本介绍
选择排序属于内部排序法,是从待排序的元素中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的。 -
图例展示
-
代码实现
/**
* 选择排序(从小到大)
**/
public class SelectSort {
public static void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
// 最小数的下标
int minIndex = i;
// 最小数的值
int minVal = arr[i];
for (int j = i + 1; j < arr.length; j++) {
// 如果发现更小的数字,保存更小数字的下标和值
if (minVal > arr[j]) {
minVal = arr[j];
minIndex = j;
}
}
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = minVal;
}
}
}
}
三、插入排序
-
基本介绍
插入式排序属于内部排序法,基本思想是:把待排序的元素看成为一个有序表和一个无序表,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。 -
图例展示
-
代码实现
/**
* 插入排序(从小到大)
*/
public class InsertSort {
public static void sort(int[] arr) {
// 定义待插入的数
int insertVal;
// 定义待插入数的下一个数的下标
int insertIndex;
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];
// 1. insertIndex >= 0 保证insertVal不越界
// 2. insertVal < arr[insertIndex] 待插入的数,还没有找到插入位置
// 3. arr[insertIndex + 1] = arr[insertIndex] 将 arr[insertIndex] 后移一位
for (insertIndex = i - 1; insertIndex >= 0 && insertVal < arr[insertIndex]; insertIndex--) {
arr[insertIndex + 1] = arr[insertIndex];// arr[insertIndex]
}
// 当退出while循环时,说明插入的位置找到,insertIndex + 1
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
}
}
}
四、希尔排序
-
基本介绍
希尔排序属于内部排序法,它是插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。希尔排序法基本思想是把待排序元素按下标的一定步长分组,对每组使用插入排序算法排序;随着增量逐渐减少,当增量减至1时,整个待排序的元素恰被分成一组,算法便终止。 -
图例展示
-
代码实现
/**
* 希尔排序(从小到大)
*/
public class ShellSort {
/**
* 希尔排序移位法
*/
public static void sort(int[] arr) {
// 定义待插入的数
int insertVal;
// 定义待插入数的下一个数的下标
int insertIndex;
// 增量gap, 并逐步的缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 从第gap个元素,逐个对其所在的组进行直接插入排序
for (int i = gap; i < arr.length; i++) {
insertVal = arr[i];
for (insertIndex = i - gap; insertIndex >= 0 && insertVal < arr[insertIndex]; insertIndex -= gap) {
arr[insertIndex + gap] = arr[insertIndex];
}
if (insertIndex + gap != i) {
arr[insertIndex + gap] = insertVal;
}
}
}
}
}
五、快速排序
-
基本介绍
快速排序属于内部排序法,它是冒泡排序的一种改进算法。基本思想是:通过一趟排序将待排序的元素分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个待排序的元素变成有序序列。 -
图例展示
-
代码实现
/**
* 快速排序(从小到大)
*/
public class QuickSort {
public static void sort(int[] arr, int low, int high) {
if (low >= high) {
return;
}
int i = low;
int j = high;
// pivot就是基准位
int pivot = arr[low];
while (i < j) {
// 右边依次往左递减
while (pivot <= arr[j] && i < j) {
j--;
}
// 左边依次往右递增
while (pivot >= arr[i] && i < j) {
i++;
}
// 满足条件则交换
if (i < j) {
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
// 最后基准为low与i的数字交换,如果相同则不交换
if(i != low) {
arr[low] = arr[i];
arr[i] = pivot;
}
// 递归调用左半数组
sort(arr, low, j - 1);
// 递归调用右半数组
sort(arr, j + 1, high);
}
}
六、归并排序
-
基本介绍
归并排序属于外部排序法,该算法采用经典的分治策略,分的阶段是将问题分解成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案"修补"在一起。 -
图例展示
-
代码实现
/**
* 归并排序(从小到大)
*/
public class MergetSort {
public static void sort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2; // 中间索引
// 向左递归进行分解
sort(arr, left, mid, temp);
// 向右递归进行分解
sort(arr, mid + 1, right, temp);
// 合并
merge(arr, left, mid, right, temp);
}
}
/**
* 合并的方法
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 做中转的数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 左边有序序列的初始索引
int j = mid + 1; // 右边有序序列的初始索引
int tempIndex = 0; // 指向temp数组的当前索引
// 把左右两边(有序)的数据按照规则填充到temp数组,直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[tempIndex++] = arr[i++];
} else {
temp[tempIndex++] = arr[j++];
}
}
// 把有剩余数据的一边的数据依次全部填充到temp
while (i <= mid) {
temp[tempIndex++] = arr[i++];
}
while (j <= right) {
temp[tempIndex++] = arr[j++];
}
// 将temp数组的元素拷贝到arr,注意:并不是每次都拷贝所有
tempIndex = 0;
int tempLeft = left;
while (tempLeft <= right) {
arr[tempLeft++] = temp[tempIndex++];
}
}
}
七、基数排序
-
基本介绍
基数排序属于外部排序法,它是通过键值的各个位的值,将要排序的元素分配至某些"桶"中,达到排序的作用。基数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。 -
图例展示
-
代码实现
/**
* 基数排序(从小到大)
*/
public class RadixSort {
public static void sort(int[] arr) {
// 得到数组中最大的数的位数
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
int maxLength = (max + "").length();
// 定义一个二维数组,表示10个桶,每个桶是一个一维数组
int[][] bucket = new int[10][arr.length];
// 定义一个一维数组来记录各个桶的数据个数
int[] bucketElementCounts = new int[10];
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
// 针对每个元素的对应位进行排序处理
for (int j = 0; j < arr.length; j++) {
int digitOfElement = arr[j] / n % 10;
bucket[digitOfElement][bucketElementCounts[digitOfElement]++] = arr[j];
}
int index = 0;
// 将桶中的数据放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
if (bucketElementCounts[k] != 0) {
for (int l = 0; l < bucketElementCounts[k]; l++) {
arr[index++] = bucket[k][l];
}
}
// 第i+1轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
}
}
}
八、排序算法总结
![](https://i-blog.csdnimg.cn/blog_migrate/589826f9555c628e8d6c7d9cc6cfe04b.png)
n:数据规模
k:桶的个数
In-place:不占用额外内存
Out-place:占用额外内存
时间复杂度: 一个算法执行所耗费的时间
空间复杂度:运行完一个程序所需内存的大小
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面
不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面
内排序:所有排序操作都在内存中完成
外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行