一、冒泡排序
public class MyBubbleSort {
/*
冒泡排序:
时间复杂度:O(n) ~ O(n^2) 最好:顺序 + 标记 最坏: 逆序
稳定性: 稳定
空间复杂度: 常数空间 ---> O(1)
*/
public static void bubleSort (int[] arr) {
int len = arr.length;
//len是未排序的元素个数
while (len > 0) {
//flag:true 已经有序
boolean falg = true;
//一轮冒泡排序
for (int i = 0; i < len - 1; i++) {
if (arr[i] > arr[i + 1]) {
MySort.swap(arr, i, i + 1);
falg = false;
}
}
if (falg) {
//为真就是有序,有序就跳出
break;
}
len--;
}
}
}
二、堆排序
public class MyHeapSort {
public static void shifDownBig (int[] arr, int parent, int sz) {
int child = parent * 2 + 1;
while (child < sz) {
if (child + 1 < sz && arr[child + 1] > arr[child]) {
child++;
}
//比较父节点和孩子节点
if (arr[child] > arr[parent]) {
MySort.swap(arr, child, parent);
parent = child;
child = parent * 2 + 1;
} else {
break;
}
}
}
/*
堆排序:
时间复杂度: 建堆: O(n) log(n) +log(n - 1) + log(n - 2) + ... + 1 ---> n * log(n)
稳定性: 不稳定 ---> 向下调整可能会破坏相对位置
空间复杂度: 常数空间 ---> O(1)
*/
public static void heapSort (int[] arr) {
int len = arr.length;
//建堆,最后一个非叶子节点向下调整
for (int i = (len - 2) / 2; i >= 0; i--) {
shifDownBig(arr, i, len);
}
//交换
int end = len - 1;
while (end > 0) {
MySort.swap(arr, 0, end);
shifDownBig(arr, 0, end);
end--;
}
}
}
从最后一个非叶子节点向下调整,一直调整到根部,每次调整完,交换根部与尾部元素,因为是大堆,所以数组中最大的数就到了尾部
三、插入排序
public class MyInsertSort {
//插入排序
/*
插入排序:
时间复杂度: 最坏情况: O(n^2) ---> 逆序序列 最好情况:O(n) ---> 顺序序列
稳定性:稳定
空间复杂度: 常数空间 ---> O(1)
*/
public static void insertSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
//i表示已排好序的最后一个位置
int key = arr[i + 1];
int end = i;
while (end >= 0 && arr[end] > key) {
//把大的元素向后移动
arr[end + 1] = arr[end];
end--;
}
arr[end + 1] = key;
}
}
}
遍历数组,然后一个一个往前插。
四、希尔排序
public class MyShellSort {
/*
希尔排序:改进的插入排序,数据越有序,优化的空间就越小
时间复杂度: O(n) ~ O(n^2) -----> O(n^1.3)
稳定性:不稳定
空间复杂度: 常数空间 ---> O(1)
*/
public static void shellSort (int[] arr) {
//对数据进行分组
//每组数据进行插入排序
//同一组数据,间隔递减
int gap = arr.length;
while (gap > 1) {
gap = gap / 3 + 1;
//i表示已排序的数据的最后一个位置
for (int i = 0; i < arr.length - gap; i++) {
int end = i;
int key = arr[end + gap];
while (end >= 0 && arr[end] > key) {
arr[end + gap] = arr[end];//依次往后挪一个
end -= gap;
}
arr[end + gap] = key;
}
}
}
}
希尔排序是优化的插入排序,普通的插入排序是每间隔一个就排一次序,而希尔排序则把间隔改成了 gap = gap / 3 + 1;
五、归并排序
public class MyMergeSort {
//归并排序
private static void mergeInternal(int[] arr, int left, int mid, int right, int[] tmp) {
int begin1 = left, end1 = mid;
int begin2 = mid + 1, end2 = right;
int idx = left;
while (begin1 <= end1 && begin2 <= end2) {
//合并
if (arr[begin1] <= arr[begin2]) {
tmp[idx++] = arr[begin1++];
} else {
tmp[idx++] = arr[begin2++];
}
}
//判断是否还有剩余元素
while (begin1 <= end1) {
tmp[idx++] = arr[begin1++];
}
while (begin2 <= end2) {
tmp[idx++] = arr[begin2++];
}
//拷贝
for (int i = left; i <= right; i++) {
arr[i] = tmp[i];
}
}
public static void mergeSort (int[] arr, int left, int right, int tmp[]) {
if (left >= right) {
return;
}
//分组
int mid = (left + right) / 2;
///首先给[left, mid] 和 [mid + 1, right]小组进行排序
mergeSort(arr, left, mid, tmp);
mergeSort(arr, mid + 1, right, tmp);
//归并: 前提条件 数组已经有序
mergeInternal(arr, left, mid, right, tmp);
}
//递归
public static void merge (int[] arr) {
int[] tmp = new int[arr.length];
mergeSort(arr, 0, arr.length - 1, tmp);
}
//非递归
public static void mergeNoR(int[] arr){
int[] tmp = new int[arr.length];
//i: 每次归并的元素个数
for(int i = 1; i < arr.length; i *= 2){
// j: 下一次归并的起始位置
for(int j = 0; j < arr.length; j += 2 * i) {
//mergeInternal(arr, j, j + i - 1, j + 2 * i - 1, tmp); 区间可能越界
int left = j;
int mid = j + i - 1;
//右半部分没有数据,不用进行归并
if(mid >= arr.length - 1) {
continue;
}
int right = j + 2 * i - 1;
//判断右边的区间是否越界
if(right >= arr.length){
right = arr.length - 1;
}
mergeInternal(arr, left, mid, right, tmp);
}
}
}
}
先分组,然后再合并,合并的时候排序。分为递归与非递归两种。
六、快速排序**
import java.util.Stack;
public class MyQuickSort {
private static int getMid(int[] arr, int left, int right) {
int mid = (left + right) / 2;
if (arr[mid] > arr[left]) {
if (arr[mid] > arr[left]) {
return mid;
} else {
if (arr[left] > arr[right]) {
return left;
} else {
return right;
}
}
} else {
if (arr[mid] > arr[right]) {
return mid;
} else {
if (arr[left] < arr[right]) {
return left;
} else {
return right;
}
}
}
}
//1.第一种分割方法:Hoare法
public static int partion1(int[] arr, int left, int right) {
//三数取中
int mid = getMid(arr, left, right);
//把三个数中 大小中间的数放到mid位置上
MySort.swap(arr, left, mid);
int key = arr[left];//key是基准值
int start = left;
//从左到右一直找,相遇为止
while (left < right) {
while (left < right && arr[right] >= key) {
//right要是有小于基准值的就停下来进行交换
right--;
}
while (left < right && arr[left] <= key) {
//left要是有大于基准值的就停下来进行交换
left++;
}
MySort.swap(arr, left, right);
}
//跳出循环时,left与right相遇,再把基准值与相遇位置进行交换
MySort.swap(arr, left, start);
//返回中间的位置,完成一次分割
return left;
}
//2.第二种分割法:挖坑法
public static int partion2 (int[] arr, int left, int right) {
//三数取中
int mid = getMid(arr, left, right);
//把三个数中 大小中间的数放到mid位置上
MySort.swap(arr, left, mid);
//基准值
int key = arr[left];
//挖坑填坑
while (left < right) {
while (left < right && arr[right] >= key) {
right--;
}
arr[left] = arr[right];
while (left < right && arr[left] <= key) {
left++;
}
arr[right] = arr[left];
}
//跳出循环后,把基准值放到相遇的位置
arr[left] = key;
return left;
}
//3.第三种分割法:前后遍历法
public static int partion3 (int[] arr, int left, int right) {
//三数取中
int mid = getMid(arr, left, right);
//把三个数中 大小中间的数放到mid位置上
MySort.swap(arr, left, mid);
int key = arr[left];
int prev = left;//最后一个小于key的位置
int cur = left + 1;//下一个小于key的位置
while (cur <= right) {
if (arr[cur] < key && ++prev != cur) {
//如果cur找到下一个小于key的位置
//并且prev和cur之间有大于key的值
//则交换prev 和cur的值
MySort.swap(arr, prev, cur);
}
cur++;
}
MySort.swap(arr, left, prev);
return prev;
}
/*
快排递归:
时间复杂度:O(nlogn) 最坏时间复杂度: 没有优化之前 O(n^2)
稳定性:不稳定 基准值的位置进行交换的时候有可能改变相同元素的相对位置
空间复杂度:logn
*/
public static void quickSort (int[] arr, int left, int right) {
if (left < right) {
int mid = partion3(arr, left, right);
quickSort(arr, left, mid - 1);
quickSort(arr, mid + 1, right);
}
}
/*
非递归: 模拟递归过程
1. 选基准值
2. 根据基准值分组
3. 给划分的小组数据进行重复1,2的过程
*/
public static void quickSortNoR (int[] arr) {
int left = 0;
int right = arr.length - 1;
//用栈来记录空间
Stack<Integer> st = new Stack<>();
if (left < right) {
st.push(left);
st.push(right);
}
while (!st.isEmpty()) {
//取出栈顶的一组区间
int right1 = st.pop();
int left1 = st.pop();
//分组
int mid = partion3(arr, left1, right1);
//新的分组进行压栈
if (mid - 1 > left1) {
st.push(left1);
st.push(mid - 1);
}
if (mid + 1 < right1) {
st.push(mid + 1);
st.push(right1);
}
}
}
}
快速排序有三种方法,不过三种方法换汤不换药。
快排主要是分组然后排序,与归并不同的是归并是先分组在排序,快排则是分组完后就会进行排序。
优化:三数去中,首先在首位和中的三个数字中取一个中间数,作为首,效率会提高。
七、选择排序
public class MySelectSort {
/*
选择排序:
时间复杂度: O(n^2)
稳定性:可以让它稳定/不稳定
空间复杂度: 常数空间 ---> O(1)
*/
public static void selectSort (int[] arr) {
//最小值放前边,最大值放后边
int begin = 0;
int end = arr.length - 1;
while (begin < end) {
int minIdx = begin;//默认开头为最值
int maxIdx = begin;
for (int i = begin; i <= end; i++) {
//最小值放到前面,
// begin往前都是排好序的
// end往后的是排好序的
if (arr[i] < arr[minIdx]) {
minIdx = i;
}
//最大值放到后边
if (arr[i] > arr[maxIdx]) {
maxIdx = i;
}
}
//交换最小值的位置
MySort.swap(arr, begin, minIdx);
//不能再直接交换end和max,如果最大值是第一位的话最大值就到min的位置了
if (maxIdx == begin) {
maxIdx = minIdx;
}
MySort.swap(arr, end, maxIdx);
begin++;
end--;
}
}
}
选择排序就是设置两个指针,一个是头部一个是尾部开始,遍历一遍最小的放在头部,最大的放在尾部,再遍历剩下的数组,以此类推,每次遍历筛选完后 begin++, end–,如此一来范围便缩小了。
运行时间测试
七个排序及其非递归写完后进行时间测试
测试代码:
import java.util.Random;
public class Test2 {
public static void main(String[] args) {
Random rdm = new Random(20200308);
int number = 88888;
int[] arr = new int[number];
for (int i = 0; i < number; i++) {
arr[i] = rdm.nextInt();
}
int[] arr2 = arr.clone();
int[] arr3 = arr.clone();
int[] arr4 = arr.clone();
int[] arr5 = arr.clone();
int[] arr6 = arr.clone();
int[] arr7 = arr.clone();
int[] arr8 = arr.clone();
int[] arr9 = arr.clone();
//快速排序
long begin = System.nanoTime();
MyQuickSort.quickSort(arr, 0, arr.length - 1);
long end = System.nanoTime();
System.out.printf("quickSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//快速排序非递归
begin = System.nanoTime();
MyQuickSort.quickSortNoR(arr2);
end = System.nanoTime();
System.out.printf("quickSortNoR time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//归并排序
begin = System.nanoTime();
MyMergeSort.merge(arr3);
end = System.nanoTime();
System.out.printf("mergeSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//归并排序非递归
begin = System.nanoTime();
MyMergeSort.mergeNoR(arr4);
end = System.nanoTime();
System.out.printf("mergeSortNoR time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//插入排序
begin = System.nanoTime();
MyInsertSort.insertSort(arr5);
end = System.nanoTime();
System.out.printf("inserSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//希尔排序
begin = System.nanoTime();
MyShellSort.shellSort(arr6);
end = System.nanoTime();
System.out.printf("shellSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//插入排序
begin = System.nanoTime();
MySelectSort.selectSort(arr7);
end = System.nanoTime();
System.out.printf("selectSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//堆排序
begin = System.nanoTime();
MyHeapSort.heapSort(arr8);
end = System.nanoTime();
System.out.printf("heapSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
//冒泡排序
begin = System.nanoTime();
MyBubbleSort.bubleSort(arr9);
end = System.nanoTime();
System.out.printf("bubleSort time: %.4fms\n", (end - begin) * 1.0 / 1000 / 1000);
}
}
创造出一个88888个数据的 随机数组,进行排序,比较七大排序的时间。
结果如下
来源:CSDN
作者:yiqqYi
链接:https://blog.csdn.net/yiqqYi/article/details/104793251