看了不后悔系列之————六大排序算法

最近在复习算法,看了好多的博文,写的都不是太明白。这里我打算进行一次性总结,方便自己查看,也方便他人学习。正所谓赠人玫瑰手有余香

评价一个算法无外乎从三个方面出发,

1,时间复杂度

2,空间复杂度

3,稳定性

本次总结六个排序算法,本文将从代码,时间复杂度,空间复杂度,稳定性以及改进这四个方面进行总结。本文采用C++

  1. 冒泡排序
  2. 插入排序
  3. 选择排序
  4. 归并排序
  5. 快速排序
  6. 堆排序

首先是一个Swap()函数,好多方法都会用到这个交换函数,直接写在最前面。

void Swap(int* a, int* b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

 一,冒泡排序

经典冒泡排序的代码:

void Bubble(int* arry,int length) {
	if (arry == NULL || length < 2) {	//判断输入是否合法
		return;
	}
	for(int i=length-1;i>0;i--){		//比较多少轮
		for (int j = 0;j < i;j++) {		//每一轮比较多少次
			if (arry[j] > arry[j + 1]) {
				Swap(&arry[j], &arry[j + 1]);
			}
		}
	}
}

时间复杂度:

冒泡排序的时间复杂度是严格的O(n^2)的,这一点从但代码段就可以看出。因为冒泡排序是跟数据状况没有关系的,无论你的数据状况是否有序,只要代码中i变量的值不越界,他就会一直比下去,直到i越野跳出循环。

关于O(n^2)的推到

假设有n个元素的数组,那么第一轮需要比较n-1次,那么第二轮需要比较n-2次,第三轮需要比较n-3次,,,,,,往下推,直到最后那么总共需要比较的次数的表达式为:(n-1)+(n-2)+(n-3)+...+2+1;这是一个等差数列,根据等差数列的求和公式可得出表达式为:n^2/2-n/2;根据时间复杂度的求法(不包括这个函数的低阶项和首项系数——百度一下时间复杂度),最后化简为O(n^2)。冒泡排序,选择排序,插入排序的时间复杂度推到均是如此。

空间复杂度:

冒泡排序没有用到额外的辅助空间,只用到了几个少数变量,因此空间复杂度为O(1)。

稳定性:

排序的稳定性就是这个算法是否改变原数据中相同元素的位置。

冒泡排序是可以做到稳定性的,在代码的第二个if语句中如果arry[ j ]<=arry[ j+1 ],跳出if语句,就不会发生交换。

如果上面的第二个if语句改为arry[ j ]>=arry[ j+1 ],那么这段代码就做不到稳定性了。

优化:

对于一组数据{1,2,3,5,4},使用上面的排序算法,第一轮交换后数据就变成了{1,2,3,4,5},此时数据已经有序了。但是由于冒泡排序是跟数据的状况没关系的,只跟是否完全部轮的比较有关。那么接下来的第二轮,第三轮还是会不停的比较下去,虽然并不会发生交换行为,但是浪费了比较次数。

因此我们在代码里加入一个bool类型的变量来监控在每一轮的比较中是否发生了交换,一旦其中的的一轮比较没有发生交换行为,此时数组就已经有序了。

改进后冒泡排序的代码

void Bubble(int* arry,int length) {
	if (arry == NULL || length < 2) {	//判断输入是否合法
		return;
	}
	bool _isChange;
	for(int i=length-1;i>0;i--){		//比较多少轮
		_isChange = false;
		for (int j = 0;j < i;j++) {		//每一轮比较多少次
			if (arry[j] > arry[j + 1]) {
				Swap(&arry[j], &arry[j + 1]);
				_isChange = true;
			}
		}
		if (!_isChange) return;
	}
}

改进后的时间复杂度可以达到O(n),空间复杂度为o(1);

二,插入排序

经典插入排序的代码

​

void Insert(int* arry, int length) {
	if (arry == NULL || length < 2) {
		return;
	}
	for (int i = 1;i < length;i++) {		//把[1,length)看成无序区,[0]为有序区
		for (int j = i - 1;j >= 0 && arry[j] > arry[j + 1];j--) {	//从无序取划进来一个数到有序区,然后在有序区中从后向前调整
			Swap(&arry[j],&arry[j + 1]);
		}
	}
}

[点击并拖拽以移动]
​

时间复杂度:

经典的插入排序的时间复杂度是O(n^2),(这个算法的最好时间复杂度为O(n)也就是当一个数组已经有序的情况下)

空间复杂度:

只在两个for循环中使用了几个变量,所以空间复杂度为O(1);

稳定性:

同冒泡排序的原理一样,可以做到稳定排序。

优化:

没想好

三,选择排序

经典的选择排序代码

void SelectSort(int* arry, int length) {
	if (arry == NULL || length < 2) {
		return;
	}
	
	for (int i = 0;i < length - 1;i++) {
		int index = i;			//每一轮排序都把这一轮中第一个元素当成最小
		for (int j = i + 1;j < length;j++) {	//arry[i]为每一轮的第一个,那么arry[j]就是第二个
			index = arry[j] < arry[index] ? j : index;	
		}
		Swap(&arry[i], &arry[index]);
	}
}

时间复杂度:

经典的选择排序的时间复杂度是严格的O(n^2);

空间复杂度:

O(1);

稳定性:

做不到稳定性,下面给出了一组数据,可以照着代码推一下。

直接选择排序: A = {4, 4, 2, 5},排序后 A = {2,4, 4, 5}

有的人可能会抬杠说:把<号改成=<不就行了?

index = arry[j] < arry[index] ? j : index;

那你试试呗。

优化:

归并排序

经典归并排序代码

void Merge(int* arry,int start,int mid,int end) {
	int length = end - start + 1;
	int* newarry = new int[length];
	int i = start;
	int j = mid + 1;
	int index = 0;
	while (i<=mid&&j<=end)
	{
		newarry[index++] = arry[i] < arry[j] ? arry[i++] : arry[j++];
	}
	while (i<=mid)
	{
		newarry[index++] = arry[i++];
	}
	while (j<=end)
	{
		newarry[index++] = arry[j++];
	}
	for (i = 0;i < length;i++) {
		arry[start + i] = newarry[i];
	}
}
void MergeSort(int* arry, int start, int end) {
	if (start == end) {
		return;
	}
	int mid =(start+end)/2 /*start + ((end - start) >> 1)*/;//求中点的位置这个写法能够防止下表越界,而且位运算比算术运算效率快
	MergeSort(arry, start, mid);
	MergeSort(arry, mid + 1, end);
	Merge(arry, start, mid, end);
	
}
void MergeSort(int* arry, int length) {
	if (arry == NULL || length < 2) {
		return;
	}
	MergeSort(arry, 0, length - 1);
}

时间复杂度

归并排序的时间复杂度分为两部分,一部分是比较和交换,另一部分是新数组给老数组赋值。

根据master公式(是用来利用分治策略来解决问题经常使用的时间复杂度的分析方法)

T [n] = aT[n/b] + T (N^d)

n为样本量;

a为一个递归子过程拆分的次数

b表示每次递归是原来的1/b之一个规模

n/b是子过程的样本量

T (N^d)是除去递归操作外的

猜你喜欢

转载自blog.csdn.net/hemmmmvip/article/details/84498817
今日推荐