冒泡排序
从数组起始位置 j 开始遍历数组,每次遍历比较 j 位置与 j+1 位置的值,将较大的数值往 j+1 位置移动(arr[j] > arr[j+1],则进行交换swap(arr,j)),数组长度为 n 。
j = 0,从 0 ~ n-1 位置依次比较并交换,此时 n-1 位置为最大值;
j = 0,从 0 ~ n-2 位置依次比较并交换,此时 n-2、n-1 位置已完成排序;
依次类推,进行排序…;
j = 0,从 0 ~ 1 位置依次比较并交换,此时整个数组完成排序。
package com.example.algorithm.sort;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* 冒泡排序,时间复杂度O(N^2),空间复杂度O(1),具备稳定性
*/
@Component
public class BubbleSort {
/**
* 从j位置开始,将j和j+1进行比较,大的数往后移动
* 相邻数据相等时,不进行交换,可以保证稳定性
*
* @param arr
* @author Alan
* @date 2021/8/31 13:53
*/
public static void bubbleSort(int[] arr) {
//每次保证最后第n-1位数字最大,共需要执行的次数为n-1次
for (int i = 0; i < arr.length; i++) {
//遍历0~n-i-1位置的数字,比较j和j+1,较大的数往后移动
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j);
}
}
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int j) {
arr[j] = arr[j] ^ arr[j + 1];
arr[j + 1] = arr[j] ^ arr[j + 1];
arr[j] = arr[j] ^ arr[j + 1];
}
}
选择排序
从数组起始位置 i 开始遍历数组,每次遍历将最小的值放到 i 位置,数组长度为 n 。
i = 0,将 i 位置的值与 1 ~ n-1 位置的值依次比较,将最小值与i位置交换,此时 i 位置的值为最小值;
i = i+1,将 i 位置的值与 2 ~ n-1 位置的值依次比较,将最小值与 i+1 位置交换,此时 i、i+1 位置已完成排序;
依次类推,进行排序…;
i = n-2,将 n-2 位置的值与 n-1 位置的值比较,将最小值与 n-2 位置交换,此时整个数组完成排序。
package com.example.algorithm.sort;
import java.util.Arrays;
/**
* 选择排序,时间复杂度O(N^2),空间复杂度O(1),不具备稳定性
*/
public class SelectSort {
/**
* 从i位置开始,将最小的数放到i位置
* 不具备稳定性
*
* @param arr
* @author Alan
* @date 2021/8/31 13:52
*/
public static void selectSort(int[] arr) {
//将最小值放到i位置,一共要操作n-1次
for (int i = 0; i < arr.length; i++) {
//从i-n-1的数据中记录最小数的位置,并存放到i位置
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j;
}
}
//最小值不是本身则进行交换
if (minIndex != i) {
swap(arr, i, minIndex);
}
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int i, int minIndex) {
arr[i] = arr[i] ^ arr[minIndex];
arr[minIndex] = arr[i] ^ arr[minIndex];
arr[i] = arr[i] ^ arr[minIndex];
}
}
插入排序
从数组起始位置 i 开始,将 i+1 位置的值依次与 0 ~ i 位置的值进行比较,较小的数往前移动,保证 0 ~ i 是有序的,数组长度为 n 。
i = 0,0 位置本身就是有序的;
i = 1,将 1 位置与 0 位置进行比较,较小是数往前移动,此时 0 ~ 1 的位置是有序的;
i = 2,将 2 位置与 i-1 ~ 0 位置进行依次比较,较小的数往前移动,此时 0 ~ i 的位置是有序的;
依次类推,进行排序…;
i = n-1,将 n-1 位置与 n-2 ~ 0 位置进行依次比较,较小的数往前移动,此时 0 ~ n-1 的位置是有序的。
package com.example.algorithm.sort;
import java.util.Arrays;
/**
* 插入排序,时间复杂度O(N^2),空间复杂度O(1),具备稳定性
*/
public class InsertSort {
/**
* 往前遍历时,相邻数据相等时,不进行交换,可以保证稳定性
*
* @param arr
* @author Alan
* @date 2021/9/17 13:13
*/
public static void insertSort(int[] arr) {
//保证0~i是有序的,第0位置本身就是有序的,共需要遍历n-2次
for (int i = 0; i < arr.length - 1; i++) {
//j=i+1位置与前一个数进行比较,小的数往前移动,并且j--后继续和j-1位置数进行比较
//如果j位置数>j-1位置数,则停止比较,此时数组0-j位置是有序的
for (int j = i + 1; j > 0; j--) {
if (arr[j - 1] > arr[j]) {
swap(arr, j);
}
}
System.out.println(Arrays.toString(arr));
}
}
public static void swap(int[] arr, int j) {
arr[j - 1] = arr[j - 1] ^ arr[j];
arr[j] = arr[j - 1] ^ arr[j];
arr[j - 1] = arr[j - 1] ^ arr[j];
}
}
归并排序
使用二分法将数组划分成左右两部分,分别对左右数组进行排序处理,处理完成后合并两个数组数据。使用递归的思路按照上述方法分别对左右数组进行相同处理,就可以完成整个数组的排序。
package com.example.algorithm.sort;
import java.util.Arrays;
/**
* 递归
*/
public class Recursion {
/**
* 归并排序,时间复杂度O(N*logN),空间复杂度O(N),具备稳定性
*
* @param arr
* @author Alan
* @date 2021/9/3 13:24
*/
public static void sort(int[] arr) {
processSort(arr, 0, arr.length - 1);
}
/**
* 递归函数,二分排序
*
* @param arr
* @param l
* @param r
* @author Alan
* @date 2021/9/6 12:52
*/
private static void processSort(int[] arr, int l, int r) {
if (l == r) {
return;
}
int mid = l + ((r - l) >> 1);
processSort(arr, l, mid);
processSort(arr, mid + 1, r);
mergeSort(arr, l, mid, r);
}
/**
* 比较在1~m与m+1~r的有序数组,将较小值拷贝到help[]数组中,helpIndex++
* 数组arr[]在m或r的位置上溢出,则将未溢出的数组arr[]剩下的值拷贝到help[]数组中
* 将数组help[]拷贝回arr[]数组
* 左侧和右侧相等时,先拷贝左侧的数据,可以保证稳定性
*
* @param arr 数组
* @param l 数组起始下标位置
* @param m 二分中点位置,m表示左侧数组结束下标位置,m+1表示右侧数组开始位置
* @param r 数组结束下标位置
* @author Alan
* @date 2021/9/6 12:44
*/
private static void mergeSort(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int helpIndex = 0;
int lIndex = l;
int rIndex = m + 1;
while (lIndex <= m && rIndex <= r) {
help[helpIndex++] = arr[lIndex] <= arr[rIndex] ? arr[lIndex++] : arr[rIndex++];
}
while (lIndex <= m) {
help[helpIndex++] = arr[lIndex++];
}
while (rIndex <= r) {
help[helpIndex++] = arr[rIndex++];
}
for (int i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
System.out.println(Arrays.toString(help));
}
}
快速排序
从数组中随机选择一个参照数,与数组的最后一个位置数据进行交换。使用参照数计算左右数组的边界,并将小于等于参照数的数据依次放入左边数组,大于参照数的数据依次放入右边数组,最后将参照数放入右边数组的第一个位置。使用递归的思路按照上述方法分别对左右数组进行相同处理,就可以完成整个数组的排序。
package com.example.algorithm.sort;
/**
* 时间复杂度O(N*logN)~O(N^2)
* 空间复杂度O(logN)~O(N)
*/
public class QuickSort {
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
/**
* 快排,时间复杂度O(N*logN),空间复杂度O(logN),不具备稳定性
*
* @param arr
* @param l
* @param r
* @author Alan
* @date 2021/9/17 13:18
*/
public static void quickSort(int[] arr, int l, int r) {
//数组中随机选择一个数据,作为参照数与数组的最后一个数进行交换
swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
int[] p = partition(arr, l, r);
quickSort(arr, l, p[0]);
quickSort(arr, p[1] + 1, r);
}
/**
* 不具备稳定性
*
* @param arr
* @param l
* @param r
* @return int[] 返回以r为参照数的等于区域的边界值值数组,数组长度为2,分别表示左右边界值
* @author Alan
* @date 2021/9/17 13:15
*/
public static int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
//如果当前数小于参照数,则将当前数与小于区域指针less的下一个位置进行交换,当前数指针前移l++
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
}
//如果当前数大于参照数,则将当前数与大于区域指针more的下一个位置进行交换,当前数指针不变
else if (arr[l] > arr[r]) {
swap(arr, --more, l);
}
//如果当前数等于参照数,则当前数的位置不变,当前数指针前移l++
else {
l++;
}
}
//遍历到数组最后一位时l=r,将参照数位置r与大于参照数区域的第一个数的位置more进行交换
swap(arr, more, r);
return new int[]{less + 1, more};
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
堆排序
数组长度为 n,堆结构中 index 为当前节点,则 (index - 1) / 2 为当前节点的父节点。
构造大根堆,将数组中的数据依次放入堆中,使用 heapInsert 方法来比较并调整数组,使其成为大根堆。
新进入堆中的数据与父节点进行比较,当前节点数据大于父节点( arr[index] > arr[(index - 1) / 2] )则进行交换,交换后当前节点就来到了父节点的位置( index = (index - 1) / 2 ),此时以父节点为当前节点继续往上比较,直到当前节点小于等于父节点或当前节点来到根节点的位置。此时大根堆已经构造完成。
堆排序,使用大根堆根节点即为最大的数的特性对数组进行排序,大根堆构造完成,此时数组的 0 位置即为根节点。
将根节点 0 位置的数据和数组 n-1 位置的数据进行交换,此时数组 n-1 位置已完成排序;
对数组 0 ~ n-2 位置的数据,使用 heapify 方法调整堆,使其再次成为大根堆,将根节点数据与数组 n-2 位置进行交换,此时数组 n-2 ~ n-1 位置已完成排序;
依次类推,进行排序…;
对数组 0 ~ 1 位置的数据,使用 heapify 方法调整堆,使其再次成为大根堆,将根节点数据与数组 1 位置进行交换,此时数组 1 ~ n-1 位置已完成排序,整个数组完成排序。
堆调整,移除堆的根节点,并将堆中的最后一个节点移动到根节点,使用 heapify 方法来调整堆,使其成为大根堆。
从根节点开始,比较当前节点下左右子节点,将较大的子节点与当前节点进行比较,当前节点小于子节点则进行交换,交换后当前节点来到了子节点的位置,此时以子节点为当前节点进行往下比较,直到当前节点大于等左右子节点中较大的节点。此时堆已经重新调整为大根堆。
package com.example.algorithm.sort;
import java.util.Arrays;
import java.util.PriorityQueue;
/**
* 堆排序
*/
public class HeapSort {
/**
* PriorityQueue优先队列底层就是小根堆
* 每次扩容按照现大小的成倍进行扩容,扩容代价O(logN)
*
* @param arr 需要排序的数组
* @param k PriorityQueue队列大小
* @author Alan
* @date 2021/9/14 13:03
*/
public static void heapSort(int[] arr, int k) {
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for (; index <= Math.min(arr.length - 1, k); index++) {
heap.add(arr[index]);
}
int i = 0;
for (; index < arr.length - 1; i++, index++) {
heap.add(arr[index]);
arr[i] = heap.poll();
System.out.println(Arrays.toString(arr));
}
while (!heap.isEmpty()) {
arr[i++] = heap.poll();
System.out.println(Arrays.toString(arr));
}
}
/**
* 时间复杂度O(N*logN)额外空间复杂度O(1)
* 堆排序,不具备稳定性
*
* @param arr 需要排序的数组
* @author Alan
* @date 2021/9/14 12:42
*/
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
System.out.println(Arrays.toString(arr));
int heapSize = arr.length;
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapify(arr, 0, heapSize);
swap(arr, 0, --heapSize);
System.out.println(Arrays.toString(arr));
}
}
/**
* 构造堆O(logN)
* 新进入的数放置到最后的位置,与父节点比较,若大于父节点的数,则与父节点交换
*
* @param arr 需要构造堆的数组
* @param index 从数组起始位置开始每次+1
* @author Alan
* @date 2021/9/10 13:13
*/
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
/**
* 移除顶级节点的数后,重新构造堆O(logN)
* 将最后一个数放到顶级节点位置,比较父节点下的两个孩子,将较大的数与父节点进行比较,若大于父节点的数,则与父节点交换
*
* @param arr 堆数组
* @param index 父节点开始位置
* @param heapSize 堆大小
* @author Alan
* @date 2021/9/10 13:26
*/
public static void heapify(int[] arr, int index, int heapSize) {
//左孩子下标
int left = index * 2 + 1;
//是否有孩子
while (left < heapSize) {
//记录较大数字的下标位置
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//和父节点比较
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
public static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
桶排序
根据当前需要排序数据的进制来设定桶的数量,桶按照单位进制数的最小值~最大值来编号。例如:10进制数,则准备 0 ~ 9 编号的桶。
个位数:取出数组中所有数据的个位数,并将其放入对应数组编号的桶中,按照桶编号顺序从桶中取出数据放回原数组,一个桶中有多个数据的按照先进先出的顺序取出。
十位数:取出数组中所有数据的十位数,并将其放入对应数组编号的桶中,按照桶编号顺序从桶中取出数据放回原数组,一个桶中有多个数据的按照先进先出的顺序取出。
依次类推,进行排序,当数组中的最大数据的最高位被取出处理后,整个数组完成排序。
package com.example.algorithm.sort;
/**
* 桶排序
*/
public class RadixSort {
public static void radixSort(int[] arr) {
radixSort(arr, 0, arr.length - 1, maxBits(arr));
}
/**
* 数组中最大数字的10进制位位数
*
* @param arr
* @return int
* @author Alan
* @date 2021/9/17 12:59
*/
public static int maxBits(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
if (max != 0) {
res++;
max /= 10;
}
return res;
}
/**
* 桶排序,具备稳定性
*
* @param arr 数组
* @param l 需要排序的起始位置下标
* @param r 需要排序的结束位置下标
* @param digit 数组中最大数的10进制位数
* @author Alan
* @date 2021/9/17 13:00
*/
public static void radixSort(int[] arr, int l, int r, int digit) {
//10进制数准备[0~9]空间的数组
final int radix = 10;
//数组数据位置
int i = 0;
//数组数据当前位数字的位置=当前位数字的值
int j = 0;
int[] bucket = new int[r - l + 1];
for (int d = 0; d < digit; d++) {
//d位数数字计数器
//10进制数准备[0~9]空间的数组
int[] count = new int[radix];
//遍历范围内的数组数据,计算d位置的数字在数组count中对应位置的数量
for (i = l; i <= r; i++) {
j = getDigit(arr[i], d);
count[j]++;
}
//数据分片,计算数值<i位置的数字在数组中的个数
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
//先进先出,则最后一个数的存放位置为bucket[count[j]-1]
for (i = r; i >= l; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
//将调整好后的数组拷贝回原数组
for (i = l, j = 0; i <= r; i++, j++) {
arr[i] = bucket[j];
}
}
}
//求一个数d位的数字
public static int getDigit(int currentNumber, int d) {
return (currentNumber / (int) Math.pow(10, d - 1)) % 10;
}
}