数据结构与算法--排序
那么今天给大家带来的是排序算法的相关内容:
排序
时间复杂度的计算:忽略低次项对时间的影响。(随着计算量的增大,低次项的计算时间可以忽略)
平均的时间复杂度和最坏的时间复杂度:
-
平均的时间复杂度值得是:所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。
-
最坏情况下的时间复杂度,称为最坏时间复杂度。一般讨论的最坏时间复杂度均是最坏情况下的时间复杂度。
-
算法是否稳定:稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
PS:相比较空间复杂度,大家更看重的是时间复杂度,更在意运行速度。所以大多数例如缓存产品等,利用的就是空间换取时间的做法。
常用的八大排序算法Java代码实现
排序算法共有十大经典排序算法,鉴于非比较类排序实现中,计数排序/桶排序和基数排序的思想类似,这里概括为八大经典算法。
可以分为两大类:
相关复杂度的分析:
Bubble
/**
* Bubble实现
* * 1. 冒泡排序(从小到大): 从第一个元素开始遍历整个数组,如果大于当前元素 交换位置
* * 2. 再依次拿后续元素进行遍历
* * 3. 即for循环的嵌套实现
* * PS: 时间复杂度为N平方
* * 优化: 如果经过前几次排序 已经成为有序的 则后面不需要再进行排序。 (冒泡排序的优化)
*/
public static List<Integer> bubbleSort(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
// 定义是否发生交换的标识
boolean flag = false;
for (int i = 0; i < list.size() - 1; i++) {
for (int j = 0; j < list.size() - i - 1; j++) {
if (list.get(j) > list.get(j + 1)) {
Integer temp = list.get(j);
list.set(j, list.get(j + 1));
list.set(j + 1, temp);
flag = true;
}
}
if (!flag) {
break;
} else {
flag = false;// 每次循环完成后需要重置
}
}
long endTime = System.currentTimeMillis();
System.out.println("冒泡排序10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
Select
/**
* 选择排序: 1. 循环查找到最小的元素 和第一个元素进行交换
* 2. 从第二个元素开始 再次进行。。。以此类推
*/
public static List<Integer> selectSort(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
for (int j = 0; j < list.size() - 1; j++) {
// 定义最小值 假定为第一个元素
int min = list.get(j);
int index = j;
for (int i = j + 1; i < list.size(); i++) {
if (min > list.get(i)) {
min = list.get(i);
index = i;
}
}
// 遍历完成进行交换
list.set(index, list.get(j));
list.set(j, min);
}
long endTime = System.currentTimeMillis();
System.out.println("选择排序10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
Insert
/**
* 插入排序:把N个待排序的元素看成一个有序表,一个无序表,排序过程中 拿出无序元素 选择插入到有序表中。
* PS: 存在的问题,如果较小的数在最后 那么需要将数据整体后移一遍 随着数据量的增大 效率影响较大
*/
public static List<Integer> insertSort(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
for (int i = 1; i < list.size(); i++) {
// 定义待插入变量
int insertVal = list.get(i);
// 定义有序表索引变量
int index = i - 1;
while (index >= 0 && insertVal < list.get(index)) {
list.set(index + 1, list.get(index));// 后移
index--;
}
// 如果退出循环 即找到要插入的位置
list.set(index + 1, insertVal);
}
long endTime = System.currentTimeMillis();
System.out.println("插入排序10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
Shell
/**
* 希尔排序:
* 介绍:其本身也是一种插入排序,经过改良后 更加高效 也成为缩小增量排序 用于解决上述问题
* 实现:1. 使用循环 /2 进行确定分组步长 进行元素交换 即交换法
* 2. 使用循环 /2 进行确定分组的步长 同时将分组内元素进行插入排序 即移位法
*/
public static List<Integer> shellSort(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
for (int gap = list.size() / 2; gap > 0; gap /= 2) {
// gap每轮的步长
for (int i = gap; i < list.size(); i++) {
for (int j = i - gap; j >= 0; j -= gap) {
if (list.get(j) > list.get(j + gap)) {
int temp = list.get(j);
list.set(j, list.get(j + gap));
list.set(j + gap, temp);
}
}
}
}
long endTime = System.currentTimeMillis();
System.out.println("希尔排序(交换法)10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
/** 交换法*/
public static List<Integer> shellSort2(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
for (int gap = list.size() / 2; gap > 0; gap /= 2) {
// gap每轮的步长
for (int i = gap; i < list.size(); i++) {
int j = i;
// 定义即将插入的元素变量
int insertVal = list.get(j);
// while 循环找到对应的位置
while (j - gap >= 0 && insertVal < list.get(j - gap)) {
list.set(j, list.get(j - gap));
j -= gap;
}
// 退出循环说明找到位置
list.set(j, insertVal);
}
}
long endTime = System.currentTimeMillis();
System.out.println("希尔排序(移位法)10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
Quick
/**
* 快速排序:是对冒泡排序方法的一种改进。其基本的思想是,通过一趟排序,将要排序的部分分割成独立的两部分,保证 其中一部分的数据比另一部分的数据都要大
* 再对这两部分数据进行相同的排序。 此程序递归的进行 完成后即排序完成
*/
public static List<Integer> quickSort(List<Integer> list, int left, int right) {
// 定义左右下标 以及中间变量
int l = left;
int r = right;
int pivot = list.get((left + right) / 2);
// while 循环的目的 让比pivot小的值放到左边
while (l < r) {
// 在中轴左边循环找 找到比中轴值大的 退出
while (list.get(l) < pivot) {
l += 1;
}
// 在中轴的右边循环找 找到比中轴小的值 退出
while (list.get(r) > pivot) {
r -= 1;
}
// 退出循环的条件 如果左下表移动到 和 右边的下标重合 说明 左边的值 已经全部小于右边的值 此次的循环退出
if (l >= r) {
break;
}
// 上两个while循环找到 能进行交换的值的时候 进行交换
int temp = list.get(l);
list.set(l, list.get(r));
list.set(r, temp);
// 存在当前交换完了的值可能等于 中轴值 防止出现中轴值 和 左右元素值相等的情况
if (list.get(l) == pivot) {
// 将下标又移位
r -= 1;
}
if (list.get(r) == pivot) {
l += 1;
}
}
if (l == r) {
l += 1;
r -= 1;
}
// 以下进行递归
if (left < r) {
quickSort(list, left, r);
}
if (l < right) {
quickSort(list, l, right);
}
return list;
}
Merge
/**
* @Description: 归并排序:利用归并的思想实现的排序,算法采用经典的分治思想,即将问题分解成一系列小问题,递归求解
* 分而治之
* @Author: WangYangjun
* @Date: 2020/9/27 22:08
* @param: list->待排序的集合
* @param: left->最左边的索引
* @param: mid->中间的索引
* @param: right->最右边的做因
* @param: temp->中间数组
*/
// 分的方法
public static void spilt(List<Integer> list, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2;
// 向左递归分解
spilt(list, left, mid, temp);
// 向右递归进行分解
spilt(list, mid + 1, right, temp);
// 递归调用合并方法
mergeSort(list, left, mid, right, temp);
}
}
// 以下是合并的方法
public static void mergeSort(List<Integer> list, int left, int mid, int right, int[] temp) {
// 初始化参数
int i = left; // 左边初始下标
int j = mid + 1; // 右边的初始下标
int t = 0; // 指向temp数组的下标
// 1. 先将两边分开的数组 按照规则比较 依次放入到temp数组中
while (i <= mid && j <= right) { // 说明还没有结束循环到达终止条件->即一边操作完毕
if (list.get(i) <= list.get(j)) {
temp[t] = list.get(i);
i += 1;
} else {
temp[t] = list.get(j);
j += 1;
}
t += 1;
}
// 2. 直到两边有一边操作完毕 结束
// 3. 将剩余的放入temp 考虑左边 右边的剩余的情况
while (i <= mid) {
temp[t] = list.get(i);
t += 1;
i += 1;
}
while (j <= right) {
temp[t] = list.get(j);
t += 1;
j += 1;
}
// 4. 将temp拷贝到原来的数组
t = 0; // 将t置为0
int tempLeft = left;
while (tempLeft <= right) {
list.set(tempLeft, temp[t]);
t += 1;
tempLeft += 1;
}
}
Radix
/**
* 基数排序(桶排序):
* 1. radixSort属于分配式排序,又称为桶子法,其核心思想是将各个数的每个位拿出来,单独进行排序。个位-最高位
* 2. 位数不够使用0 进行替代 这样的话 使用十个数组(代表0-9) 最后一轮排完 取出后 就是有序
* 3. 思考如果包含负数呢: 可以找到最小值 取其绝对值 将所有的数值进行+绝对值 全部转换为整数进行排序 !
* 4. 基数排序呢 是最稳定的排序,而实是效率最高的稳定排序
* 5. 它是桶排序的扩展 经典的空间换取时间的算法 所以呢,它的风险点就是 会占用很大的内存空间 所以当数据量特别大的时候
* 需要注意,内存溢出
*/
public static List<Integer> radixSort(List<Integer> list) {
// 时间测试
long startTime = System.currentTimeMillis();
// 循环找到集合中最大值 得到位数 定义 方法体的循环次数
Integer max = list.stream().max((ele1, ele2) -> ele1 > ele2 ? 1 : -1).get();
int counts = (max + "").length();
// 定义一个二位数组 表示存放的 10个桶
int[][] buckets = new int[10][list.size()]; // 最坏的情况就是 所有元素 同一位 数字相同 全放入一个桶
// 定义一个一维数组存放 每个桶中对应元素数量数量 即下标
int[] eleCounts = new int[10];
for (int a = 0, n = 1; a < counts; a++, n *= 10) { // 定义n 的到每次除数 接着进行取余
// 循环进行处理 拿出每位数 放到对应的桶
for (int i = 0; i < list.size(); i++) {
// 取出每位
int digitOfEle = list.get(i) / n % 10;
// 放入对应的桶中
buckets[digitOfEle][eleCounts[digitOfEle]] = list.get(i);
eleCounts[digitOfEle]++;//表示该个桶 的元素数量
}
// 按照桶中元素的顺序去除数字 放回集合
int index = 0; // 新集合的索引
for (int j = 0; j < eleCounts.length; j++) {
if (eleCounts[j] != 0) { // 对应的一维数组个数不为0 才说明有值
for (int k = 0; k < eleCounts[j]; k++) {
list.set(index++, buckets[j][k]);
}
}
eleCounts[j] = 0;// 遍历完成需要将 eleCounts 数组置为空
}
}
long endTime = System.currentTimeMillis();
System.out.println("基数排序10000个数据排序消耗总时间是:" + (endTime - startTime) + "ms");
return list;
}
堆排序
待我看完二叉树,会补上相关内容。
未完待续…
万丈高楼平地起,勿以浮沙筑高台
posted on 2020-10-03 13:48 王泱钧的Java小站 阅读(139) 评论(0) 收藏 举报
浙公网安备 33010602011771号