(排序4)堆排序与冒泡排序

堆排序

  1. 在这里插入图片描述
  2. 堆排序的话其实就是把数组看成一颗完全二叉树,因为我们事先就知道数组的话可以等价于一颗完全二叉树,然后再把数组看成完全二叉树的基础之上,在给这个完全二叉树进行建堆,比如说我需要升序排列的话那我应该要建一个大根堆。
  3. 然后堆排序,他实际上就是利用了堆的一个性质:堆顶的数据是整个堆当中数据的最值。他实际上就是在不断的选数,将合乎要求的数放到数组尾巴开始。
  4. 堆(数组)里面不能随便的去挪动数据,因为一旦挪动数据的话,这个完全二叉树里面的父子关系就会全乱掉。所以说比如说在往堆里面进行插入数据或者删除数据之类,比如说插入的话是先把它插入到数组的尾巴,然后再向上调整;比如说删除数据的话,就是把数组最后一个元素与堆顶元素换一下位置,然后把堆顶元素向下调整。实际上都没有随意挪动数据。
  5. 需要注意的是排升序要建大堆,排降序建小堆
  6. 首先是两个万金油
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustUp(int* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(arr + child, arr + parent);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

void AdjustDown(int* arr, int parent, int n)
{
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child] > arr[child + 1])
		{
			child++;
		}
		if (arr[parent] > arr[child])
		{
			Swap(arr + child, arr + parent);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
  1. 具体堆排序(建堆+调堆)这个堆排序当中的调堆的end可以把它理解成在接下来此时此刻即将要换到堆顶上面的一个数据。
void HeapSort(int* arr, int n)
{
	//建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n);
	}
	//调堆
	int end = n - 1;
	while (end > 0)
	{
		Swap(arr, arr + end);
		AdjustDown(arr, 0, end);
		end--;
	}
}

堆排序时间复杂度

  1. 堆排序的话,除了一开始要把原始数据(是数组或者说是完全二叉树)建堆之后,还要去转数据调堆。对于第一步,复杂度是O(N)。
  2. 然后调堆的时间复杂度也是logN*N,用错位相减法减减算算也很快的,或者说你用直观的方法去看一下,对于最后一层而言,如果说你想要确定最后一层(堆排序的话,它是确定的顺序是从数组的从后往前慢慢确定下来的),要确定最后一层的话,就需要不断的把堆顶的数据给往下调整,这就相当于就是说节点最多的层数,它需要调整的次数也是最多。
  3. 因此对于堆排序而言的话,就是说你在一开始对数组(完全二叉树)建堆的时候,无论是用什么方法去建堆,最后总的堆排序时间复杂度都是O(N*logN)
    在这里插入图片描述

冒泡排序复习优化与时间复杂度

  1. 首先比如说用n个数据的话,因为冒泡排序,每一趟确定一个数据,因此总共需要n-1趟。然后对于每一趟而言,去画个图找一下下标的规律关系就OK了,非常简单。
  2. 冒泡排序的话,最坏的情况时间复杂度是O(N^ 2),是一个标准的等差数列。然后如果是纯朴素的冒泡排序的话,也就是说没有进行任何的优化,最好的情况也是O(N^2),因为两个for循环都必须得全部走完嘛。
  3. 然后好一点的情况的话,就对这个冒泡排序进行一些优化就可以。就是说如果说在某一趟的过程当中没有发生任何的交换,这就说明这个数据已经是全部都有序了,因为但凡不是有序的话都会发生交换。
  4. 然后当这个优化完了之后,这个冒泡排序最好的情况就是O(N)
  5. 没优化的
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j <= (n - i - 2); j++)
		{
			if (arr[j] > arr[j + 1])
			{
				Swap(arr + j, arr + j + 1);
			}
		}
	}
}
  1. 优化过的
void BubbleSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		bool exchange = false;
		for (int j = 0; j <= (n - i - 2); j++)
		{
			if (arr[j] > arr[j + 1])
			{
				exchange = true;
				Swap(arr + j, arr + j + 1);
			}
		}
		if (exchange == false)
		{
			break;
		}
	}
}

在这里插入图片描述

关于冒泡排序与直接插入排序与选择排序的时间复杂度比较

  1. 虽然他们三个排序算法的时间复杂度都是O(N^ 2),但是时间复杂度仅仅只是代表一个量级而已,在具体执行算法过程当中,性能差异还是会显现出来。
  2. 首先来看这个选择排序的话,无论怎么样都是铁打不动的O(N^ 2),效率是最慢的。
  3. 然后来看冒泡排序与直接插入排序,他们两个的话时间复杂度都是O(N^ 2),并且最好的情况下都是O(N).
  4. 但具体而言的话还是直接插入排序要更加性能好一点。首先,如果数组是全部有序的话,两者还是没有区别,都是O(N);
  5. 然后在接近有序的情况之下,差距非常小,还是直接插入排序更快一点。因为在接近有序的时候,对于插入排序而言,只需要在某些插入的过程之后,再把新插入的数据融入到原先的有序数组这么去大概操作一下。然后对于冒泡排序而言的话,对于第一趟,那是肯定要走的;然后第一趟走完的话,由于exchange是true,所以说势必会走第二趟,反正就是说如果说在某一趟走完之后,整个数组已经是有序的情况之下,对于冒泡排序而言,他还需要再去走一趟,走这一趟实际上是白走的,因为数组已经有序了。
  6. 差距体现的最大的就是在部分有序的情况之下,在部分有序的情况之下,对于冒泡排序而言,他对于部分有序并不敏感,仍然需要去走大量的趟数。然而对于插入排序而言,部分有序的存在就可以省略掉很多的调整过程。
  7. 在这里插入图片描述
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

絕知此事要躬行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值