(1)前期准备参考:代码环境准备
(2)空间复杂度 & 原地算法
空间复杂度:
算法执行所需要的临时空间
原地算法 in-place:
不依赖额外的资源或者依赖少数的额外资源,仅依靠输出来覆盖输入
空间复杂度为O(1)的可以认为是原地算法
非原地算法,称为 Not-in-place 或者 Out-of-place
开局一张图
排序过程动态gif
冒泡
经典冒泡
public class _01_Bubble <T extends Comparable<T>> extends Sort<T> {
@Override
public void sort() {
for(int end = array.length -1; end> 0;end--){
for(int cur = 0;cur < end;cur++){
if(cmp(array[cur] , array[cur+1]) > 0){
swap(cur,cur+1);
}
}
}
}
}
- end的定义 :[0,end]范围内都是无序的;当前安排 [0,end]范围内最大元素放在end位置;
- 在完成当前轮排序前:[0,end]是无序的, (end,length-1]是有序的
完成当前轮排序后:[0,end)是无序的, [end,length-1]是有序的
冒泡优化1
public class _01_Bubble_2 <T extends Comparable<T>> extends Sort<T> {
/**
* 优化思路1:
* [0,end] 是乱序的 (end,length-1] 是有序的
* 如果 cur 在遍历[0,end)的过程中,没有发生swap(cur,cur+1)操作,表示[0,end]数据是有序的
* 此时,end不需要 end-- 迭代了,直接结束程序
*/
@Override
public void sort() {
for(int end = array.length -1; end> 0;end--){
boolean sorted = true;
for(int cur = 0;cur < end;cur++){
if(cmp(array[cur] , array[cur+1]) > 0){
swap(cur,cur+1);
sorted = false;
}
}
if(sorted) break;
}
}
}
这种优化本质上并没有减少冒泡排序的交换次数,只是在达到有序后,提前终止了循环,减少了循环的轮数;
冒泡优化2
public class _01_Bubble_3<T extends Comparable<T>> extends Sort<T> {
/**
* 优化思路:
* 记录下[0,end)最后一次交换的位置 index, [index,length-1]都是有序的
* 从而实现从end-- 直接跳跃到 index
* 减少循环次数
*/
@Override
protected void sort() {
for (int i = array.length - 1; i > 0; i--) {
int end = 1;
for(int j = 0;j< i;j++){
if(cmp(array[j], array[j + 1]) > 0){
swap(j,j+1);
end=j+1;
}
}
i = end;
}
}
}
这种优化本质上也没有减少冒泡排序的交换次数,只是实现了end 跨越,从而减少了比较次数;
冒泡排序的复杂度和稳定性
时间复杂度
- 最坏:O(n2) 完全逆序 从大到小
- 最好:O(n) 完全正序(优化后的冒泡排序,一轮后直接结束程序;如果是经典的,还需要比较,仍然为O(n2))
- 平均:O(n2)
空间复杂度
- 空间复杂度:O(1)
- 冒泡排序属于 In-place
- 冒泡排序属于稳定的排序算法
稳定性
冒泡排序的稳定性取决于 发生交换的判决条件:
if(cmp(array[j], array[j + 1]) > 0){
swap(j,j+1);
}
- 如果是> 交换,那么两个相等的元素就不会进行交换,在后面的会先放到end,因此是稳定的;
- 如果是>= 交换,那么两个相等的元素会发生交换,破坏了稳定性;
选择排序
- [0,end]是无序区,每轮在无序区找到最大元素的索引,和end位置的元素进行交换;
- 优势:每一轮end 只发生一次交换操作
public class _02_Select<T extends Comparable<T>> extends Sort<T> {
@Test
public void sort(){
for (int end = array.length -1; end > 0; end--) {
int maxIndex = 0;
for(int cur = 1;cur<=end;cur++){
if(cmp(array[cur],array[maxIndex]) >= 0){
maxIndex = cur;
}
}
swap(maxIndex,end);
}
}
}
选择排序的复杂度和稳定性
时间复杂度
- 最坏:O(n2)
- 最好:O(n2)
- 平均:O(n2)
空间复杂度
空间复杂度:O(1)
冒泡排序属于 In-place
冒泡排序属于稳定的排序算法
稳定性
冒泡排序的稳定性取决于 发生交换的判决条件:
if(cmp(array[cur],array[maxIndex]) >= 0){
maxIndex = cur;
}
如果是>,那么假设有两个最大元素,会将第一个元素放到end位置,因此是不稳定的;
如果是>= ,那么假设有两个最大元素,会将最后面一个元素放到end位置,因此是稳定的;
main方法测试验证:
public static void main(String[] args) {
/*
count:测试的数据量
min:最小值
max:最大值
*/
Integer[] array = Integers.random(20000, 1, 10000);
// 在这里面写要测试的代码
testSorts(array,
new _01_Bubble(),
new _01_Bubble_2(),
new _01_Bubble_3(),
new _02_Select()
);
}
【_02_Select】
稳定性:true 耗时:0.4s(400ms) 比较:2.00亿 交换:2.00万
------------------------------------------------------------------
【_01_Bubble_2】
稳定性:true 耗时:1.845s(1845ms) 比较:2.00亿 交换:9987.16万
------------------------------------------------------------------
【_01_Bubble_3】
稳定性:true 耗时:1.93s(1930ms) 比较:2.00亿 交换:9987.16万
------------------------------------------------------------------
【_01_Bubble】
稳定性:true 耗时:2.504s(2504ms) 比较:2.00亿 交换:9987.16万
------------------------------------------------------------------
这里选择排序用的是>=判断条件,可以看出是稳定的;
选择排序因为交换次数远小于冒泡,因此效率高很多;
堆排序
图解可参考:
https://blog.csdn.net/weixin_43734095/article/details/105108135
public class _03_HeapSort<T extends Comparable<T>> extends Sort<T> {
private int heapSize; // 堆大小
@Override
protected void sort() {
heapSize = array.length;
for (int i = (heapSize - 2) >> 1; i >= 0; i--) {
siftDown(i);
}
while (heapSize > 1) {
// 交换堆顶元素和尾部元素
swap(0, --heapSize);
// 对0位置进行siftDown(恢复堆的性质)
siftDown(0);
}
}
public void siftDown(int index) {
T old = array[index];
while (index <= (heapSize - 2) >> 1) {
int leftIndex = (index << 1) + 1;
int rightIndex = leftIndex + 1;
int maxIndex = leftIndex;
if (index <= (heapSize - 3) >> 1) {//左右都有
maxIndex = cmp(array[leftIndex], array[rightIndex]) > 0 ? leftIndex : rightIndex;
}
if (cmp(array[maxIndex], old) > 0) {
array[index] = array[maxIndex];
index = maxIndex;
} else {
break;
}
}
array[index] = old;
}
}
堆排序的复杂度和稳定性
堆排序:先对无序序列原地建堆,有了堆以后,可以直接选出最大值;
将最大值和elements[size-1]互换,再对elements[0]进行下滤;
最好、最坏、平均时间复杂度: O(nlogn)
空间复杂度:O ( 1 )
堆排序属于不稳定排序