【数据结构详解】——选择排序(动图详解)

🕒 1. 直接选择排序

💡 算法思想:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始(末尾)位置,然后选出次小(或次大)的一个元素,存放在最大(最小)元素的下一个位置,重复这样的步骤直到全部待排序的数据元素排完。
请添加图片描述

代码实现如下:这里可以进行一个优化,最小值和最大值同时选,然后将最小值与起始位置交换,将最大值与末尾位置交换。

void Swap(int* p1, int* p2)
{
    int tmp = *p1;
    *p1 = *p2;
    *p2 = tmp;
}

void SelectSort(int* a, int n)
{
    int begin = 0;  // 起始位置
    int end = n - 1;  // 结束位置

    // 循环直到整个数组都被排序
    while (begin < end)
    {
        int mini = begin;  // 保存最小元素下标
        int maxi = begin;  // 保存最大元素下标

        // 在当前未排序部分查找最小和最大元素的下标
        for (int i = begin + 1; i <= end; ++i)
        {
            if (a[i] < a[mini])
            {
                mini = i;  // 更新最小元素下标
            }
            if (a[i] > a[maxi])
            {
                maxi = i;  // 更新最大元素下标
            }
        }

        // 将找到的最小元素交换到起始位置
        Swap(&a[begin], &a[mini]);

        // 如果最大元素的位置在起始位置,更新最大元素下标为 mini
        if (maxi == begin)
        {
            maxi = mini;
        }

        // 将找到的最大元素交换到末尾位置
        Swap(&a[end], &a[maxi]);

        // 缩小排序范围
        ++begin;
        --end;
    }
}

在这里插入图片描述

选择排序的特性总结:

  1. 选择排序步骤非常好理解,但是效率不是很好(不论数组是否有序都会执行原步骤),实际中很少使用。
  2. 时间复杂度:O(N2)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

🕒 2. 堆排序

💡 算法思想:堆排序即利用堆的思想来进行排序,总共分为两个步骤:1. 建堆升序:建大堆;降序:建小堆) 2. 利用堆删除思想来进行排序:建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

这里以升序为例:

  • 首先应该建一个大堆,不能直接使用堆来实现。可以将需要排序的数组看作是一个堆,但需要将数组结构变成堆结构。
  • 我们可以从堆从下往上的第二行最右边开始依次向下调整直到调整到堆顶,这样就可以将数组调整成一个堆,且如果建立的是大堆,堆顶元素为最大值。
  • 然后按照堆删的思想将堆顶和堆底的数据交换,但不同的是这里不删除最后一个元素。
  • 这样最大元素就在最后一个位置,然后从堆顶向下调整到倒数第二个元素,这样次大的元素就在堆顶,重复上述步骤直到只剩堆顶时停止。

请添加图片描述

// AdjustDown函数:在数组a中,从节点root开始向下调整,使得以root为根的子树满足大顶堆的性质。
void AdjustDown(int* a, int n, int root)
{
	assert(a);
	int parent = root; // 当前子树的根节点
	int child = parent * 2 + 1; // 左孩子节点

	// 循环直到没有孩子节点
	while (child < n)
	{
		// 如果右孩子存在且比左孩子大,则选择右孩子作为比较对象
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}

		// 如果孩子节点比父节点大,则交换父节点和孩子节点的值,并更新父节点和孩子节点继续向下比较
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break; // 如果孩子节点不再比父节点大,则退出循环
		}
	}
}

void HeapSort(int* a, int n)
{
	assert(a);
 
	// 建立大顶堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i); // 对每个非叶子节点进行向下调整,建立大顶堆
	}
 
	// 交换堆顶元素和末尾元素,并重新调整堆
	for (int i = n - 1; i > 0; i--)
	{
		Swap(&a[i], &a[0]); // 将当前堆顶(最大值)与数组末尾元素交换
		AdjustDown(a, i, 0); // 调整剩余堆为大顶堆,范围缩小为0到i-1
	}
}

在这里插入图片描述

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率较高,适用于需要频繁插入和删除的场景。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

❗ 转载请注明出处
作者:HinsCoder
博客链接:🔎 作者博客主页

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值