【算法 Java】排序
排序
所有的排序以从小到大排序为例
模板题:牛客-简单的排序
排序算法的稳定性体现在相同数值元素的相对位置会不会随排序改变,如果改变则不稳定,如果不改变则稳定
冒泡排序
比较相邻的元素。如果第一个比第二个大,就交换他们两个。越大的元素会经由交换慢慢"浮"到数列的末端。
时间复杂度:O(n^2)
- 稳定的排序算法
BubbleSort.java
import java.util.Scanner;
public class BubbleSort {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int len = sc.nextInt();
int[] arr = new int[len];
for (int i = 0; i < len; ++ i ) {
arr[i] = sc.nextInt();
}
bubbleSort(arr, len);
for (int i = 0; i < len; ++ i ) {
System.out.print(arr[i]);
System.out.print(i == len - 1 ? '\n' : ' ');
}
}
/**
* 冒泡排序
* @param a:待排序的数组
* @param len:数组中有效数值的个数
*/
public static void bubbleSort(int[] a, int len) {
for(int i = 0; i < len - 1; ++ i ) { // 比较轮数:len-1;每轮比较最大的值会冒泡到数列最后,因此每轮比较次数是递减的
for(int j = 0; j < len - 1 - i; ++ j ) { // 每轮比较次数:len-1-i
if(a[j] > a[j + 1]) {
swap(a, j, j + 1);
}
}
}
}
/**
* 交换数值:交换a[i]和a[j]的数值
* 一定要注意判断是不是同一个变量,如果是同一个变量再用异或交换变量值会变为0
*/
public static void swap(int[] a, int i, int j) {
if(i == j) return;
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
}
选择排序
从0号索引开始,拿着每一个索引上的元素和之后的元素依次比较。每个索引比较结束之后,该索引上的值就是排好序的元素。
时间复杂度:O(n^2)
- 不稳定的排序算法
SelectSort.java
import java.util.Scanner;
public class SelectSort {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int len = sc.nextInt();
int[] a = new int[len];
for(int i = 0; i < len; ++ i ) {
a[i] = sc.nextInt();
}
selectSort(a, len);
for(int i = 0; i < len; ++ i ) {
System.out.print(a[i] + (i == len - 1 ? "\n" : " "));
}
}
public static void selectSort(int[] a, int len) {
for(int i = 0; i < len - 1; ++ i ) { // 比较轮数
for(int j = i + 1; j < len; ++ j ) { // 每一轮:比较a[i]及其索引之后的元素
if(a[i] > a[j]) {
swap(a, i, j);
}
}
}
}
public static void swap(int[] a, int i, int j) {
if(i == j) return;
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
}
插入排序
现在要插入a[i],已知a[0,i-1]已经按照升序排好,现在将a[i]从a[i-1]开始往前依次比较,找到第一个比a[i]小的数,然后将a[i]插入到这个数后面。
时间复杂度:O(n^2)
- 稳定的排序算法
insertSort
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; ++ i ) {
arr[i] = in.nextInt();
}
insertSort(arr, n);
for(int i = 0; i < n; ++ i ) {
System.out.print(arr[i]);
System.out.print(i == n - 1 ? '\n' : ' ');
}
}
public static void insertSort(int[] arr, int len) {
for(int i = 1; i < len; ++ i ) {
int tmp = arr[i];
int j = i;
while(j > 0 && tmp < arr[j - 1]) {
arr[j] = arr[j - 1];
-- j;
}
if(j != i) {
arr[j] = tmp;
}
}
}
}
希尔排序
插入排序的改进算法,算法图解
初始增量: gap=length/2
缩小增量:gap = gap/2
根据增量对原数组分组,对于每一个分组采用插入排序进行排序,直至增量为1,则排序完成。
时间复杂度:O(n^(3/2))
- 不稳定的排序算法
shellSort
public static void shellSort(int[] arr, int len) {
for(int gap = len >> 1; gap >= 1; gap >>= 1) {
for(int i = gap; i < len; ++ i ) {
int tmp = arr[i];
int j = i;
while(j - gap >= 0 && arr[j - gap] > tmp) {
arr[j] = arr[j - gap];
j -= gap;
}
if(i != j) {
arr[j] = tmp;
}
}
}
}
快速排序
期望(平均)时间复杂度/最优时间复杂度:O(nlogn)->分界值每次都取到序列的中位数
最差时间复杂度O(n^2)->分界值每次都取到最小值或最大值,退化为选择排序(实际上,等概率随机取分界值,每次都取到边缘数值的概率很小,所以几乎不可能达到最差情况)
- 不稳定的排序算法
三路快速排序
- 随机选取分界点pivot
- 将待排数列划分为三个部分:小于分界点的区间
[l, lit)
、等于分界点的区间[lit, rit)
以及大于分界点的区间[rit, r)
。 - 再排序小于分界点区间和大于分界点区间
- 不稳定的排序算法
三路快速排序在处理含有多个重复值的数组时,效率远高于原始快速排序。其最佳时间复杂度为 O(n)。
quickSort
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; ++ i ) {
arr[i] = in.nextInt();
}
quickSort(arr, 0, n);
for(int i = 0; i < n; ++ i ) {
System.out.print(arr[i]);
System.out.print(i == n - 1 ? '\n' : ' ');
}
}
public static void quickSort(int[] a, int l, int r) {
if(l == r) return;
Random random = new Random();
int x = l + random.nextInt(r - l);
int pivot = a[x];
int i = l, lit = l, rit = r;
while(i < rit) {
if(a[i] < pivot) {
swap(a, i ++ , lit ++ );
} else if(a[i] > pivot) {
swap(a, i, -- rit);
} else {
++ i;
}
}
quickSort(a, l, lit);
quickSort(a, rit, r);
}
public static void swap(int[] a, int i, int j) {
if(i == j) return;
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
}
非递归三路快排
NonRecursiveQuickSort
class NonRecursiveQuickSort {
private Random random = new Random();
void quickSort(int[] arr) {
Stack<int[]> stack = new Stack<>();
stack.push(new int[]{0, arr.length - 1});
while(!stack.empty()) {
int[] range = stack.pop();
int l = range[0];
int r = range[1];
int[] its = partition(arr, l, r);
if(l < its[0] - 1) {
stack.push(new int[]{l, its[0] - 1});
}
if(its[1] + 1 < r) {
stack.push(new int[]{its[1] + 1, r});
}
}
}
int[] partition(int[] arr, int low, int high) {
// <: [low, lit - 1], =: [lit, rit], >: [rit + 1, high]
int pivot = random.nextInt(high - low) + low;
pivot = arr[pivot];
int i = low;
int lit = i, rit = high;
while(i <= rit) {
if(arr[i] < pivot) {
swap(arr, i ++ , lit ++ );
} else if(arr[i] > pivot){
swap(arr, i , rit -- );
} else {
++ i;
}
}
return new int[]{lit, rit};
}
static void swap(int[] a, int i, int j) {
if(i == j) return ;
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
}
归并排序
时间复杂度:O(nlogn)
划分区间一共有logn层,而每一层合并时每一个元素都会遍历到一次,所以是nlogn
- 稳定的排序算法
mergeSort
import java.util.*;
public class Main {
private static int[] tmp;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] arr = new int[n];
for(int i = 0; i < n; ++ i ) {
arr[i] = in.nextInt();
}
tmp = new int[n];
mergeSort(arr, 0, n - 1);
for(int i = 0; i < n; ++ i ) {
System.out.print(arr[i]);
System.out.print(i == n - 1 ? '\n' : ' ');
}
}
public static void mergeSort(int[] arr, int l, int r) { // [l, r]
if(l == r) return;
int mid = l + ((r - l) >> 1);
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
public static void merge(int[] arr, int l, int mid, int r) {// [l, mid] [mid + 1, r]
int i = l, j = mid + 1, k = l;
while(i <= mid && j <= r) {
if(arr[i] <= arr[j]) tmp[k ++ ] = arr[i ++ ]; // 这里必须是小于等于,不然回影响算法的稳定性
else tmp[k ++ ] = arr[j ++ ];
}
while(i <= mid) tmp[k ++ ] = arr[i ++ ];
while(j <= r) tmp[k ++ ] = arr[j ++ ];
for(int id = l; id <= r; ++ id ) arr[id] = tmp[id];
}
}
堆排序
- 不稳定的排序算法
- 时间复杂度O(nlogn)
升序排序:维护大根堆,取堆顶(最大值)置数组末尾选择排序
降序排序:维护小根堆,取堆顶(最小值)置数组末尾选择排序
heapSort
import java.util.*;
public class Main{
static int n;
static int[] a;
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
a = new int[n];
for(int i = 0; i < n; ++ i ) {
a[i] = in.nextInt();
}
heapSort();
for(int i = 0; i < n; ++ i ) {
System.out.print(a[i]);
System.out.print(i == n - 1 ? "\n" : " ");
}
}
public static void heapSort() {
// 从【最后一个叶子节点的父亲节点】开始往前遍历,完成数组的(大根)堆化
for(int i = (n - 1 - 1) >> 1; i >= 0; -- i) {
sift(i, n - 1);
}
// 将堆顶固定在数组末尾(选择排序),将剩余堆维持堆的特性
for(int i = n - 1; i > 0; -- i) {
swap(i, 0);
sift(0, i - 1);
}
}
// 将数组子区间[st, ed]进行(大根)堆化
public static void sift(int st, int ed) {
int rt = st;
int son = rt << 1 | 1;
while(son <= ed) {
// 取根节点值更大的子节点(默认左儿子,如果右儿子更大则更换)
if(son + 1 <= ed && a[son + 1] > a[son]) ++ son;
// 如果根节点值小于子节点,则交换
if(a[rt] < a[son]) swap(rt, son);
else return; // 否则堆化完成
// 继续往下堆化子树
rt = son;
son = rt << 1 | 1;
}
}
public static void swap(int i, int j) {
if(i == j) return ;
a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j];
a[i] = a[i] ^ a[j];
}
}