当前位置: 首页 > article >正文

数据结构之交换排序

目录

交换排序

冒泡排序

冒泡排序的时间复杂度

快速排序

快速排序单趟排序的时间复杂度

快速排序的时间复杂度


交换排序

在日常生活中交换排序的使用场景是很多的,比如在学校做早操,老师通常会让学生按大小个排队,如果此时来了一个新学生,要让他进入队伍,此时就要让他先与队列的第一个学生进行比较,发生交换,最终将他排到合适的队列位置,这就是一个简单的交换排序的使用场景。在数据结构中,我们怎样实现交换排序呢

 交换排序分为冒泡排序快速排序(重点),下来就让我们一起研究这两个排序。

冒泡排序

冒泡排序的思想:我们将冒泡排序分成了多个单趟排序,每趟排序找出最大的元素,并且将最大的元素放置于数组的末尾。

冒泡排序的单趟排序代码:

for (int i = 0; i < size -j-1; i++)
		{
			//比较两个元素,因为是排升序,所以如果第一个元素比第二个元素要大,就要发生交换
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				flag = 0;
			}
		}

冒泡排序整体代码:

void BubbleSort(int* a, int size)
{
	//整体的排序
	for (int j = 0; j < size - 1; j++)
	{
		//定义一个标志变量,为循环结束的条件
		int flag = 1;
		//单趟排序:先让第一个元素与第二个元素进行比较大小,因为是排升序,所以如果第一个元素比第二个元素大,要发生交换,依此步骤,完成一次单趟排序
		for (int i = 0; i < size -j-1; i++)
		{
			//比较两个元素,因为是排升序,所以如果第一个元素比第二个元素要大,就要发生交换
			if (a[i] > a[i + 1])
			{
				Swap(&a[i], &a[i + 1]);
				flag = 0;
			}
		}
		//优化:如果进行了一趟排序之后,没有元素发生交换,就意味着数组已经变得有序,所以就没有必要再去进行下一趟排序了
		if (flag == 1)
		{
			break;
		}
	}
}

int main()
{
	int arr[] = { 1000,999,888,777,666,555,444,333,222,111 };
	BubbleSort(arr, sizeof(arr) / sizeof(int));
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 运行截图如下:

 注意:我们里面定义了一个flag变量为循环结束的标志,因为我们对冒泡排序做了优化,因为当一个数组已经有序时,我们人眼可以看出来,但是编译器是看不出来的,所以,我们要设置这个变量,为的就是,当我们进行了一趟排序后,如果没有发生元素的交换,证明此时数组已经变得有序了,所以就没有必要再去进行下一趟排序了,应该直接终止排序,即跳出循环。

冒泡排序的时间复杂度

时间复杂度: 最好:O(N)     最坏:O(N^2)

稳定性:稳定

快速排序

快速排序是排序中最牛也是最重要的排序。 本期我们主要讲解快速排序的递归版本

快速排序的思想:先进行单趟排序,单趟排序找出key之后,对key左边的数组和key右边的数组依次进行快速排序,按照递归的思想最终完成排序。

单趟排序的思想:

方法一:hoare版本,这个版本是发明快排的大佬所使用的版本,有些难懂,称之为原始版本。

主要思路:1.设置两个变量left和right,分别表示数组第一个元素和最后一个元素的位置,然后规定key为left或者right的位置。

                  2.从left位置开始,找比key位置上的元素大的元素,从right位置开始,找比key位置上的元素小的元素,这里需要注意:如果选取left为key,应从right位置开始找,如果选取right为key,应该从left位置开始找。

                  3.当找到了对应的元素之后,将两个位置上的元素进行交换,分别让left++,right--然后重复上述2步骤,直到left和right处于同一位置,将此位置上的元素与key位置上的元素进行交换,然后将key挪动到此位置,一趟排序就完成了,此时key位置左边的所有位置的元素都比key位置上元素要小,key位置右边的元素都比key位置上的元素都要大,此时就证明key位置上的元素已经放到了最终的位置,排好了序。

单趟排序图示如下:

单趟排序的代码如下:

int PartSort1(int* a, int left, int right)
{
	int key = left;
	while (left < right)
	{
		while (left<right && a[right]>=a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		swap(&a[right], &a[left]);
	}
	swap(&a[left], &a[key]);
	key = left;
}

   大家注意这两行代码:

为什么限制条件必须这样写呢?因为我们要避免两种极端情景。

情景1:当数组元素相同时

当数组元素相同时,按照我们以往的算法,从right开始找比key小的,如果比key大就right--,,然后在从left开始找,找比key大的,如果比key小就left++,但是在这种情况下right不可能比key大的,所以right就不可能--,left也不可能比key值小,所以left就不可能++,里面的while循环无法正常进行,所以为了避免这样情况下我们就要,增加判断条件,即a[right]>=a[key]和a[left] <= a[key],比以往多了一个==,这样就会让left++和right--正常进行。

情景2:当数组已经是个有序数组时:

这种情景刚开始都是正常的,但是大家仔细思考下面的两行代码。

当right和left相遇之后,因为最外层的控制条件只会堆内层的循环产生一次限制影响,所以此时的right仍然会再次--,这就会导致right越界。所以我们还必须加上一个限制条件,里面的while循环也必须加上left<right的限制条件,所以改善之后的代码如下:

单趟排序方法二:挖坑法

主要思路:1.将第一个数据存放在临时变量key中,然后第一个位置形成一个坑位。然后从right开始找比key小的元素,找到之后与放置到坑位,然后自己成为坑位。

                  2.从left开始找比key大的元素,找到之后放置到坑位,然后自己形成一个坑位。

                  3.重复1,2,直到left和right相遇。最终将key的值放置在坑位,最红key左边的值都小于key的值,右边的值都大于key的值。

单趟排序第二种方法代码:

int PartSort2(int* a, int left,int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[left], &a[mid]);
	int key = a[left];
	int pivot = left;
	while (left < right)
	{
		while (left<right && a[right]>=key)
		{
			--right;
		}
		a[pivot] = a[right];
		pivot = right;
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[pivot] = a[left];
		pivot = left;
	}
	a[left] = key;

}

快速排序整体代码:

void  QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	int key= PartSort1(a, left, right);
	QuickSort(a, left, key - 1);
	QuickSort(a, key + 1, right);
}
int main()
{
	int arr[] = { 100,99,88,77,66,55,44,33,22,11 };
	QuickSort(arr,0,sizeof(arr)/sizeof(int)-1);
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 运行截图如下:

快速排序单趟排序的时间复杂度

left和right从数组两端一直到相遇,再与key交换,整个过程刚好每个元素都和key位置的元素比较了一次,所以快速排序单趟排序的时间复杂度为O(N)

快速排序的时间复杂度

最好:O(N*logN)

最坏:O(N^2)

稳定性不稳定。 

综上我们知道,当数组有序时吗,快排的效率非常差劲,为了提高效率,我们发明了一种三数取中法,为一个将有序数组变称无序数组的方法,以提高效率。

具体思路:在进行单趟排序之前,先将left,right和(left+right)/2,位置上的元素分别进行对比,找出出刚好大小处于中间的元素,将次位置设置成mid;然后将mid位置上的元素与left位置上的元素进行交换,就导致left位置上的元素已经是数组中两个元素中间的一个元素,也就意味着进行了这样的一个调整之后,数组一定是无序的。这样就强制性的改变了快排最坏的情况,向最好的情况引导。

 这就保证了,单趟排序之后,key的位置一定不是处于最边上的位置,而是处于中间的位置。

获取中间元素的位置代码:

int GetMid(int* a, int left, int right)
{
	int mid = left + right / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
			return mid;
		else if (a[left] > a[right])
			return left;
		else
			return right;
	}
	else
	{
		if (a[left] < a[right])
			return left;
		else if (a[mid] > a[right])
			return mid;
		else
			return right;
	}
}

 改进后的单趟排序代码:

