[数据结构]选择排序、堆排序

文章目录

  • 选择排序
  • 堆排序
    • 堆的概念及结构
    • 堆向下调整算法
    • 堆排序的实现
  • 性能测试

选择排序

基本思想:

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素

动图演示:

[数据结构]选择排序、堆排序_第1张图片

动图演示的是每次选出集合中最小的数,我们可以进行优化,每次从集合中选出最小和最大的数,分别与第一个数和最后一个数交换,这样可以每趟排序可以排好两个数,效率提高一倍。

参考代码:

// 选择排序
void SelectSort(int* a, int n)
{
	// 一次选两个数,最大和最小插入到一前一后
	int left = 0, right = n - 1;
	while (left < right) 
	{
		int mini = left;
		int maxi = left;
		for (int i = left; i <= right; ++i)
		{
			if (a[i] > a[maxi])
				maxi = i;
			if (a[i] < a[mini])
				mini = i;
		}
		Swap(&a[mini], &a[left]);
		if (left == maxi)
			maxi = mini;
		Swap(&a[maxi], &a[right]);

		left++;
		right--;
	}
}

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

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

堆排序

堆的概念及结构

堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。

[数据结构]选择排序、堆排序_第2张图片

堆向下调整算法

堆排序的第一步是建堆,建堆的方法有向上调整算法和向下调整算法,我们这里以向下调整算法建小堆为例:

  1. 向下调整算法的前提是两边子树必须全是大堆或者小堆
  2. 根据要调整的结点下标算出左孩子和右孩子的下标
  3. 比较左孩子和右孩子,取较小的值与要调整的结点值进行比较
  4. 如果较小的孩子结点的值比父亲结点的值要小,那就需要交换较小孩子的结点和父亲结点的值,并将较小孩子的值作为新的要向下调整的结点,继续向下调整知道叶子结点
  5. 如果较小的孩子结点的值比父亲结点的值要大,那就无需向下调整

建小堆参考图:

[数据结构]选择排序、堆排序_第3张图片

参考代码:

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 右孩子存在且右孩子比孩子小
		if (child + 1 < n && a[child + 1] < a[child])
		{
			child++;
		}

		if (a[parent] > a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

堆排序的实现

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

建堆参考图:

[数据结构]选择排序、堆排序_第4张图片

堆排序的思想:

  1. 将需要排序的数组建成大根堆或者小根堆
  2. 利用堆删除的思想来进行排序,循环已经建成堆的数组,交换堆顶和最后一个数,类似于删除堆顶元素,将交换后的堆顶向下调整,形成新的堆,依次循环,就可以达到排序的目的

堆排序参考图:

[数据结构]选择排序、堆排序_第5张图片

参考代码:

void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 右孩子存在且右孩子比孩子小
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;
		}

		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

// 堆排序
void HeapSort(int* a, int n)
{
	// 建堆
	for (int i = (n - 2) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	// 选堆顶和最后的数交换,类似于删除堆顶
	// 新的堆顶再向下调整
	for (int i = n - 1; i > 0; --i)
	{
		Swap(&a[0], &a[i]);
		AdjustDown(a, i, 0);
	}
}

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

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

性能测试

测试程序:

// 测试排序的性能对比
void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];

	}
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	free(a1);
	free(a2);
	free(a3);
	free(a4);
}

测试结果:

[数据结构]选择排序、堆排序_第6张图片

你可能感兴趣的:(数据结构,排序算法,算法)