数据结构与算法 排序算法(二)
Tips: 采用java语言, 关注博主,底部附有完整代码
本篇介绍排序算法:
- 希尔排序(选择法/交换法)
- 希尔排序(位移法)
- 归并排序(分治排序)
- 快速排序
- 自定义排序
算法名称 | 10万次数据耗时 | 效果图 |
---|---|---|
希尔排序 | 选择法 10万次耗时约 20秒 位移法10万次耗时约 19毫秒 | ![]() |
归并排序 | 10万次耗时约 20毫秒 | ![]() |
快速排序 | 10万次耗时约17毫秒 | ![]() |
自定义排序 | 10万次耗时约7秒左右 |
希尔排序(选择法/交换法)
重点:
-
倒叙输出结果 (根据步长来倒叙)
-
交换
-
默认之前的数 始终是有序的
-
始终和前面的数相比较
原理:
假设现在有10个数据,依次隔 length / 2次来排序
-
第一次排序 隔5个元素来排序 (10 / 2 = 5)
-
第二次排序 隔2个元素来排序(5 / 2 = 2)
-
第三次排序 隔1个元素来排序(2 / 2 = 1)
这样一来,就能将相邻的数字替换到大致位置, 减少了交换的次数
效果图:
倒叙输出结果 (根据步长来倒叙)
交换
交换和上一篇中的冒泡排序交换一样
根据步长倒叙排序
这段代码稍微有点复杂,多看几次就懂了!!
这段代码懂了,在看希尔排序代码就十分清晰了!
完整代码
# 希尔排序(选择法/交换法)
// ints数据: 12, 4, 16, 1, 20, 21, 5, 8, 10, 3
public static void method2(int[] ints) {
int count = 0;
// step = 步长
for (int step = ints.length / 2; step > 0; step /= 2) {
// 排序次数
for (int i = step; i < ints.length; i++) {
// 交换
for (int j = i - step; j >= 0; j -= step) {
// 当前元素 > 前一个元素 就交换位置
if (ints[j] > ints[j + step]) {
int temp = ints[j + step];
ints[j + step] = ints[j];
ints[j] = temp;
}
}
}
count++;
System.out.println("第" + (count) + "次排序,步长为" + step + " 结果为:" + Arrays.toString(ints));
}
System.out.println("希尔排序选择法(交换法)最终结果为:" + Arrays.toString(ints));
}
最终结果为:
第1次排序,步长为5 结果为:[12, 4, 8, 1, 3, 21, 5, 16, 10, 20]
第2次排序,步长为2 结果为:[3, 1, 5, 4, 8, 16, 10, 20, 12, 21]
第3次排序,步长为1 结果为:[1, 3, 4, 5, 8, 10, 12, 16, 20, 21]
希尔排序选择法(交换法)最终结果为:[1, 3, 4, 5, 8, 10, 12, 16, 20, 21]
优缺点分析
优点:
- 代码变复杂了,看着像是一位高手
缺点:
- 10个数据排序时间和 冒泡排序 接近…
- 存在大量毫无意义的交换
- 代码变复杂,时间还是很久, 没卵用.
希尔排序(位移法)
位移法和交换法逻辑几乎相同只是:
- 位移法 类似于插入排序,没有大量的交换 节省时间
- 交换法 采用大量的交换 浪费时间
完整代码
# 希尔排序(位移法)
public static void method3(int[] ints) {
System.out.println("希尔排序 - 位移法");
System.out.println("原始数据" + Arrays.toString(ints));
long oldTime = System.currentTimeMillis();
// step = 步长
for (int step = ints.length / 2; step > 0; step /= 2) {
for (int i = step; i < ints.length; i++) {
// 当前数据
int currentValue = ints[i];
int j;
// 倒叙 每一个元素
// ints[j] 倒叙后的每一个元素
for (j = i - 1; j >= 0 && currentValue < ints[j]; j--) {
// 元素后移 给需要插入的元素腾位置
ints[j + 1] = ints[j];
}
// 需要插入的元素 = currentValue
ints[j + 1] = currentValue;
}
System.out.printf("步长为%d,当前数据 =%s\n", step, Arrays.toString(ints));
}
System.out.println("希尔排序-位移法:" + Arrays.toString(ints));
}
结果:
希尔排序 - 位移法
原始数据[12, 4, 16, 1, 20, 21, 5, 8, 10, 3]
步长为5,当前数据 =[12, 4, 16, 1, 3, 5, 8, 10, 20, 21]
步长为2,当前数据 =[1, 3, 12, 4, 5, 8, 10, 16, 20, 21]
步长为1,当前数据 =[1, 3, 4, 5, 8, 10, 12, 16, 20, 21]
希尔排序-位移法:[1, 3, 4, 5, 8, 10, 12, 16, 20, 21]
优缺点分析
优点:
- 省时 10万次数据约19毫秒
归并排序(分治排序)
重点:
- 合并两个有序数组
- 递归
原理:
归并排序一共有2步
- 归(分)
- 并(治)
归(分): 将数组无限拆分,直到数组中有1个值,或者2个值,然后依次排序判断
**并(治)😗*是将一个数组分一分为二为2个数组
- 数组1
- 数组2
数组1和数组2都是有序的,然后根据两个指针依次合并到一个数组中,最终就完成了排序
来看看动画图:
合并两个数组
这段代码还是很简单的
那如果要是先插入一个ints1,在插入一个ints2,这样依次插入该怎么操作呢?
可以看出,根据自己写的数据,就可以实现先插入一个ints1 , 在插入一个 ints2
看懂这段代码,在来看归并排序就简单了!
递归
// 测试递归
testRecursion(100, 0);
private static void testRecursion(int num, int position) {
if (num >= 1) {
testRecursion(num / 2, ++position);
}
System.out.println("num:" + num + "\tposition = " + position);
}
结果:
num:0 position = 7
num:1 position = 7
num:3 position = 6
num:6 position = 5
num:12 position = 4
num:25 position = 3
num:50 position = 2
num:100 position = 1
完整代码
# 归并排序(并)的代码
private static void mergeSort(int[] ints, int low, int middle, int high) {
// 并(合并操作) 操作
int firstStart = low;
int firstEnd = middle;
int secondStart = middle + 1;
int secondEnd = high;
// 临时位置
int[] temp = new int[high - low + 1];
int index = 0;
// 第一个指针是否到最后 && 第二个指针是否到最后
while (firstStart <= firstEnd && secondStart <= secondEnd) {
// 左侧的元素 比右侧的小
if (ints[firstStart] <= ints[secondStart]) {
// 添加左侧的元素到集合中
temp[index] = ints[firstStart];
firstStart++;
} else {
// 添加右侧的元素到集合中
temp[index] = ints[secondStart];
secondStart++;
}
index++;
}
// first数组 有剩余
while (firstStart <= firstEnd) {
temp[index] = ints[firstStart];
firstStart++;
index++;
}
// second数组 有剩余
while (secondStart <= secondEnd) {
temp[index] = ints[secondStart];
secondStart++;
index++;
}
// 重新给 ints 赋值
for (int i = 0; i < temp.length; i++) {
ints[i + low] = temp[i];
}
// System.out.println("现在元素:" + Arrays.toString(ints));
}
# 归并排序(归)的代码
private static void sort(int[] ints, int low, int high) {
// 归(分) 操作
// 中间位置
int middle = (high + low) / 2;
if (low < high) {
// first 排序
sort(ints, low, middle);
// second 排序
sort(ints, middle + 1, high);
// 开始排序
mergeSort(ints, low, middle, high);
}
}
// 调用归并排序
sort(ints, 0, ints.length - 1);
优缺点分析:
优点:
- 节省时间 10万次数据平均耗时20毫秒
缺点:
- 需要开启新的空间
- 需要用到递归,增加了代码难度
- 需要写2个方法
- 归(递归)
- 并
快速排序
重点:
- 递归
- 开始位置,结束位置,临时位置
原理:
在数组中找3个位置
- 开始位置
- 结束位置
- 临时位置
假设目前是从小到大排序
,那么
- 开始位置就是最小的值
- 结束位置就是最大的值
如果开始位置 > 临时位置 那么 结束位置 = 开始位置
如果结束位置 < 临时位置 那么 开始位置 = 结束位置
如果 开始位置 = 结束位置 那么 临时位置 = (开始位置/结束位置)
看一眼流程图:
开始位置,结束位置,临时位置
第一次排序时
- 开始位置 = 0
- 结束位置 = ints.length -1
- 临时位置 = 0 (默认临时位置就是开始位置)
完整代码
private static void sort(int[] ints, int left, int right) {
// 如果 left >= right 则不排序
if (left >= right) {
return;
}
// 最左侧下标
int start = left;
// 最右侧下标
int end = right;
// 临时比较的位置
int tempValue = ints[start];
// 左侧索引 < 右侧索引
// 比 middleValue 大的 放到右侧
// 比 middleValue 小的 放到左侧
while (start < end) {
// 从右侧开始找比 tempValue 小的数
while (start < end && ints[end] >= tempValue) {
end--;
}
// 开始位置 = 结束位置
ints[start] = ints[end];
// 从左侧开始找比 tempValue 大的数
while (start < end && ints[start] <= tempValue) {
start++;
}
// 结束位置 = 开始位置
ints[end] = ints[start];
}
// 此时 start = end
ints[start] = tempValue;
// 递归 开始排序左侧
sort(ints, left, start);
// 递归 开始排序右侧
sort(ints, start + 1, right);
}
优缺点分析
优点:
- 耗时短 10万次数据耗时17秒左右
缺点:
- 代码稍微复杂一点
- 使用到了递归
自定义排序
这是我自己想到的一个排序规则,不算算法,但是比冒泡快
完整代码
for (int i = 0; i < ints.length - 1; i++) {
if (ints[i] > ints[i + 1]) {
// 交换 。。
int temp = ints[i];
ints[i] = ints[i + 1];
ints[i + 1] = temp;
// 如果交换了 就退2个
if (i >= 2) {
i -= 2;
} else {
// 防止前两个数据下标越界
i = -1;
}
}
}
思路很简单, 就是如果交换了的话,让重新for,直到最后循环完毕
10万个数据耗时 7秒左右
上一篇:数据结构与算法 排序算法(一) 附有详细动画流程分析图
原创不易,您的点赞就是对我最大的支持!