int PartSort(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[mid], &a[left]);
	int key = left;
	while (left < right)
	{
		while (left < right && a[right]>=a[key])
		{
			right--;
		}
		while (left < right && a[left] <= a[key])
		{
			left++;
		}
		Swap(&a[right], &a[left]);
	}
	Swap(&a[left], &a[key]);
	key = left;
	return key;
}

 以上便是交换排序的所有内容,比较重要的还得是快速排序,但是快速排序我们只学习了递归版本,非递归版本在下期为大家讲解。欲知后事如何,且看下期分解。

本期的所有内容到此结束^_^


http://www.kler.cn/a/160471.html

相关文章:

  • 如何查看电脑关机时间
  • arkUI:遍历数据数组动态渲染(forEach)
  • 《云原生安全攻防》-- K8s安全防护思路
  • 【JavaEE进阶】导读
  • 稀疏视角CBCT重建的几何感知衰减学习|文献速递-基于深度学习的病灶分割与数据超分辨率
  • srs http-flv处理过程
  • Flink优化——数据倾斜(二)
  • ssh远程连接服务器
  • ELK的日志解决方案
  • PACS源码,医学影像传输系统源码,全院级应用,支持放射、超声、内窥镜、病理等影像科室,且具备多种图像处理及三维重建功能
  • Kafka 的消息格式:了解消息结构与序列化
  • 2023字节跳动软件测试工程师面试题及答案分享
  • 万界星空科技MES系统在工业生产中的应用
  • WordPress发布文件随机设置作者昵称信息
  • gitlab高级功能之CI/CD组件 - 原理介绍(一)
  • Failed to connect to github.com port 443 after 21055 ms: Timed out
  • React Node.js 和 Prisma 构建全栈框架
  • gitLab 和Idea分支合并
  • 【Flink on k8s】- 5 - 简要介绍 Flink
  • VR远程带看,助力线下门店线上化转型“自救”
  • 在windows下编译libiconv库
  • 对GPU进行基准测试可以帮助你评估其功能,识别潜在问题,防患于未然
  • 学习极市开发平台
  • 【daily notes on IT/AI/science】
  • Linux--环境变量
  • [实践总结] 使用Apache HttpClient 4.x进行进行一次Http请求