排序算法是最基础的算法知识,也是面试笔试中必考的问题!!!!
-
笔试题中主要是各个算法的复杂度,稳定性,所属类型和模拟几次实现结果之类的问题
通过本文的两张总结图和10张算法动态图基本都可以迎刃而解 -
面试题中当然就是手撕代码了,自己实现一个排序方法,如果没有较好的准备,很容易就蒙圈。虽然大家在数据结构课程都学过大部分的排序算法了,但是知道原理和手写出来还是有很大区别的。
现在我就将这十种排序算法的Java代码实现也记录在这里,方便自己复习,也方便同样在准备找工作的同学
本文图片大多数都转自这里,感谢这个大佬的图,牛批!! 如果觉得我的总结的不是很清楚可以去看看大佬的思路
下面所有的代码我都经过100000次随机数据的测试了,如果有问题请私聊我及时更正。
快速到达看这里-->
各类排序算法的对比与分类
分类记忆
一般手撕代码都会是O(N*logN)
的算法,所以一定要能熟练的手写快速排序,归并排序和堆排序
- 初级排序(O(N*2)
- 选择排序:每次找到最小值,然后放到待排序数组的起始位置
- 插入排序:从前到后逐步建立有序序列,对于未排序数据,在已排序序列中从后往前扫描,找到对应位置并插入
- 冒泡排序:嵌套循环,每次查看相邻的元素,如果逆序,则交换
- 高级排序***(O(N*logN))
- 快速排序:数组取标杆pivot,将元素小的放在pivot左边,大元素放右边,然后依次对左边和右边的子数组继续快速排序,达到整体有序
- 归并排序:
- 把长度为n的输入序列分为两个长度为n/2的子序列
- 对两个子序列分别采用归并排序
- 将两个排序好的子序列合并成为一个最终序列
- 堆排序:
- 数组元素一次建立小顶堆
- 一次取堆顶元素,并删除
- 特殊排序(O(N)) 针对整数
- 计数排序(数的范围有限)
- 统计每个数出现的次数
- 顺序存入数组
- 桶排序
- 每一段数据是一个桶,桶内加入时进行排序
- 把所有的桶有序拼接,达到整体有序
- 基数排序
- 从最低位开始排序
- 排序好之后再递归调用上一位进行排序
- 最高位有序后达到最终有序
- 计数排序(数的范围有限)
选择排序
单向选择
i 记录当前有序位
j 记录遍历数组指针
minIndex 记录每次遍历的最小数的下标
每次遍历结束交换 下标为i 和下标为 minIndex 的值
/**
* 选择排序1
* 每次遍历查找剩余元素的最小值然后放到对应的位置
*
* @param arr
*/
public static void SelectionSort1(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
int minIndex = i;
for (int j = i; j < n; j++) {
if (arr[j] < arr[minIndex])
minIndex = j;
}
swap(arr, i, minIndex);
}
}
双向选择
left 记录左侧有序位(从小到大)
right 记录右侧有序位 (从大到小)
类似于单向选择从大到小的排序,双向选择是同时进行从小到大和从大到小的排序
每次遍历获取最大值和最小值并调整到正确位置
/**
* 选择排序2
* 每次遍历查找剩余元素的最小值和最大值然后放到对应的位置
*
* @param arr
*/
public static void SelectionSort2(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int minIndex = left;
int maxIndex = right;
//每次查找,保证arr[minIndex] <= arr[maxIndex]
if (arr[minIndex] > arr[maxIndex]) {
swap(arr, minIndex, maxIndex);
}
//从left+1到right-1遍历,找出最大最小
for (int i = left + 1; i < right; i++) {
if (arr[i] < arr[minIndex])
minIndex = i;
else if (arr[i] > arr[maxIndex])
maxIndex = i;
}
swap(arr, left, minIndex);
swap(arr, right, maxIndex);
left++;
right--;
}
}
插入排序
依次交换
从i位置向前依次进行比较,如果比本身大,就交换一下,直到正确位置
/**
* 插入排序1
* 和前一个一次交换,直到正确位置
*
* @param arr
*/
public static void InsertionSort1(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
//寻找arr[i]适合的地方插入
for (int j = i; j > 0 && arr[j] < arr[j - 1]; j--) {
swap(arr, j, j - 1);
}
}
}
依次覆盖
先暂存本身的值
从 i 位置开始向前遍历,如果arr[j-1[>arr[j] 就让arr[j-1[覆盖arr[j[的值,直到找到正确位置,将本身的值填进去
/**
* 插入排序2
* 暂存插入的数据
* 将前一个元素循环赋值给后一个元素,当发现正确位置后,将暂存数据放到对应位置
*
* @param arr
*/
public static void InsertionSort2(int[] arr) {
int n = arr.length;
for (int i = 0; i < n; i++) {
int e = arr[i];
int j = i;
for (; j > 0 && e < arr[j - 1]; j--) {
arr[j] = arr[j - 1];
}
arr[j] = e;
}
}
冒泡排序
常规冒泡
从第一个数开始遍历,发现大于后面的数就交换,交换之后继续比较,直到有序
/**
* 冒泡排序1
* 从头到尾全部遍历了
*
* @param arr
*/
public static void BubbleSort1(int[] arr) {
int n = arr.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
优化冒泡
从第一个数开始遍历,发现大于后面的数就交换,交换之后继续比较
每次交换都记录下标记位,最后一次更新的标记位后都是有序的了,外层遍历到标记位就退出,减少了遍历次数,如果是基本有序的,最好情况可升级至O(N)
/**
* 冒泡排序2
* 如果后面m个有序,记录位置,每次遍历到有序位就跳出来
*
* @param arr
*/
public static void BubbleSort2(int[] arr) {
int n = arr.length;
int new_n;
while (n > 0) {
new_n = 0;
for (int i = 1; i < n; i++) {
if (arr[i - 1] > arr[i]) {
swap(arr, i, i - 1);
new_n = i;//每次交换记录交换位置,最后一次交换位置之后都是有序的了
}
}
n = new_n;
}
}
希尔排序
/**
* 希尔排序
* @param arr
*/
public static void ShellSort(int[] arr) {
int n = arr.length;
//增量
int gap = 1;
while (gap < n / 3) {
gap = gap * 3 + 1;
}
while (gap >= 1) {
for (int i = gap; i < n; i++) {
//使用插入排序
int e = arr[i];
int j = i;
for (; j >= gap && e < arr[j - gap]; j -= gap) {
arr[j] = arr[j - 1];
}
arr[j] = e;
}
gap /= 3;
}
}
归并排序
/**
* 归并排序递归方法体
*
* @param arr
* @param l
* @param mid
* @param r 将arr[l...mid]和arr[mid+1...r]两部分进行归并
*/
private static void merge(int[] arr, int l, int mid, int r) {
int[] tmp = new int[r - l + 1];//中间数组
int i = l;//前半段下标
int j = mid + 1;</