排序(插入,希尔,堆排,冒泡)

常见的排序算法:
在这里插入图片描述

插入排序:
直接插入排序:是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。保证插入之前的数组是有序的,插入后也保证是有序的。时间复杂度:原数组如果和需要排的顺序逆序的时候是最坏的情况,O(N^2),和冒泡排序时间复杂度相同。最好的情况是O(N),顺序有序。

// 直接插入插入排序
void InsertSort(int* a, int n)
{
	//多趟排序
	for(int i = 0; i < n-1; ++i) // 当n = n-2时,插入的就是第n-1的位置的值,就完成了n个数的插入。
	{
		// 单趟排序 把tmp插入数组[0,end]的有序区间
		int end = i; // 从后往前判断,end 就是当前判断的最后一个值
		int tmp = a[end + 1]; // end外面要插入的数据
		while(end >= 0) // 从后往前,end--
		{
			if(tmp < a[end])
			{
				a[end + 1] = a[end]; //让end位置的值放在end+1的位置
				end--; 
			}
			else
				break; // 说明tmp >= a[end-1]
		}
		a[end + 1] = tmp; // 需要在end-1的后面 也就是end+1的位置存放tmp
	}
} 

希尔排序:再插入排序的基础上,让数组首先接近于有序。也就是1.预排序 ->让数组接近有序,2.直接插入排序。
预排序 ->先分组,对分组的数据进行插入排序。间隔为gap的分成一组,对一组进行插入排序。
蓝色的整体为一组,红色的整体为一组,绿色的整体为一组。每次比较的时候只比较各自颜色指向的数字,以蓝色为例,也就是9,5,8,5相比较后进行排序。
gap越大,说明大的数和小的数可以更快的挪到对应的位置,但gap越大越不接近有序。gap越小则相反。
如果gap==1,则就是直接插入排序。排序的规则就是gap = 5, gap = 3, gap = 1来三次就完成希尔排序。
希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N^1.3- N^2)
在这里插入图片描述

 // 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	// 整个循环最坏是O(N*log3(N))
	while(gap > 1) // n/3/3/3/3/../3 = 1截至 -> 3^x = n, x = log3(N)就是while的次数
	{
		gap = (gap / 3 + 1); //这样可以保证gap最后一次是1。
		// i<n-gap 则对应上图的话就到蓝色8这个位置,i一直加到这个位置可以把蓝色红色和绿色全部处理完。
		// 也就是 先蓝色,再红色,再绿色,再蓝色,再红色,再绿色,再蓝色(8这个位置)就完了。因为后面红色绿色只有一个数。
		// gap很大时是预排序让它接近有序,就是O(N)。
	    // gap很小时,本应该是O(N*N),但是经过了预排序,数组很接近有序,所以也接近于O(N)
	    // 因此,这个for循环就是O(N)
		for(int i = 0; i < n-gap; ++i) 
		{
			int end = i;
			int tmp = a[end + gap]; //要插入的值
			while(end >= 0) // 间隔为gap的排序,类似于直接插入排序(间隔为1)
			{
				if(tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
					break;
			}
			a[end + gap] = tmp;
		}
	}
}

直接选择排序:在数组中直接选择最大的,次大的放到相应位置直接排。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// 选择排序 O(N^2) 无论如何都会遍历一遍选择数
void SelectSort(int* a, int n)
{
	// 将最小的放在最左边,最大的放在最右边,让左右位置往中间夹
	int left = 0;
	int right = n-1;
	while(left < right)
	{
		int minIndex = left, maxIndex = left;
		for(int i=left; i<right; ++i)
		{
			if(a[i] < a[minIndex]) // 选出最小的
				minIndex = i;
			if(a[i] > a[maxIndex]) //选出最大的
				maxIndex = i;
		}
		Swap(&a[left], &a[minIndex]); 
		//如果最大值在left位置,上面交换后max就被换走了 换到minIndex的位置去了 修正位置。
		if(left == maxIndex) 
		{
			maxIndex = minIndex;
		}
		Swap(&a[right], &a[maxIndex]);
		++left;
		--right;
	}
}

堆排序:先得有个堆然后再选择出放到特定位置的选择排序方法。
堆排序的详细介绍

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
 // 堆排序 建堆是O(N) 每次调整需要O(logN)(调整一次走高度次,高度是logN) 因此堆排序是O(NlogN)
 //自顶向下的调整,但是parent的传参是自下而上的传,先保证最下面的parent及其子节点属于大堆,再一层一层的向上传parent。
void AdjustDwon(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)
{
	// 建堆 parent的传参是自下而上的传,先保证最下面的parent及其子节点属于大堆,再一层一层的向上传parent。
	for(int i = (n-1-1)/2; i >=0; --i)
	{
		AdjustDwon(a, n, i);
	}
	int end = n-1;
	while(end > 0) //只剩一个数就不选了
	{
		Swap(&a[0], &a[end]);
		// 再选出次大的 因为之前已经建堆完成了,因此只有换上去到最上面的那个数不满足大堆的排序,把它排好就行
		AdjustDwon(a, end, 0)
		end--; //每次最后一个位置就是被调换过的最大的、次大的...的数
	}
}

冒泡排序:定义两个指针,指向第一个和第二个位置,前一个比后一个大就交换,然后同时向后挪接着比较,把最大的放到最后一个位置。最坏的情况:O(N^2),最好的情况:O(N)。冒泡和插入的时间复杂度差不多相同,但是当数组接近有序时,她两比起来插入好一些。
在这里插入图片描述

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// 冒泡排序
void BubbleSort(int* a, int n)
{
	for(int end = n; end > 0; end--)
	{
		int exchange = 0;
		for(int i = 1; i<end; i++)
		{
			if(a[i-1] < a[i])
			{
				Swap(&a[i-1], &a[i]);
				exchange = 1;
			}
		}
		if(exchange == 0) //如果没有发生交换,可以提前结束程序
			break;
	}
}
上一篇:EKF学习笔记


下一篇:算法——冒泡排序