目录
排序算法
排序算法概念
# 1.排序算法
- 参考文档:https://baike.baidu.com/item/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/5399605?fr=aladdin
- 排序算法,就是如何使得记录按照要求排列的方法。
# 2.排序算法分类
- 内部排序:指将需要处理的所有数据都加载到内部存储器中进行排序。
- 外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。
# 3.常用排序算法
时间复杂度
# 4.时间复杂度
- 参考文档:https://baike.baidu.com/item/%E7%AE%97%E6%B3%95%E5%A4%8D%E6%9D%82%E5%BA%A6/210801?fr=aladdin
- 即从序列的初始状态到经过排序算法的变换移位等操作变到最终排序好的结果状态的过程所花费的时间度量。
- 时间频度:一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
- 时间复杂度分类:
(1).事后统计:事后统计方法更多的依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用事前分析估算的方法。
(2).事前估算:在计算机程序编制前,依据统计方法对算法进行估算。
# 5.常见时间复杂度
- 常数阶O(1)
- 对数阶O(log2n)
- 线性阶O(n)
- 线性对数阶O(nlog2n)
- 平方阶O(n^2)
- 立方阶O(n^3)
- k次方阶O(n^k)
- 指数阶O(2^n)
- 算法时间复杂度由小到大依次为:Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n2)<Ο(n3)< Ο(nk) <Ο(2n) ,随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
# 6.平均时间复杂度
- 平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
# 7.最坏时间复杂度
- 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
# 8.平均时间复杂度与最坏时间复杂度
- 平均时间复杂度和最坏时间复杂度是否一致,和算法有关。
空间复杂度
# 1.空间复杂度
- 一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,它也是问题规模n的函数。
- 空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的量度。有的算法需要占用的临时工作单元数与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元。
- 用空间换时间;或者用时间换空间。需要在特定的情况下区分,如单片机则空间内存不大,则用时间换空间。而WEB服务器一般内存较大,故使用空间换时间。
排序算法种类
冒泡排序
# 1.冒泡排序
- 冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class BubbleSort {
public static void main(String[] args) {
// int[] arr = {3, -9, -1, 10, -2};
// System.out.println("排序前");
// System.out.println(Arrays.toString(arr));
// bubbleSort(arr);
// System.out.println("排序后");
// System.out.println(Arrays.toString(arr));
/**
* 测试冒泡排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 800000);//生成[0,800000)之间的数
}
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
bubbleSort(arr);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
// for (int i = 0; i < arr.length; i++) {
// for (int j = 0; j < arr.length; j++) {
// if (arr[i] < arr[j]) {
// int temp = arr[i];
// arr[i] = arr[j];
// arr[j] = temp;
// }
// }
// }
}
//冒泡排序封装
public static void bubbleSort(int[] arr) {
//冒泡排序 时间复杂度(n^2)
//标识变量 标识是否进行过交换
boolean flag = false;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
flag = true;
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// System.out.println("第" + (i + 1) + "趟排序后的数组");
// System.out.println(Arrays.toString(arr));
if (!flag) { //在一趟排序中 一次交换都没有发生
break;
} else {
//充值flag 进行下次判断
flag = false;
}
}
}
}
选择排序
# 1.选择排序
- 选择排序算法的基本思路是为每一个位置选择当前最小的元素。
- 选择排序的基本思想是,基于直接选择排序和堆排序这两种基本的简单排序方法。
- 选择排序不是稳定的排序算法,它在计算过程中会破坏稳定性。
# 2.选择排序的思路
(1).首先从第1个位置开始对全部元素进行选择,选出全部元素中最小的给该位置,
(2).再对第2个位置进行选择,在剩余元素中选择最小的给该位置即可;
(3).以此类推,重复进行“最小元素”的选择,直至完成第(n-1)个位置的元素选择,则第n个位置就只剩唯一的最大元素,此时不需再进行选择。使用这种排序时,要注意其中一个不同于冒泡法的细节。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class SelectSort {
public static void main(String[] args) {
// int[] arr = {101,34,119,1,-1,90,123};
// System.out.println("排序前:"+ Arrays.toString(arr));
// selectSort(arr);
// System.out.println("排序后:"+ Arrays.toString(arr));
/**
* 测试选择排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
int[] arr = new int[80000];
for (int i = 0; i < 80000; i++) {
arr[i] = (int) (Math.random() * 800000);//生成[0,800000)之间的数
}
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
selectSort(arr);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
}
//选择排序
public static void selectSort(int[] arr) {
/**
* 第一轮:101 34 119 1
* 第一轮排序: 1,34,119,101
* 第二轮排序: 1,34,119,101
* 第三轮排序: 1,34,101,119
* 时间复杂度:O(n^2)
*/
//使用逐步推导解释选择排序思路
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) { //假定最小值
min = arr[j]; //重置min
minIndex = j; //重置minIndex
}
}
//将最小值 放在arr[0] 即交换
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
// System.out.println("第" + (i + 1) + "轮后");
// System.out.println(Arrays.toString(arr));
}
}
}
插入排序
# 1.插入排序
- 插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。
# 2.插入排序思想
- 插入排序(Insertion Sorting)的基本思想是:把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表。
- 每步将一个待排序的对象, 插入到前面已经排好序的有序表的适当位置上, 直到对象全部插入为止。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class InsertSort {
public static void main(String[] args) {
// int[] arr = {101, 34, 119, 1, 21, 25, 12, 16, 8};
// insertSort(arr);
/**
* 测试选择排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
int[] arr = new int[800000];
for (int i = 0; i < 800000; i++) {
arr[i] = (int) (Math.random() * 800000);//生成[0,800000)之间的数
}
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
insertSort(arr);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
}
/**
* 插入排序
*
* @param arr
*/
public static void insertSort(int[] arr) {
//使用逐步推导方式讲解
//第一轮 {101,34,119,1} => {34,101,119,1}
int insertVal = 0;
int insertIndex = 0; //即arr[i] 前面一个数下标
//定义待插入的数
for (int i = 1; i < arr.length; i++) {
insertVal = arr[i];
insertIndex = i - 1; //即arr[i] 前面一个数下标
/**
* 1. insertIndex >= 0 保证在给InsertVal 找到插入位置 不产生越界ArrayOut
* 2.insertVal < arr[insertIndex] 待插入的数 没有找到插入位置
* 3.将arr[insertIndex] 后移
*/
//给insertVal 找到插入的位置
// 若需要从大到小排序 只需要将 insertVal `<` arr[insertIndex] 修改为 => insertVal `>` arr[insertIndex]
while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
arr[insertIndex + 1] = arr[insertIndex];
insertIndex--;
}
//当退出while循环时 说明插入位置找到 insertIndex + 1
//TODO 若无法理解 debug即可
//判断是否需要赋值
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
// System.out.println("第"+i+"轮:" + Arrays.toString(arr));
}
}
}
希尔排序
# 1.希尔排序
- 希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序。
# 2.希尔排序思想
- 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class ShellSort {
public static void main(String[] args) {
int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
shellSortImprove(arr);
System.out.println(Arrays.toString(arr));
/**
* 测试选择排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
// int[] arr = new int[80000];
// for (int i = 0; i < 80000; i++) {
// arr[i] = (int) (Math.random() * 800000);//生成[0,800000)之间的数
// }
//
// Date startStr = new Date();
// SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// String dateStartStr = dateFormatStartStr.format(startStr);
// System.out.println("排序前的时间: " + dateStartStr);
// shellSortImprove(arr);
// Date endStr = new Date();
// String dateEndStr = dateFormatStartStr.format(endStr);
// System.out.println("排序后的时间: " + dateEndStr);
}
/**
* 希尔排序
* 对有序序列在插入时采用·交换法·, 并测试排序速度
*
* @param arr
*/
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
//遍历个组中所有的元素(共gap组 每组有gap个元素) 步长为5
for (int j = i - gap; j >= 0; j -= 5) {
//如果当前元素大于arr[j] > arr[j+5] 则交换元素
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
// System.out.println("第" + (++count) + "轮:" + Arrays.toString(arr));
}
}
public static void shellSortImprove(int[] arr) {
int count = 0;
//增量gap 并逐步缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
//从第gap个元素 逐个对其所在的组进行直接插入
for (int i = gap; i < arr.length; i++) {
int j = i;
int temp = arr[j];
if (arr[j] < arr[j - gap]) {
while (j - gap >= 0 && temp < arr[j - gap]) {
//移动
arr[j] = arr[j - gap];
j -= gap;
}
//当 退出while后 就给temp找到插入的位置
arr[j] = temp;
}
System.out.println("第" + (++count) + "轮:" + Arrays.toString(arr));
}
}
}
}
快速排序
# 1.快速排序
- 快速排序(Quicksort)是对冒泡排序的一种改进。
# 2.快速排序基本思想
- 取待排序记录序列中的某个记录(例如取第一个记录)作为基准(枢轴),按照该记录的关键字大小,将整个记录序列划分为左右两个子序列: 左侧子序列中所有记录的关键字都小于或等于基准记录的关键字;右侧子序列中所有记录的关键字都大于基准记录的关键字。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class QuickSort {
public static void main(String[] args) {
// int[] arr = {-9, 78, 0, 23, -567, 70, 5, 8, 1, 3, 9, 6, 2, 7};
// quickSort(arr, 0, arr.length - 1);
// System.out.println(Arrays.toString(arr));
/**
* 测试选择排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
int[] arr = new int[80000000];
for (int i = 0; i < 80000000; i++) {
arr[i] = (int) (Math.random() * 800000000);//生成[0,800000)之间的数
}
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
quickSort(arr,0,arr.length-1);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
}
/**
* @param arr
* @param left
* @param right
*/
public static void quickSort(int[] arr, int left, int right) {
/**
* eft:左下标
* ght:右下标
* pivot:中轴值
* temp:临时变量 作为交换时使用
*/
int eft = left;
int ght = right;
int pivot = arr[(left + right) / 2];
int temp = 0;
/**
* while循环目的: 比pivot小的值放在左边 比pivot大的值放在右边
*/
while (eft < ght) {
//在pivot左边 找到大于等于pivot值
while (arr[eft] < pivot) {
eft = eft + 1;
}
while (arr[ght] > pivot) {
ght = ght - 1;
}
//若eft > ght 说明pivot的左右两的值 已经按照左边全部是小于等于(>=)pivot值 右边全部是大于等于(<=)pivot值
if (eft >= ght) {
break;
}
//交换
temp = arr[eft];
arr[eft] = arr[ght];
arr[ght] = temp;
//交换完成后 发现arr[eft] == pivot 值 让eft--; 前移
if (arr[eft] == pivot) {
ght = ght - 1;
}
//交换完成后 发现arr[ght] == pivot 值 让ght++; 后移
if (arr[ght] == pivot) {
eft = eft + 1;
}
}
//若eft == ght 必须 eft++ ght-- 否则出现栈溢出
if (eft == ght) {
eft = eft + 1;
ght = ght - 1;
}
//向左递归
if (left < ght) {
quickSort(arr, left, ght);
}
//向右递归
if (right > eft) {
quickSort(arr, eft, right);
}
}
}
归并排序
# 1.归并排序
- 参考文档:https://baike.baidu.com/item/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/1639015?fr=aladdin
- 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
- 归并是将两个或两个以上的有序表合并成一个新的有序表。
# 2.归并排序思路
-
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class MergetSort {
public static void main(String[] args) {
/**
* 归并排序需要额外的空间
*/
// int arr[] = {8, 4, 5, 7, 1, 3, 6, 2};
// int temp[] = new int[arr.length];
// mergeSort(arr, 0, arr.length - 1, temp);
// System.out.println(Arrays.toString(arr));
/**
* 测试选择排序的速度O(n^2) 设置80000个数据
* 创建80000个随机数组
*/
int[] arr = new int[8000000];
for (int i = 0; i < 8000000; i++) {
arr[i] = (int) (Math.random() * 800000000);//生成[0,800000)之间的数
}
int[] temp = new int[arr.length];
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
mergeSort(arr,0,arr.length-1,temp);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
}
/**
* 归并排序-分、合并方法
*
* @param arr
* @param left
* @param right
* @param temp
*/
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
//向左递归进行分解
mergeSort(arr, left, mid, temp);
//向右递归进行分解
mergeSort(arr, mid + 1, right, temp);
//分解后 需要 合并
merge(arr, left, mid, right, temp);
}
}
/**
* 归并排序-合并方法
*
* @param arr 排序的原始数组
* @param left 左边有序序列的初始索引
* @param mid 中间索引
* @param right 右边索引
* @param temp 临时数组
*/
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
/**
* i:初始化i,左边有序序列的初始索引
* j:初始化j,右边有序序列的初始索引
* cur:指向temp数组当前索引
*/
int i = left;
int j = mid + 1;
int cur = 0;
//1. 先把左右(有序)的数据按照规则填充到temp数组,直到左右两边的有序序列 有一边处理完毕
while (i <= mid && j <= right) {
//如果左边的有序序列的当前元素 小于等于(<=) 右边有序序列的当前元素
//即将左边的当前元素 拷贝到temp数组
//然后cur++ i++
if (arr[i] <= arr[j]) {
temp[cur] = arr[i];
cur = cur + 1;
i = i + 1;
} else {
//如果左边的有序序列的当前元素 大于等于(>=) 右边有序序列的当前元素
//即将右边的当前元素 拷贝到temp数组
temp[cur] = arr[j];
cur = cur + 1;
j = j + 1;
}
}
//2.有剩余数据的一边 将数据一次全部移到temp数组
while (i <= mid) { //左边的有序序列还有剩余的元素 将剩余的元素全部拷贝到temp
temp[cur] = arr[i];
cur = cur + 1;
i = i + 1;
}
while (j <= right) { //右边的有序序列还有剩余的元素 将剩余的元素全部拷贝到temp
temp[cur] = arr[j];
cur = cur + 1;
j = j + 1;
}
//3.将temp数组的元素拷贝到arr
//TODO 不是每次都拷贝所有
cur = 0;
int tempLeft = left;
// System.out.println("tempLeft: "+tempLeft+" right: "+right);
while (tempLeft <= right) {
arr[tempLeft] = temp[cur];
cur = cur + 1;
tempLeft = tempLeft + 1;
}
}
}
基数排序(桶排序)
# 1.基数排序
- 基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通过键值的各个位的值,将要排序的元素分配至某些"桶"中,达到排序的作用。
- 基数排序法是属于稳定性的排序,基数排序法的是效率高的稳定性排序法。
- 基数排序(Radix Sort)是桶排序的扩展。
- 基数排序是1887年赫尔曼·何乐礼发明的。它是这样实现的:将整数按位数切割成不同的数字,然后按每个位数分别比较。
# 2.基数排序
- 将所有待比较数值统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
- 基数排序是对传统桶排序的扩展,速度很快。
- 数排序是经典的空间换时间的方式,占用内存很大, 当对海量数据排序时,容易造成 OutOfMemoryError 。
- 基数排序时稳定的。[注:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的]
- 有负数的数组,我们不用基数排序来进行排序, 如果要支持负数。参考: https://code.i-harness.com/zh-CN/q/e98fa9
代码实现
import java.util.Arrays;
public class RaidxSort {
public static void main(String[] args) {
int arr[] = {53, 3, 542, 748, 14, 214};
radixSort(arr);
}
/**
* 基数排序
*
* @param arr
*/
public static void radixSort(int[] arr) {
//基数排序代码
//1.得到数组中最大的数的位数
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数的位数
int maxLength = (max + "").length();
//定义一个二维数组 表示10个桶 每个桶都是一个一维数组
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中 实际存放了多少个数组 定义一个一维数组来记录各个桶每次放入的数据个数
//如 bucketElementCounts[0] 记录的数据为 bucket[0] 桶放入数据个数
int[] bucketElementCounts = new int[10];
//使用循环将代码处理
for (int i = 0, n = 1; i < maxLength; i++, n = n * 10) {
//针对每个元素的位数的 个位数、十位数、百位数等进行排序处理
for (int j = 0; j < arr.length; j++) {
//取出每个元素的个位的数
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照桶的顺序(一维数组的下标依次取出数据 放入原来数组)
int index = 0;
//遍历每一个桶 并将桶中的数据 放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中 有数据 才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第K个桶(即第k个一维数组)
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出数组放入到arr
arr[index++] = bucket[k][l];
}
}
//TODO 第n轮处理过后 需要将每个bucketElementCounts[k] = 0 不处理会出现 java.lang.ArrayIndexOutOfBoundsException: 6 错误
bucketElementCounts[k] = 0;
}
System.out.println("第" + (i + 1) + "轮,个位的数排序处理 arr= " + Arrays.toString(arr));
}
}
}
堆排序
# 1.堆排序
- 参考文档:https://baike.baidu.com/item/%E5%A0%86%E6%8E%92%E5%BA%8F/2840151?fr=aladdin
- 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏、最好、平均时间复杂度均为O(nlogn),它也是不稳定排序。
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
- 每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
- 大顶堆特点:arr[i] >= arr[2*i+1] && arr[i] >= arr[2*i+2]。i从0开始编号。
- 小顶堆特点:arr[i] <= arr[2*i+1] && arr[i] <= arr[2*i+2]。i从0开始编号。
- 一般升序采用大顶堆,降序采用小顶堆。
# 2.堆排序基本思想
- 将待排序序列构造成一个大顶堆。
- 整个序列的最大值就是堆顶的根节点。
- 将其与末尾元素进行交换,此时末尾就为最大值。
- 然后将剩余n-1个元素重新构造成一个堆,这样会得到n-1个元素的次小值。如此反复执行,便能得到一个有序序列了。
代码实现
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class HeapSort {
public static void main(String[] args) {
// int[] arr = {4, 6, 8, 5, 9};
// heapSort(arr);
/**
* 测试选择排序的速度O(n^2) 设置80000000个数据
* 创建80000个随机数组
*/
int[] arr = new int[80000000];
for (int i = 0; i < 80000000; i++) {
arr[i] = (int) (Math.random() * 800000000);//生成[0,800000000)之间的数
}
Date startStr = new Date();
SimpleDateFormat dateFormatStartStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStartStr = dateFormatStartStr.format(startStr);
System.out.println("排序前的时间: " + dateStartStr);
heapSort(arr);
Date endStr = new Date();
String dateEndStr = dateFormatStartStr.format(endStr);
System.out.println("排序后的时间: " + dateEndStr);
}
/**
* 堆排序方法
*
* @param arr
*/
public static void heapSort(int[] arr) {
int temp = 0;
System.out.println("堆排序");
/**
* 分步操作
*/
// adjustHeap(arr,1,arr.length);
// System.out.println("第一次:" + Arrays.toString(arr));//49856
// adjustHeap(arr,0,arr.length);
// System.out.println("第一次:" + Arrays.toString(arr));//96854
/**
* 1.将无序序列构建成一个堆 根据升序降序需要选择大顶堆或小顶堆
*/
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
// System.out.println(Arrays.toString(arr));//96854
/**
* 2.将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
* 3.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
*/
for (int j = arr.length - 1; j > 0; j--) {
//交换
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr,0,j);
}
// System.out.println(Arrays.toString(arr));//96854
}
/**
* 将数组(二叉树) 调整成一个大顶堆
* 功能:完成将以i 对应的非叶子节点的树调整成大顶堆
* int[] arr = {4, 6, 8, 5, 9} => i = 1 => adjustHeap -> {4,9,8,5,6} => i = 0 => jdjustHeap => {9,6,8,5,4}
*
* @param arr 待调整的数组
* @param i 表示非叶子节点在数组中的索引
* @param length 表示对多少个元素进行调整 length是在逐渐减少
*/
public static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i];//先取出当前元素的值 保存在临时变量
/**
* 开始调整
* 说明:
* 1.k = i * 2 + 1 k是i节点的左子节点
*
*/
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
if (k + 1 < length && arr[k] < arr[k + 1]) {//左子节点值小于右子节点的值
k++;//k指向右子节点
}
if (arr[k] > temp) {//若子节点大于父节点
arr[i] = arr[k];//把较大的值赋给当前节点
i = k; //i 指向 k 继续循环比较
} else {
break; //重要!!!
}
}
//当for循环结束后 已将以i为父节点的树最大值 放在了顶部(局部)
arr[i] = temp; //将temp值放到调整后的位置
}
}