插入排序
代码
import java.util.Arrays;
/**
* @ClassName InsertSort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/26/11:07
*/
// 时间复杂度O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序(如果排序数组中有两个元素的值是相等的,如果排序后两个相等的元素的相对顺序是一样的,这样的排序称为稳定排序。否则就是不稳定的。)
public class InsertSort {
// 升序排序
public static void insertSort(int[] array) {
// 通过 bound 来划分两个区间
// 有序区间 :[0,bound)
// 无序区间 :[bound,size)
for (int bound = 1; bound < array.length; bound++) {
// 处理 bound 位置元素,将bound 元素插入前边的有序区间
int tmp = array[bound];
// 从后往前遍历,插入到合适位置
int cur = bound - 1;
for (; cur >= 0; cur--) {
// 如果这里写作大于等于就会变成不稳定排序
if (array[cur] > tmp) {
array[cur + 1] = array[cur];
} else {
// 说明已经找到合适位置
break;
}
}
array[cur + 1] = tmp;
}
}
public static void main(String[] args) {
int[] array = {12, 34, 45, 56, 5, 7, 686, 34};
insertSort(array);
System.out.println(Arrays.toString(array));
}
}
性能分析
// 时间复杂度:O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序(如果排序数组中有两个元素的值是相等的,如果排序后两个相等的元素的相对顺序是一样的,这样的排序称为稳定排序。否则就是不稳定的。)
特点
- 当待排序区间元素比较少的时候,排序效率很高。
- 当整个数组比较接近有序时,排序效率也非常高。
优化(优化查找)
希尔排序
希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1时,所有记录在统一组内排好序。
- 希尔排序是对直接插入排序的优化。
- 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap ==1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
gap = 3 同组相邻元素下标之差也就是3
针对每组元素分别插排后,虽然未能彻底完成最终排序,但是有序性整体提高了。
gap = 2
重复进行插排,此时仍然未能完成排序,但是有序性得到了进一步提高。
gap = 1
最后一次插排,完成排序。
代码
import java.util.Arrays;
/**
* @ClassName ShellSort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/26/12:04
*/
// 时间复杂度:极限形况;O(N^1.3),按照size,size/2 ... 设定 gap 时间复杂度为:O(N^2)
// 空间复杂度:O(1)
// 稳定性; 不稳定(分组会打乱相对顺序)
public class ShellSort {
public static void main(String[] args) {
int[] array = {12, 23, 45, 576, 23, 12356, 34};
shellSort(array);
System.out.println(Arrays.toString(array));
}
public static void shellSort(int[] array) {
int gap = array.length / 2;
while (gap > 1) {
// 循环进入分组插入排序
insertSortGap(array, gap);
gap = gap / 2;
}
insertSortGap(array, 1);
}
private static void insertSortGap(int[] array, int gap) {
// 通过 bound 来划分两个区间
// 有序区间 :[0,bound)
// 无序区间 :[bound,size)
for (int bound = gap; bound < array.length; bound++) {
// 处理 bound 位置元素,将bound 元素插入前边的有序区间
int tmp = array[bound];
// 从后往前遍历,插入到合适位置
int cur = bound - gap;// 找到同组中的上一个元素
for (; cur >= 0; cur -= gap) {
// 如果这里写作大于等于就会变成不稳定排序
if (array[cur] > tmp) {
array[cur + gap] = array[cur];
} else {
// 说明已经找到合适位置
break;
}
}
array[cur + gap] = tmp;
}
}
}
性能
// 时间复杂度:极限形况;O(N^1.3),按照size,size/2 … 设定 gap 时间复杂度为:O(N^2)
// 空间复杂度:O(1)
// 稳定性; 不稳定(分组会打乱相对顺序)
选择排序
基于打擂台的思想,每次从数组中找出最小值,然后把最小值放到合适的位置上。
代码
import java.util.Arrays;
/**
* @ClassName SelectSort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/26/16:55
*/
// 时间复杂度:O(N^2)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
public class SelectSort {
public static void main(String[] args) {
int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
selectSort(array);
System.out.println(Arrays.toString(array));
}
public static void selectSort(int[] array) {
for (int bound = 0; bound < array.length; bound++) {
// bound 位置作为擂台,将后边区间进行比较
// 如果打擂成功,进行交换
for (int i = bound + 1; i < array.length; i++) {
if (array[i] < array[bound]) {
// 打擂成功
int temp = array[bound];
array[bound] = array[i];
array[i] = temp;
}
}
}
}
}
性能
// 时间复杂度:O(N^2)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
堆排序
升序排序:
- 把数组建立一个小堆,取出最小值放到另一个数组中,循环取堆顶元素尾插到新数组即可(缺陷是需要额外的O(N)的空间)
- 把数组建立一个大堆,把对堆顶元素和堆的最后一个元素互换,把最后一个元素删除,再从堆顶来向下调整。
代码
import java.util.Arrays;
/**
* @ClassName HeapSort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/26/17:24
*/
// 时间复杂度:O(N) + O(N*logN)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
public class HeapSort {
public static void main(String[] args) {
int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
heapSort(array);
System.out.println(Arrays.toString(array));
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
public static void heapSort(int[] array) {
// 先建立堆
createHeap(array);
// 循环交换堆顶元素,并进行调整
// 当只剩下一个元素就一定是有序的了
for (int i = 0; i < array.length - 1; i++) {
// 交换堆顶元素和堆的最后一个元素
// 循环一次堆的元素就会减少一个
// 最后一个元素的下标就是 array.length - 1 - i
swap(array, 0, array.length - 1 - i);
// 交换完之后,堆的长度就会变短
// [0,array.length - i -1) 为待排序区间
shiftDown(array, array.length - i - 1, 0);
}
}
//O(N*logN)
private static void shiftDown(int[] array, int heapLength, int index) {
// 升序排序,建立的是大堆,大堆就需要找出左右子树的最大值,在和根节点比较
int parent = index;
int child = 2 * parent + 1;
while (child < heapLength) {
if (child + 1 < heapLength && array[child + 1] > array[child]) {
child = child + 1;
}
// 条件结束意味着, child 就已经是左右子树比较大的值的下标
if (array[child] > array[parent]) {
swap(array, child, parent);
} else {
break;
}
parent = child;
child = parent * 2 + 1;
}
}
private static void createHeap(int[] array) {
// 从最后一个非叶子节点出发向前循环,依次进行向下调整
// (array.length - 1 - 1) / 2 得到最后一个节点的父节点
for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
shiftDown(array, array.length, i);
}
}
}
性能
// 时间复杂度:O(N) + O(N*logN)
// 空间复杂度:O(1)
// 稳定性:不稳定排序
冒泡排序
代码
import java.util.Arrays;
/**
* @ClassName Bubblesort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/26/18:15
*/
// 时间复杂度: O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序
public class Bubblesort {
public static void main(String[] args) {
int[] array = {1, 1, 2, 3, 4, 5, 5, 3, 4, 5, 6, 87653, 24, 23};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
public static void bubbleSort(int[] array) {
for (int i = 0; i < array.length; i++) {
boolean flag = true;
for (int j = 0; j < array.length - 1 - i; j++) {
if (array[j] > array[j + 1]) {
flag = false;
int tmp = array[j];
array[j] = array[j + 1];
array[j + 1] = tmp;
}
}
if (flag) {
break;
}
}
}
}
性能
// 时间复杂度: O(N^2)
// 空间复杂度;O(1)
// 稳定性:稳定排序
快速排序
- 待排序区间中找到一个基准值(常见的可以取区间的第一个元素或者最后一个元素)
- 以基准值为中心,把整个区间整理成三个部分:左侧部分的元素都小于等于基准值,右侧区间都大于等于基准值。
- 在针对左侧整理好的区间和右侧整理好的区间,进一步的进行递归,重复之前的整理过程。
一次整理过程:
- 取最右侧元素为基准值
- 从左往右找到一个大于基准值的元素(left)
- 从右往左找到一个小于基准值的元素(right)
- 交换 left 和 right 位置的元素
- 循环之前的操作,直到 left 和 right 重合
若以左侧元素作为基准值,则需要先从右往左找,再从左往右找;否则会破坏快排要求
- 如果是先从左往右找,在从右往左找,left 和 right 重合位置的元素,一定大于等于基准值
- 如果是先从右往左找,在从左往右找,left 和 right 重合位置的元素,一定小于等于基准值
递归代码
import java.util.Arrays;
/**
* @ClassName QuickSort2
* @Description :TODO
* @Author Josvin
* @Date 2021/03/27/11:29
*/
// 时间复杂度:O(N*logN)
// 空间复杂度:O(logN)
// 稳定性:不稳定
public class QuickSort2 {
public static void main(String[] args) {
int[] array = {12, 23, 45, 576, 23, 12356, 34};
quickSort(array);
System.out.println(Arrays.toString(array));
}
public static void quickSort(int[] array) {
// 辅助完成递归过程
// 全闭区间
quickSortHelper(array, 0, array.length - 1);
}
private static void quickSortHelper(int[] array, int left, int right) {
if (left >= right) {
// 区间中有一个或者0 个元素时,此时就不需要排序了
return;
}
// 针对 【left ,right】 区间进行整理
// index 返回值就是整理完毕后,left 和 right 的重合位置,在进行下一步递归
int index = partition(array, left, right);
quickSortHelper(array, left, index - 1);
quickSortHelper(array, index + 1, right);
}
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
// 取最右侧元素为基准值
int base = array[right];
while (i < j) {
// 从左往右找到比基准值大的元素
while (i < j && array[i] <= base) {
i++;
}
// 当上面的循环结束时,i 和 j 要莫重合,要莫 i 就指向一个大于 base 的值
// 从右往左找比基准值小的元素
while (i < j && array[j] >= base) {
j--;
}
// 当上面的循环结束时,i 和 j 要莫重合,要莫 j 就指向一个小于 base 的值
// 交换 i 和 j 的值
swap(array, i, j);
}
// 当 i 和 j 重合的时候,最后一步要把重合位置的元素与基准值进行交换
//
swap(array, i, right);
return i;
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
非递归代码
import java.util.Arrays;
import java.util.Stack;
/**
* @ClassName QuickSort3
* @Description :TODO
* @Author Josvin
* @Date 2021/03/27/14:45
*/
public class QuickSort3 {
public static void main(String[] args) {
int[] array = {12, 23, 45, 576, 23, 12356, 34};
quickSortByLoop(array);
System.out.println(Arrays.toString(array));
}
public static void quickSortByLoop(int[] array) {
// 借助栈,模拟实现递归过程
// stack 用来存放数组下标,通过下标来表示接下来要处理的区间是啥
Stack<Integer> stack = new Stack<>();
// 初始情况下,先把右侧边界下标入栈,再把左侧边界下标入栈,左右边界仍然构成前闭区间后闭区间
stack.push(array.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
// 这个取元素的顺序要和push 的顺序正好相反
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
// 区间只有一个或者没有元素,就不需要进行整理
continue;
}
// 通过 partition 把区间整理成以基准值为中心,左侧小于等于基准值,右侧大于等于基准值
//
int index = partition(array, left, right);
// 准备处理下一个区间
// 【index+1,right】 基准值右侧区间
stack.push(right);
stack.push(index + 1);
// 【left, index-1】 基准值左侧区间
stack.push(index - 1);
stack.push(left);
//
}
}
private static int partition(int[] array, int left, int right) {
int i = left;
int j = right;
// 取最右侧元素为基准值
int base = array[right];
while (i < j) {
// 从左往右找到比基准值大的元素
while (i < j && array[i] <= base) {
i++;
}
// 当上面的循环结束时,i 和 j 要莫重合,要莫 i 就指向一个大于 base 的值
// 从右往左找比基准值小的元素
while (i < j && array[j] >= base) {
j--;
}
// 当上面的循环结束时,i 和 j 要莫重合,要莫 j 就指向一个小于 base 的值
// 交换 i 和 j 的值
swap(array, i, j);
}
// 当 i 和 j 重合的时候,最后一步要把重合位置的元素与基准值进行交换
//
swap(array, i, right);
return i;
}
private static void swap(int[] array, int i, int j) {
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
优化
- 优化基准值的取法:三个元素进行取中间值。(最左侧元素、中间位置元素、最右侧元素,取中间值作为基准值,把确认的基准值 swap 到数组末尾或者开头,为后边的整理做铺垫)
- 当区间已经比较小的时候,再去进行递归,其效率就会下降,应不再进行递归,而是直接进行插入排序
- 如果区间特别的大,递归的深度也会比较深。当递归深度达到一定程度的时候,把区间的排序使用堆排序来进行优化
性能分析
// 时间复杂度:O(N*logN)
// 空间复杂度:O(logN)
// 稳定性:不稳定
归并排序
可以适用于外部排序(数组存在磁盘上),也可以适用于链表排序。
递归代码
import java.util.Arrays;
/**
* @ClassName MergeSort
* @Description :TODO
* @Author Josvin
* @Date 2021/03/27/17:20
*/
// 时间复杂度;O(N*logN)
// 空间复杂度;O(N) + O(logN)
// 稳定性;稳定
public class MergeSort {
// [low,mid) 有序区间
// [mid,high) 有序区间
// 把这两个有序区间合并为一个有序区间
public static void merge(int[] array, int low, int mid, int high) {
int[] output = new int[high - low];
int outputIndex = 0; //记录当前 output 数组中被放入了多少个元素
int cur1 = low;
int cur2 = mid;
while (cur1 < mid && cur2 < high) {
// 这里写作 <= 才能保证稳定性
if (array[cur1] <= array[cur2]) {
output[outputIndex] = array[cur1];
outputIndex++;
cur1++;
} else {
output[outputIndex] = array[cur2];
outputIndex++;
cur2++;
}
}
// 当循环结束,有一个先到达,一个后到达
// 后边的直接加入
while (cur1 < mid) {
output[outputIndex] = array[cur1];
outputIndex++;
cur1++;
}
while (cur2 < high) {
output[outputIndex] = array[cur2];
outputIndex++;
cur2++;
}
// 把 output 中的元素搬运回去
for (int i = 0; i < high - low; i++) {
array[low + i] = output[i];
}
}
public static void mergeSort(int[] array) {
mergeSortHelper(array, 0, array.length);
}
// [low,high) 两者差值小于等于1 区间就只有一个或者0个元素
private static void mergeSortHelper(int[] array, int low, int high) {
if (high - low <= 1) {
return;
}
int mid = (low + high) / 2;
//这个方法执行完就认为low ,mid 已经排序ok
mergeSortHelper(array, low, mid);
//这个方法执行完就认为mid ,high 已经排序ok
mergeSortHelper(array, mid, high);
// 当把左右区间已经归并完了,说明在左右区间已经是有序的了
/// 接下来就可以针对两个区间进行合并了
merge(array, low, mid, high);
}
public static void main(String[] args) {
int[] array = {12, 23, 45, 576, 23, 12356, 34};
mergeSort(array);
System.out.println(Arrays.toString(array));
}
}
非递归代码
public static void mergeSortByLoop(int[] array) {
// 引入一个 gap 变量进行分组
// 当gap 为 1 的时候,[0][1]进行合并,[2][3]进行合并, [4][5]进行合并...
// 当gap 为 2 的时候,[0,1] 和 [2,3]进行合并,[4,5]和 [6,7]进行合并...
// 当gap 为 4 的时候,[0,1,2,3]和 [4,5,6,7]进行合并...
for (int gap = 1; gap < array.length; gap *= 2) {
// 接下来进行具体的分组合并
// 下边的循环执行一次,就完成了一次相邻两个组的合并
for (int i = 0; i < array.length; i += 2 * gap) {
// 当前相邻组
// [beg,mid)
// [mid,end)
int beg = i;
int mid = i + gap;
int end = i + 2 * gap;
// 防止下标越界
if (mid > array.length) {
mid = array.length;
}
if (end > array.length) {
end = array.length;
}
merge(array, beg, mid, end);
}
}
}
性能分析
// 时间复杂度;O(N*logN)
// 空间复杂度;O(N) + O(logN)
// 稳定性;稳定