重点
- 面试掌握冒泡排序、快速排序、堆排序、归并排序的代码实现与平均时间复杂度即可。
- shell排序是直接插入的优化,堆排序是直接选择的优化,快速排序是冒泡排序的优化。
排序时间复杂度
参考网站 https://zhuanlan.zhihu.com/p/34982598
类别 | 排序方法 | 平均时间复杂度 | 最好时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|---|
插入排序 | 直接插入 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 |
插入排序 | shell排序 | O ( n 1.3 ) O(n^{1.3}) O(n1.3) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 |
选择排序 | 直接选择 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 |
选择排序 | 堆排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( 1 ) O(1) O(1) | 不稳定 |
交换排序 | 冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 稳定 |
交换排序 | 快速排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | 不稳定 |
归并排序 | 归并排序 | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_2n) O(nlog2n) | O ( 1 ) O(1) O(1) | 稳定 |
基数排序 | 基数排序 | O ( k ∗ n ) O(k*n) O(k∗n) | O ( k ∗ n ) O(k*n) O(k∗n) | O ( k ∗ n ) O(k*n) O(k∗n) | O ( k + n ) O(k+n) O(k+n) | 稳定 |
各类排序算法
插入排序
从前向后遍历数组,将后面的数插入前面合适的位置
// 从小向大排序示例
public static void insertSort(int[] array) {
int temp;
//从第一个数开始向后遍历,将后面的数插到前面已经排序好的数里面
for (int i = 1; i < array.length; i++) {
// 记录当前位置数字
temp = array[i];
//找到前面的一个数,使它比当前数字小
while (i >= 1 && array[i - 1] > temp) {
// 如果不满足,被比较的数字向右移一位
array[i] = array[i - 1];
//不断向左,直到找到或最左
i--;
}
//退出了循环说明找到了合适的位置了,将当前数据插入合适的位置中
array[i] = temp;
}
}
shell 排序
参考网站 https://www.cnblogs.com/chengxiao/p/6104371.html
这个又叫希尔排序,实现原理上就是分组排序的插入排序
// 把数组元素分成若干组,最开始每组2个数,然后4个...最后整个数组有序
public static void shellSort(int[] arrays) {
//增量每次都/2
for (int step = arrays.length / 2; step > 0; step /= 2) {
//从增量那组开始进行插入排序,直至完毕
for (int i = step; i < arrays.length; i++) {
int j = i;
int temp = arrays[j];
// j - step 就是代表与它同组相邻的元素
while (j - step >= 0 && arrays[j - step] > temp) {
arrays[j] = arrays[j - step];
j = j - step;
}
arrays[j] = temp;
}
}
}
直接选择排序
从未排序的数中找到合适的数,放在排序的数后
public static void chooseSort(int[] arr) {
// 依次去找第一个数至倒数第二个数
for (int i = 0; i < arr.length - 1; ++i) {
// 将i看做当前位置,并将剩余数字中最小值的下标初始化为最小值的下标
int min = i;
// 找到未排序数字中最小的那个数,并记录下标
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j;
}
}
// 交换当前位置的数与最小位置的数
if (min != i) {
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
堆排序
堆排序是选择排序的加强版。在从小到大排序中,它将数组中映射为一个最大堆,数组末尾的数字自然是最大的。然后将最大的数字排除,重新建堆,最后完成排序。
// 向堆中插入值,建立最大堆
private static void heapify(int[] array, int currentRootNode, int size) {
if (currentRootNode < size) {
//左子树和右子树的位置
int left = 2 * currentRootNode + 1;
int right = 2 * currentRootNode + 2;
//把当前父节点位置看成是最大的
int max = currentRootNode;
if (left < size) {
//如果比当前根元素要大,记录它的位置
if (array[max] < array[left]) {
max = left;
}
}
if (right < size) {
//如果比当前根元素要大,记录它的位置
if (array[max] < array[right]) {
max = right;
}
}
//如果最大的不是根元素位置,那么就交换
if (max != currentRootNode) {
int temp = array[max];
array[max] = array[currentRootNode];
array[currentRootNode] = temp;
//继续比较,直到完成一次建堆
heapify(array, max, size);
}
}
}
// 把数组建成一个堆
public static void maxHeapify(int[] array, int size) {
// 从数组的尾部开始,直到第一个元素(角标为0)
for (int i = size - 1; i >= 0; i--) {
heapify(array, i, size);
}
}
//堆排序
public static void heapSort(int[] array) {
for (int i = 0; i < array.length; i++) {
//每次建堆就可以排除一个元素了
maxHeapify(array, array.length - i);
//交换
int temp = array[0];
array[0] = array[(array.length - 1) - i];
array[(array.length - 1) - i] = temp;
}
}
冒泡排序
对每个数字进行两两交换,一次冒泡将一个数字归位。
// 冒泡排序 https://www.cnblogs.com/jyroy/p/11248691.html
// 一般能写出标志位过面试就没问题
public static void bubbleSort(int[] array) {
boolean isChanged = false;
for (int i = 0; i < array.length - 1; i++) {
isChanged = false;
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j] > array[j + 1]) {
isChanged = true;
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
// 如果当前数字没有改变过位置,说明所有数字都已经有序了
if (!isChanged) {
return;
}
}
}
快速排序
快速排序使用分治,一次分治下来小的在左边,大的在右边。递归重复这个过程,使整个数组有序。
// 默认使用最左边的数作为中间点
// 一次分治后,比中间点小的在左边,大的在右边
private static int partition(int[] array, int low, int high) {
//固定的切分方式
int key = array[low];
while (low < high) {
// 注意两个while中判断key大小关系时不能全加上等于条件,否则当开头和结尾都是最值的时候会出错
while (array[high] > key && high > low) {//从后半部分向前扫描
high--;
}
if(low < high){
array[low] = array[high];
}
while (array[low] <= key && high > low) {//从前半部分向后扫描
low++;
}
if(low < high){
array[high] = array[low];
}
}
array[high] = key;
return high;
}
// 递归重复分治过程,使范围内每个数有序
public static void quickSort(int[] array, int low, int high) {
if (low >= high || array == null) {
return;
}
int index = partition(array, low, high);
quickSort(array, low, index - 1);
quickSort(array, index + 1, high);
}
//快速排序
public static void quickSort(int[] array) {
quickSort(array, 0, array.length - 1);
}
归并排序
先将数组拆成最小长度为1的小数组,然后小数组合并为长度为2的数组,长度为2的数组合并为成长度为4的数组…最后整个数组完整排序。
private static void merge(int[] array, int left, int right, int mid, int[] temp) {
int i = left;//左序列指针
int j = mid + 1;//右序列指针
int t = 0;//临时数组指针
while (i <= mid && j <= right) {
if (array[i] <= array[j]) {
temp[t++] = array[i++];
} else {
temp[t++] = array[j++];
}
}
while (i <= mid) { //将左边剩余元素填充进temp中
temp[t++] = array[i++];
}
while (j <= right) { //将右边剩余元素填充进temp中
temp[t++] = array[j++];
}
t = 0;
while (left <= right) {
array[left++] = temp[t++];
}
}
public static void mergeSort(int[] array, int left, int right, int[] temp) {
if (left >= right || array == null) {
return;
}
int mid = (left + right) / 2;
mergeSort(array, left, mid, temp);
mergeSort(array, mid + 1, right, temp);
merge(array, left, right, mid, temp);
}
//归并排序
public static void mergeSort(int[] array) {
//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int[] temp = new int[array.length];
//递归版本 调用上面的重载方法
// mergeSort(array,0,array.length-1,temp);
//非递归版本
int len = 1;
while (len <= array.length) {
for (int i = 0; i + len <= array.length; i += len * 2) {
int left = i, mid = i + len - 1, right = i + 2 * len - 1;
if (right > array.length - 1) {
right = array.length - 1;
}
merge(array, left, right, mid, temp);
}
len = len * 2;
}
}
基数排序
从个位开始向桶子里面分配,然后按个位从小到大向数组里面填数。接着是十位,十位同样下放到桶子后按十位大小从小到大向数组中填数,同时覆盖原有个位的结果。然后是百位、千位…重复这个过程直到最大数字的最高位也下放到桶子里面。这次回收后数组排序完成。
// 基数排序 参考网站 https://segmentfault.com/a/1190000013986116
public static void radixSort(int[] arrays) {
// 找到数组中的最大值
int max = arrays[0];
for (int i = 1; i < arrays.length; ++i) {
max = Math.max(arrays[i], max);
}
// 需要遍历的次数由数组最大值的位数来决定
for (int i = 1; max / i > 0; i = i * 10) {
int[][] buckets = new int[arrays.length][10];
//获取每一位数字(个、十、百、千位...分配到桶子里)
for (int j = 0; j < arrays.length; j++) {
int num = (arrays[j] / i) % 10;
//将其放入桶子里
buckets[j][num] = arrays[j];
}
//回收桶子里的元素
int k = 0;
//有10个桶子
for (int j = 0; j < 10; j++) {
//对每个桶子里的元素进行回收
for (int l = 0; l < arrays.length; l++) {
//如果桶子里面有元素就回收(数据初始化会为0)
if (buckets[l][j] != 0) {
arrays[k++] = buckets[l][j];
}
}
}
}
}