【排序算法】第二章:选择排序----普通选择排序与堆排序的详解和对比

🫡和我一起感受 两种排序算法的魅力吧!
在这里插入图片描述

前言:本文可能稍微涉及到一点其他排序算法,若想要了解可以看看:第一章:插入排序`

【下面用到的:随机数生成测试排序性能器的代码




一、普通选择排序


注意下面几种写法的 Max 和 Min 指的都是 元素下标,不是元素本身的值



1.1.一般写法

这种写法,我认为是最好理解的 排序算法了,直接暴力遍历找最值交换即可,思路很直接

void SelectSort(int* a, int n) {
	for (int i = 0; i < n - 1; ++i) {
		int Min = i;
		for (int j = i + 1; j < n; ++j) {
			if (a[Min] > a[j]) Min = j;
		}
		Swap(&a[i], &a[Min]);
	}
}


1.2.一般写法动图演示

在这里插入图片描述



2.1.优化版本

优化思路:既然每一轮都要遍历数组,可以直接将 最大值和最小值 一起找出来,不用像一般写法:每一轮只找出一个 最值,效率较低

每轮找到最大值 和 最小值 就和 头尾交换即可

2.2.优化版本的错误写法
void SelectSort2(int* a, int n) {
	int begin = 0, end = n - 1;
	while (begin < end) {
		int Min = begin, Max = end;
		for (int i = begin + 1; i <= end; ++i) {
			if (a[i] < a[Min]) Min = i;
			if (a[i] > a[Max]) Max = i;
		}
		Swap(&a[end], &a[Max]);
		Swap(&a[begin], &a[Min]);
		
		begin++;
		end--;
		Print(a, 10);
	}
}

比如下面这种情况

注意: Max 和 Min 指的都是 元素下标,不是元素本身的值

遍历一遍记录最值下标是 Max == 8, Min == 9

在这里插入图片描述

若 直接交换:Swap(a[Max], a[end]) : {2, 5, 8, 1, 4, 7, 3, 6, 0, 9}

可以发现,原本的 Min == 9 的位置,数值变成了 a[Min] = 9 ??!

此时若直接进行:Swap(a[Min], a[begin]) 必然会出错

⭐原因:Min 的位置 和 Max 要交换的目标位置 end 重叠了

⭐方案:加一个 if 语句判断:若 Min == end 则 Min = Max 代表 Min 变成 Max 交换后的位置,就是原有的 Min

if(Min == end) {
    Min = Max;
}

反过来一样:

若先进行 Swap(a[Min], a[begin]); 则 要判断

if(Max == begin) {
    Max = Min;
}


2.3.优化版本的正确写法:
void SelectSort2(int* a, int n) {
    int begin = 0, end = n - 1;
    while (begin < end) {
        int Min = begin, Max = end;
        for (int i = begin + 1; i <= end; ++i) {
            if (a[i] < a[Min]) Min = i;
            if (a[i] > a[Max]) Max = i;
        }

        Swap(&a[end], &a[Max]);
        if(Min == end) {
            Min = Max;
        }
        Swap(&a[begin], &a[Min]);


        begin++;
        end--;
        Print(a, 10);
    }
}


3.1.一般的选择排序 和 优化后选择排序(以及其他几种排序)的性能对比

下面我用代码随机生成 100000 个数据,执行几个排序算法,显示的数据为 时间,以 ms 为单位
【上面用到的:随机数生成测试排序性能器的代码

在这里插入图片描述

可以看到 一般选择排序 和 优化选择排序的在 十万数据量的差别较小,但是还是有一定优化的

(另外说一下:冒泡“真强”,希尔真强 doge)


直接选择排序的特性总结:

  1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定



二、堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

在这里插入图片描述

想要升序排列,应该使用 大根堆
而不是常理所想的小根堆:常理中,想要升序,top 应该是 最小值才对,但是….
若使用小根堆 的堆排序
每轮排序出 top 为 最小值,和 最后一个元素交换:
依此类推 从后往前的序列元素分别是: 第 n 小的、第 n-1 小的 … 第 2 小的、第 1 小的
反而是 降序排序
因此,依照堆排序的原理,排序应该反着来
// 堆排序

void HeapSort(HPDataType* a, int n)
{
	// 建堆
    // 因为 Parent = (child - 1) / 2
    // 需要从最后一个节点的父节点开始向前循环比较
    // 最后一个节点的下标是 (n-1)
    // 则 最后一个节点的父节点 是 (  (n - 1)   - 1) / 2
	for (int i = ((n - 1) - 1) / 2; i >= 0; --i) {
		AdjustDown(a, n, i);
	}
	// 单独一个元素向下调整时间复杂度为 O(logN)  一共调整约 N 轮,时间复杂度为 O(NlogN)
	int end = n-1;
	while (end > 0) {
		Swap(&a[0], &a[end]);   // 把处在第一位置的最优值 和 最后位置的值替换
		AdjustDown(a, end, 0);  // 再 将 a[0] 进行向下调整
		end--;
	}
}


void AdjustDown(HPDataType* a, int size, int Parent) {
	// 孩子取较大的那个: 为了结果为 升序排列
	int LeftChild = Parent * 2 + 1;
	int RightChild = Parent * 2 + 2;
	int child = (RightChild < size && a[LeftChild] < a[RightChild]) ? RightChild : LeftChild;
	// 注意:这里必须加上:RightChild < size :因为 若 RightChild 越界,越界的值是随机的,也是可能大于另一方的,也可能被计算进去,
	// 当 child 为越界下标时,下面的循环就不能进去,导致较小的但是合法的 左孩子LeftChild 没有参与向下调整,则会漏掉一个值没有计算

	while (child < size && a[child] > a[Parent]) {  //  只要孩子 :child < size 说明孩子的下标就是合法的
		Swap(&a[child], &a[Parent]);
		Parent = child;
		LeftChild = Parent * 2 + 1;
		RightChild = Parent * 2 + 2;
		child = (RightChild < size && a[LeftChild] < a[RightChild]) ? RightChild : LeftChild;
	}
}

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定



三、选择排序 和 堆排序 的性能对比

下面我用代码随机生成 100000 个数据,执行两个排序算法,显示的数据为 时间,以 ms 为单位
【上面用到的:随机数生成测试排序性能器的代码

在这里插入图片描述

可以见识到 堆排序 O(nlogn) 的威力了吧,(薄纱 O(n^2))




四、浅谈 各种排序算法的意义

上一章节讲解的 希尔排序 中提到

希尔排序时间复杂度大概在 O ( n 1.3 ) O(n^{1.3}) O(n1.3) 略大于 O ( n l o g n ) O(nlogn) O(nlogn)

我们在此 将 希尔排序 ( O ( n 1.3 ) O(n^{1.3}) O(n1.3) ) 和 堆排序 ( O ( n l o g n ) O(nlogn) O(nlogn)) 对比一下

【下面用到的:随机数生成测试排序性能器的代码
十万 数据量级在这里插入图片描述

千万 数据量级在这里插入图片描述

一亿 数据量级在这里插入图片描述

希尔排序 在后面数据量级升级后,反超了 堆排序,说明了一点

时间复杂度,看的是最坏的情况,有时候不能用于真正的速度对比,算法的运行速度和 算法思想、数据的实际情况等等相关

同时要注意,我们堆排序之前,还要进行 O(n) 的建堆,我们也不能忽略 O(n) 的影响



⭐总结:每个排序都有其存在的意义,不能只盯着排序算法的时间复杂度大小的比较,更多的是看适不适合当前实际的应用场景,,比如堆排序虽然时间复杂度较为优秀,但是,例如当数据基本有序时,使用 插入排序 都能秒了 堆排,不信?🤣 看下面

如图:我先用 希尔排序 将 数组 a2 排序成有序的,营造数据有序场景,来测试 插入排序 和 堆排序

在这里插入图片描述

😎😎谁更强一目了然!!




🫡至此,第二章完结!

【若文章有什么错误或则可以改进的地方,非常欢迎评论区讨论或私信指出】**

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,我可以为您解答关于Java实现常用的排序算法中的冒泡和选择排序。 首先,我们来看一下冒泡排序的实现。 冒泡排序的基本思想是比较相邻的元素。如果第一个比第二个大,就交换它们两个;否则不交换。对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这样一趟过去,最后的元素一定是最大的数。然后再从头开始进行相同的操作,但是不包括已经排序好的最大元素。以此类推,直到全部排序完成。 下面是Java代码实现: ```java public class BubbleSort { public static void main(String[] args) { int[] arr = {3, 9, 8, 5, 2}; bubbleSort(arr); for (int i : arr) { System.out.print(i + " "); } } public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n; i++) { for (int j = 1; j < n - i; j++) { if (arr[j - 1] > arr[j]) { int temp = arr[j - 1]; arr[j - 1] = arr[j]; arr[j] = temp; } } } } } ``` 接下来,我们看一下选择排序的实现。 选择排序的基本思想是每次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部排完序。 下面是Java代码实现: ```java public class SelectionSort { public static void main(String[] args) { int[] arr = {3, 9, 8, 5, 2}; selectionSort(arr); for (int i : arr) { System.out.print(i + " "); } } public static void selectionSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { int minIndex = i; for (int j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; } } int temp = arr[i]; arr[i] = arr[minIndex]; arr[minIndex] = temp; } } } ``` 希望这些代码可以帮助您更好地理解冒泡排序和选择排序的实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值