直接选择排序&堆排序

1.基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始(终止)位置,直到全部待排序的 数据元素排完 。

2.直接选择排序

1.思路

假设有一个含有n个元素的待排序数组,遍历一遍待排序数组,找出数组中最大的元素,将其与数组尾元素交换,然后继续遍历数组,这一次遍历(n-1)个元素,然后将最大的元素与数组倒数第二个元素交换,以此类推,直到遍历了n-1遍数组。

2.代码

#include <stdio.h>
void Swap(int* a, int* b)
{
	int t = *a;
	*a = *b;
	*b = t;
}
void SelectSort(int arr[], int n)
{
	for (int j = 0; j < n - 1; j++)
	{
		int maxi = 0;
		for (int i = 0; i < n - j; i++)
		{
			if (arr[i] >= arr[maxi])
			{
				maxi = i;
			}
		}
		Swap(&arr[maxi], &arr[n - j - 1]);
	}
	
}
int main()
{
	int arr[] = { 7, 6, 5, 4, 3, 2, 1, 9, 8 , 0};
	int n = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	SelectSort(arr, n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

3.优化

上面的直接选择排序,我们每遍历一遍数组,只选出了最大值,下面的代码是,我们遍历一遍数组把最大值和最小值都选出来,最大值与尾元素交换,最小值与首元素交换,以此类推,这样我们就可以只遍历n/2次就把数组排有序,当然每次遍历我们比较的变量多了一个。

#include <stdio.h>
void Swap(int* a, int* b)
{
	int t = *a;
	*a = *b;
	*b = t;
}
void SelectSort(int arr[], int n)
{
	int left = 0, right = n - 1;
	while (left < right)
	{
		int mini = left, maxi = left;
		for (int i = left + 1; i <= right; i++)
		{
			if (arr[mini] > arr[i])
			{
				mini = i;
			}
			if (arr[maxi] < arr[i])
			{
				maxi = i;
			}
		}
		Swap(&arr[left], &arr[mini]);
		//处理当left和maxi在同一位置
		if (left == maxi)
		{
			maxi = mini;
		}
		Swap(&arr[maxi], &arr[right]);
		right--;
		left++;
	}
}
int main()
{
	int arr[] = { 7, 6, 5, 4, 7, 3, 2, 1, 9, 8, 0};
	int n = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	SelectSort(arr, n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

注意当我们每次遍历同时选出最大值和最小值时,会出现一种情况:maxi和left的位置重合

这时如果我们让mini和left元素交换后,直接继续交换maxi和right,就会出错,因为maxi位置已经不是最大值了

所以我们在交换后,判断left是否等于maxi,如果等于,我们就把mini的值赋给maxi,然后再交换maxi。

3.堆排序

1.定义

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

2.思路

首先我们有一个无序数组,在逻辑上我们把它抽象成堆

如果我们想把数组排为升序,那么我们需要先建个大堆,当它是大堆时,那么堆顶元素就是这组数据中的最大值,然后我们把堆顶元素和堆底最后的元素交换,堆在逻辑上是一个树形结构,但是它的本质还是数组,堆顶就是数组首元素,堆底最后元素就是数组尾元素,然后从上到下从左到右依次是数组的每个元素。当我们把堆顶元素和堆底最后的元素交换后,我们就是将数组最大值放到了数组的末尾位置,然后现在我们还要保证现在的堆是大堆,我们就要让堆顶元素向下调整,调整后我们得到大堆,然后再把堆顶元素和堆底元素交换,但是这次与堆底元素的交换,堆底元素不能是上一次得到的堆底元素,而是它的前一个元素。

3.代码

#include <stdio.h>
void Swap(int* a, int* b)
{
	int t = *a;
	*a = *b;
	*b = t;
}
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 n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//选出左右孩子中大的那一个
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapSort(int arr[], int n)
{
	//向上调整建大堆
	for (int i = 1; i < n; i++)
	{
		AdjustUp(arr, i);
	}
	//堆排序
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[end], &arr[0]);
		//向下调整
		AdjustDown(arr, end, 0);
		end--;
	}
}
int main()
{
	int arr[] = { 20, 4, 17, 3, 16 ,5 };
	int n = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	HeapSort(arr, n);
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

4.代码分析

1.向上调整建堆

首先在堆里有parent和child的概念,顾名思义,child就是parent的孩子

下面我们主要用到left child,所以我们直接把left child 称为child,然后我们来分析向上调整,假设有个数组:3,5,4,20,17,16。它的堆型结构如下

我们从这个堆的第二个元素开始,即‘5’要向上调整,此时‘5’就是child就,‘3’就是它的parent,然后比较child和parent的大小,如果child大于parent,我们就把child和parent交换,交换完成后,我们就对下一个元素进行向上调整,‘4’和‘3’元素同理,到‘20’时此时堆的结构如下

‘20’的parent是‘3’,‘20’>‘3’,交换‘20’和‘3’,交换后,注意此次向上调整还没结束,交换后的‘20’还要和新的parent比较,即‘20’和‘5’比较,‘20’>‘5’,交换‘20’和‘5’,这次交换后,元素‘20’的向上调整才结束,因为‘20’已经没有新parent,此时堆结果如下

’20‘调整之后,轮到’17‘调整,以此类推,直到所有元素被调整一遍。

所有元素调整一遍后,此时的堆就成了大堆。

2.向下调整,调整堆

每次排好大堆交换堆顶和堆底元素后,堆就不再是大堆,但是它基本还是大堆,只是堆顶元素不满足大堆的条件,于是我们对堆顶元素进行向下调整

此时’4‘作为parent要和‘17’leftchild和‘16’rightchild比较,‘17’>'4',‘16’ > '4',那我们就把‘17’和‘4’交换,交换后,向下调整还没有结束,此时‘4’有新的child,我们还要把‘4’和新的孩子比较,即‘4’和‘3’leftchild和‘5’rightchild,因为‘4’<'5', '4' > '3',所以‘4’和‘5’交换,此时‘4’的向下调整结束,此时堆又变成大堆。堆结构如下

然后我们再将堆顶数据和堆底交换,循环往复,直到堆只有一个数据,此时就排好序了。

5.时间空间复杂度和稳定性

1.直接选择排序

1.时间复杂度:O(n^2)

普通的直接选择排序,一共要遍历n-1次,第一次遍历n个元素,第二次n-1个元素,第三次n-2等等等,最后求和为(n^2+n-2)/2,所以时间复杂度为O(n^2)

2.空间复杂度:O(1)

在直接选择排序中,随着数据规模的不断变大,交换的次数也会变多,Swap函数调用产生的t变量次数也会增加,但是,每次调用完Swap后t变量的空间都会被释放,然后下一次再重新定义t,每次调用完都会被释放,所以虽然调用的多,但是被释放了。所以空间复杂度为O(1)。

3.稳定性:不稳定

按理来说:在每次遍历选取最大值时,如果遇到相等的值,会选着靠后的,所以直接选择排序不会改变值相等的元素在初始数组的相对位置。

但是下面这种情况,当我们选出小的‘1’时,我们与序列第一个‘2’交换位置,交换后,序列中两个而得相对位置就发生了改变,所以在这种情况下,选择排序不稳定。

2.堆排序

1.时间复杂度:O(nlogn)

首先堆排序,最开始建堆需要消耗时间,如果我们用向上调整建堆,那么建堆消耗时间为:(n*logn)。

建完堆后,后面我们每次将堆顶与堆底元素交换后,都要进行一次向下调整,每次向下调整的最多(最差情况)次数,就是logn,换句话说,最差的情况是,有几层我们就要将这个元素向下换几次位置。然后我们要将堆顶和堆底数据交换(n-1)次,每次交换后都要向下调整建堆,所以消耗时间为:(n-1)logn

总的时间为(n*logn)+(n-1)logn,所以时间为:2*nlogn,所以时间复杂度为O(nlogn).

如果我们建堆时用向下调整建堆,那么建堆消耗的时间为:N - logn,但最后整个堆排序的时间复杂度是一样的,都是:O(nlogn)

2.空间复杂度:O(1)

堆排序过程中,随着数据量的增大,建堆或调整堆时,调用Swap函数的次数增多,但是每次调用完后,都释放了,所以总的来开,堆排序中,的空间消耗,就是几个固定的循环变量,和交换时用到的t,虽然会多次创建,但每次交换完后就会立马被销毁,所以空间的消耗是常量级的,即O(1)。

3.稳定性:不稳定

堆排序时,值相等的数据,在一次又一次交换中,他们的相对位置会改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值