八大排序
一. 时间复杂度
一. 时间复杂度为O(n^2)
1. 冒泡排序(BubbleSort)
思想:连续两个数进行比较大的后移
import java.util.Arrays;
/**
* 时间复杂度是O(n^2)
*/
public class BubbleSort {
public static void bubble(int []arr){
int len = arr.length;
//总共比较n-1次
int tmp = 0;
for (int i = 0; i < len-1; i++) {
//每次都会找到大的将它排除在外(每趟排序)
for (int j = 0; j < len-i-1; j++) {
if(arr[j]>arr[j+1]){
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
public static void bubbleY(int []arr){
int len = arr.length;
boolean flag = false;
int tmp = 0;
//总共比较n-1次
for (int i = 0; i < len-1; i++) {
//每次都会找到大的将它排除在外
for (int j = 0; j < len-i-1; j++) {
if(arr[j]>arr[j+1]){
tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
flag = true;
}
}
System.out.println("第"+(i+1)+"趟结果为:");
System.out.println(Arrays.toString(arr));
//如果一趟都没有交换
if (!flag)
break;
else
flag = false;
}
}
//从前往后找最大,从后往前找最小,记录最后交换的位置,说明之后的已经有序了
public static void bubbleYY(int []arr) {
int len = arr.length-1;
boolean flag;
int pos = 0;//记录最后交换的位置
int index = 0;//每趟过后都有值确定
int tmp = 0;
//总共比较n-1次
for (int i = 0; i < len; i++) {
flag = false;
pos = 0;
//每次都会找到大的将它排除在外
//从前往后找最大
for (int j = index; j < len; j++) {
if (arr[j] > arr[j + 1]) {
tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = true;
pos = j;
}
}
if (!flag)
break;
//len的值改变
len = pos;
//从后往前找最小
for(int j = len ;j>index;j--){
if(arr[j]<arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
flag = true;
}
}
index ++;
System.out.println("第" + (i + 1) + "趟结果为:");
System.out.println(Arrays.toString(arr));
//如果一趟都没有交换
if(!flag)
break;
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
int [] arr = {3,9,-1,10,20,-2};
bubbleYY(arr);
long end = System.currentTimeMillis();
// System.out.println(Arrays.toString(arr));
System.out.println(end-start+"ms");
}
}
2. 选择排序
思想:依次选择最小的放在数组前面,每次记录最小值的位置
import java.util.Arrays;
public class SelectSort {
public static void selectSort(int[] arr) {
int len = arr.length;
int min;
int index;
int tmp;
for (int i = 0; i < len - 1; i++) {//len-1次循环
//假定当前值是最小值
min = arr[i];
index = i;
for (int j = i + 1; j < len; j++) {
if (min > arr[j]) {
min = arr[j];
//并记录位置
index = j;
}
}
//一次循环后交换
if (i != index) {
arr[index] = arr[i];
arr[i] = min;
}
}
}
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, 20, -2};
selectSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3. 插入排序
思想:首先前两个比较,然后对排好的进行后续的插入,找到插入的位置
import java.util.Arrays;
public class InsertSort {
public static void insertSort(int arr[]) {
int len = arr.length;
int tmp;
for (int i = 1; i < len; i++) {
tmp = arr[i];//记录无序列表第一个元素
int j;
//插入有序列表
for (j = i - 1; j >= 0; j--) {
//如果比无序表中的数大时
if (arr[j] > tmp) {
//后移
arr[j + 1] = arr[j];
} else
break;
}
arr[j + 1] = tmp;
System.out.println("第"+i+"轮交换后:");
System.out.println(Arrays.toString(arr));
}
}
public static void main(String[] args) {
int[] arr = {3, 9, -1, 10, 20, -2};
insertSort(arr);
// System.out.println(Arrays.toString(arr));
}
}
二. 时间复杂度为O(n*logn)
1. 归并排序
思想:首先先递归分解让数组中每个数成为长度为1的有序区间,然后合并成最大长度为2的有序区间(排序),以此类推直到所有数为一个有序区间
import java.util.Arrays;
public class MergetSort {
public static void mergetSort(int[
] arr) {
int len;
if ((len = arr.length) > 0 && arr != null) {
int[] tmpArr = new int[len];
mergetSort(arr, 0, len - 1, tmpArr);
} else {
System.out.println("数组中没有任何信息!!");
}
}
//递归拆分
public static void mergetSort(int[] arr, int left, int right, int[] tmpArr) {
if (left < right) {
int mid = (right - left) / 2 + left;
//递归分解
mergetSort(arr, left, mid, tmpArr);
mergetSort(arr, mid + 1, right, tmpArr);
//每次合并
merget(arr, left, mid, right, tmpArr);
}
}
//合并
public static void merget(int[] arr, int left, int mid, int right, int[] tmpArr) {
int i = left;//两个小数组的起始坐标
int j = mid + 1;
int tmpIndex = i;
//排序合并到大数组
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
tmpArr[tmpIndex] = arr[i];
i++;
} else {
tmpArr[tmpIndex] = arr[j];
j++;
}
tmpIndex++;
}
//拷贝剩下的
while (i <= mid) {
tmpArr[tmpIndex] = arr[i];
i++;
tmpIndex++;
}
while (j <= right) {
tmpArr[tmpIndex] = arr[j];
j++;
tmpIndex++;
}
//拷贝到arr数组
for (int k = left; k <= right; k++) {
arr[k] = tmpArr[k];
}
}
public static void main(String[] args) {
int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
mergetSort(arr);
System.out.println(Arrays.toString(arr));
}
}
2. 快速排序
思想:随机的在数组中选择一个数(划分值),小于这个数的统一放在左边,大于这个数的统一放在右边,接下来递归的调用快速排序。(一次划分过程的时间复杂度为O(n))
import java.util.Arrays;
public class QuickSort {
public static void quickSort(int[] arr) {
if (arr == null || arr.length == 0) {
System.out.println("数组中没有值");
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {//结束条件
int middle = getMiddle(arr, low, high);
// System.out.println(Arrays.toString(arr));
//根据基准元素划分,递归调用
quickSort(arr, low, middle - 1);
quickSort(arr, middle + 1, high);
}
}
//进行划分(方法1)
public static int getMiddle1(int[] arr, int low, int high) {
int tmp = arr[low];//基准元素(随便找)
while (low < high) {
while (low < high && arr[high] >= tmp) {
high--;
}
arr[low] = arr[high];
while (low < high && arr[low] <= tmp) {
low++;
}
arr[high] = arr[low];
}
arr[low] = tmp;
return low;//根据基准的中间位置
}
//进行划分(方法2)
public static int getMiddle(int[] arr,int low,int high) {
int tmp = arr[high];//基准元素(随便找)
for (int i = low; i < high; i++) {
if (arr[i] <= tmp) {
int a = arr[i];
arr[i] = arr[low];
arr[low] = a;
low++;
}
}
arr[high] = arr[low];
arr[low] = tmp;
return low;//根据基准的中间位置
}
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
quickSort(arr);
System.out.println(Arrays.toString(arr));
}
}
3. 堆排序
思想:首先把n个数建成大堆(小堆),把堆顶元素与最后位置的数交换,把最大值放在最后成为有序部分,然后构建n-1的大堆。
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] arr = {4, 6, 8, 5, 9};
heapSort(arr);
System.out.println(Arrays.toString(arr));
}
// 完全二叉树,线性存储
public static void heapSort(int[] arr) {
//1. 构建大顶堆(升序) 如果降序构建小顶堆
int len = 0;
int tmp = 0;
for (len = arr.length; len > 1; len--) {// 每次会把大的放在数组末尾排除掉
int index = len / 2 - 1;// 第一个非叶子节点
// 调整
for (int i = index; i >= 0; i--) {
adjustHeap(arr, i, len);// 调整
}
// 交换
tmp = arr[0];
arr[0] = arr[len - 1];
arr[len - 1] = tmp;
}
}
/**
* @param arr : 要调整的数组
* @param index : 非叶子节点
* @param len : 还需要调整的长度
*/
public static void adjustHeap(int arr[], int index, int len) {
int tmp = arr[index];// 要调整子树的根
for (int k = 2 * index + 1; k < len; k = 2 * k + 1) {
if (k + 1 < len && arr[k] < arr[k + 1]) {// index 的左右子孩子
k++;
}
// 和根比
if (arr[k] > tmp) {
arr[index] = arr[k];
index = k;// !!!
} else {
break;
}
}
arr[index] = tmp;
}
}
4. 希尔排序
思路:插入排序的改良,插入排序步长为1和前面一个一个比较,而希尔步长是调整的,从大到小调整,根据步长比较交换。关键在于步长的选择
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* 基本插入排序会有一些问题比如
* 23456781,需要后移次数很多,影响效率
*/
public class ShellSort {
//1. 希尔排序在交换时用的交换法(效率不够高)
public static void shellSort(int[] arr) {
int len = arr.length;
int stepSize = len / 2;//gap步长
int tmp;
while (stepSize > 0) {
//分stepSize组
for (int i = stepSize; i < len; i++) {
//比较交换(不是单纯的两两比较交换)
for (int j = i - stepSize; j >= 0; j -= stepSize) {
if (arr[j] > arr[j + stepSize]) {
tmp = arr[j];
arr[j] = arr[j + stepSize];
arr[j + stepSize] = tmp;
}
}
}
// System.out.println("步长为" + stepSize + "交换后数组为:");
// System.out.println(Arrays.toString(arr));
stepSize = stepSize / 2;
}
}
//2. 希尔排序在交换时用的移动法(速度会高很多)
public static void shellSortG(int[] arr) {
int len = arr.length;
int stepSize = len / 2;//gap步长
int tmp;
while (stepSize > 0) {
//分stepSize组,逐个对其所在的组进行直接插入排序
for (int i = stepSize; i < len; i++) {
tmp = arr[i];
int j;
for (j = i - stepSize; j >= 0;j -= stepSize) {
//移动
if (arr[j] > tmp) {
arr[j + stepSize] = arr[j];
} else {
break;
}
}
//找到插入的位置
arr[j + stepSize] = tmp;
}
// System.out.println("步长为" + stepSize + "交换后数组为:");
// System.out.println(Arrays.toString(arr));
stepSize = stepSize / 2;
}
}
public static void main(String[] args) {
// int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random()*80);
}
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(dateFormat.format(date));
shellSortG(arr);
Date date1 = new Date();
System.out.println(dateFormat.format(date1));
// System.out.println(Arrays.toString(arr));
}
}
三. 时间复杂度趋近于O(n)
不是基于比较的排序,思想来源于桶排序
1. 计数排序
思想:先建立桶,然后把要排的实例放到对应的桶,然后顺序倒出就是排好序的
import java.util.Arrays;
//基于桶排序的思想
public class CountSort {
/**
* 计数排序,用于待排序的数位数相同;
* 比如比较员工身高 在100-300之间
* @param arr
*/
public static void countSort(int[] arr){
//1.建立桶
int[] dp = new int[201];//身高在100 ~300
//2. 入桶
for (int i = 0; i < arr.length; i++) {
dp[arr[i]-100]++;
}
//3. 出桶,已经排列好了
int count =0;
for (int i = 0; i <= 200; i++) {
if(dp[i]!=0){
for (int j = 0; j < dp[i]; j++) {
arr[count] = i+100;
count++;
}
}
}
}
public static void main(String[] args) {
int[] arr = {178,156,165};
countSort(arr);
System.out.println(Arrays.toString(arr));
}
}
2. 基数排序
思想:比如排序的数是十进制的建立0-9号桶,根据个位数进桶,然后从0-9依此倒出,然后按十位进入对应桶,然后再倒出,然后再根据最高位入桶,然后倒出后就是有序的
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class RadixSort {
/**
* 使用于正整数情况,而且位数可以不相同,如果都相同时用计数排序
* @param arr 待排序数组
*/
public static void radixSort(int[] arr) {
//1. 找到最大值,看有几位就是需要排几次,几次入桶
int max = 0;
int count = 0;
for (int i = 0; i < arr.length; i++) {
if (max < arr[i]) {
max = arr[i];
}
}
//看有几位
while (max > 0) {
max /= 10;
count++;
}
//建立桶(动态的二维数组)
List<ArrayList<Integer>> dp = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ArrayList<Integer> list = new ArrayList<>();
dp.add(list);
}
//进桶count次
int bit;
for (int i = 0; i < count; i++) {
//比较所有数组元素从个位到count位(入桶)
for (int j = 0; j < arr.length; j++) {
//获取对应位的值 0 : 个位 1 : 十位
bit = arr[j] % (int)Math.pow(10,i+1) / (int)Math.pow(10,i);
ArrayList<Integer> list = dp.get(bit);
list.add(arr[j]);
// dp.set(bit,list);
}
//按次序倒出桶
int tmp = 0;
for (int j = 0; j < 10; j++) {
ArrayList<Integer> list = dp.get(j);
if(list.size()>0){
while(list.size()>0) {
arr[tmp] = list.remove(0);
tmp ++;
}
}
}
}
}
public static void main(String[] args) {
int []arr = {123,22,44,35,12,90,9,0};
radixSort(arr);
System.out.println(Arrays.toString(arr));
}
}
二. 经典排序算法的空间复杂度
1. O(1)
插入排序,选择排序,冒泡排序, 堆排序(递归版O(logn)), 希尔排序
2. O(logn)~O(n)
快速排序,基于划分情况
3. O(n)
归并排序,(手摇算法->O(1))
4. O(m)
计数排序,基数排序(选择桶的个数决定的)
三. 经典排序算法的稳定性
假定待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,称为稳定的,否则为不稳定的。
1. 稳定排序:
冒泡排序 插入排序 归并排序 计数排序 基数排序 桶排序
2. 不稳定排序
选择排序,2221
快速排序,43335
希尔排序,5115 步长2
堆排序, 555
四. 补充说明
1. 排序算法无绝对优劣
2. 为什么叫快速排序
并不代表比堆排序,归并排序优良,只是常量系数比较小
3. 工程上的排序
- 是综合排序
- 数组较小时,插入排序
- 数组较大时,快速排序或其它O(n*logn)的排序。