排序算法的实现

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6


文章目录

  • 一、排序的概念及其运用
    • 1.排序的概念
    • 2.常见的排序算法
  • 二、常见排序算法的实现
    • 1.插入排序
      • 1.直接插入排序
      • 2.希尔排序
    • 2.选择排序
      • 1.直接选择排序
      • 2.堆排序
    • 3.交换排序
      • 1.冒泡排序
      • 2.快速排序
        • 1.hoare版本
        • 2.挖坑法
        • 3.前后指针版本
    • 4.归并排序
    • 5.非比较排序
  • 三、排序算法复杂度及其稳定性分析


一、排序的概念及其应用

1.排序的概念

排序所谓排序就是使一连串记录按照其中的某个或某些关键字的大小递增或者递减的排列起来的操作。

稳定性在待排序的序列中存在多个具有相同的值若经过排序使得这些值的相对次序保持不变即在原序列中r[i]=r[j]且r[i]在r[j]之前在排序后次序仍然保持不变那就称这种排序算法稳定的否则就称为不稳定的。

内部排序数据元素全部放在内存中的排序。

外部排序数据元素太多不能同时放在内存中根据排序过程中的要求不能拿在内外存之间移动数据的排序。

2.常见的排序算法

二、常见排序算法的实现跑排序算法OJ链接

插入排序

插入排序的思想是一种简单的插入排序法其基本思想是将待排序的记录按照其值大小依次逐个插入到一个已经排好序的有序序列中知道所有的记录插入完为止得到一个新的有序序列

1.直接插入排序

//简单插入排序
void InsertSort(int* a, int n)
{
	//需要将n-2后面的元素插入排序
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;//将end中后面一个元素插入到有序的区间[0,end]中
		int tmp = a[end + 1];//保存一下end后面一个元素的大小
		while (end >= 0)//将所插入的元素中的前面end个元素进行比较
		{
			if (tmp < a[end])//如果end后面一个元素的值比前面一个元素小的话往后移动
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;/*如果end此时的值比后面一个元素的值小的话则退出后进
				行赋值保证了此时数据能够全部插入
				而且有序*/
			}

		}
		a[end + 1] = tmp;/*将此时的tmp赋值
		(一种是因为这个有序区间的值都比后面这个大遍历完之后end为-1此时应该将tmp赋值
		第二种是因为此时break之后数值没有赋值)*/
	}

直接插入排序特性总结

1.元素集合越接近有序直接插入排序的效率越高。

2.时间复杂度O(n^2

3.空间复杂度O(1)

4.稳定性稳定

2.希尔排序缩小增量排序

排序思想先选定一个整数把待排序文件中所有记录分成小组所有距离为记录的分在一个同一个小组内进行排序。然后取重复上述分组和排序的工作。当到达1时所有记录在统一组内排好序。

1预排序使接近有序

2直接插入排序有序

	//希尔排序(存在gap的简单排序
	void ShellSort(int* a, int n)
	{
		int gap = n;//定义一个步长
		while (gap > 1)//确保gap的最后一次等于1(最后一次为插入排序
		{
			gap = gap / 3 + 1;//无论gap是奇数还是偶数使得最后一次的gap为1
            //gap=gap/2;
			for (int i = 0; i < n - gap; i++)
			{
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (tmp < a[end])//如果end后面一个元素的值比前面一个元素小的话往后移动
					{
						a[end + 1] = a[end];
						end = end - gap;
					}
					else
					{
						break;/*如果end此时的值比后面一个元素的值小的话则退出后进
				行赋值保证了此时数据能够全部插入
				而且有序*/
					}
					a[end + gap] = tmp;
					/*将此时的tmp赋值
		(一种是因为这个有序区间的值都比后面这个大遍历完之后end为-1此时应该将tmp赋值
		第二种是因为此时break之后数值没有赋值)*/
				}
			}
		}
	}

 希尔排序的特性总结

1.希尔排序是对直接排序的优化。

2.当gap>1时都是预排序目的是让数组更接近于有序。当gap=1时。数组已经接近于有序了这样就会很快。这样整体而言可以达到优化的效果。我们实现后可以进行性能测试的对比。

3.希尔排序的时间复杂度不好计算因为gap取值方法比较多导致很难去计算因此在好些树中给出的希尔排序的时间复杂度都不固定

4.稳定性不稳定

选择排序

基本思想
       每一次从待排序的数据元素中选出最小或最大的一个元素存放在序列的起始位置直到全部待排序的数据元素排完 。

直接选择排序

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

void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;

	while (begin < end) {
		int mini = begin, maxi = begin;

		for (int i = begin + 1; i <= end; i++) {
			if (a[i] < a[mini])
				mini = i;
			if (a[i] > a[maxi])
				maxi = i;
		}

		Swap(&a[begin], &a[mini]);
		if (maxi == begin)
			maxi = mini;

		Swap(&a[end], &a[maxi]);

		begin++;
		end--;
	}
}

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

堆排序

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

 

void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;

	while (child<size) {
		if (child+1<size&&a[child] < a[child + 1])
			child++;

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

void HeapSort(int* a, int n)
{
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
		AdjustDown(a, n, i);

	int end = n - 1;
	while (end >= 0) {
		Swap(&a[end], &a[0]);
		AdjustDown(a, end, 0);
		end--;
	}
}
直接选择排序的特性总结
        1. 堆排序使用堆来选数效率就高了很多。
        2. 时间复杂度 O(N*logN)
        3. 空间复杂度 O(1)
        4. 稳定性不稳定

交换排序

基本思想所谓交换就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置交换排 序的特点是将键值较大的记录向序列的尾部移动键值较小的记录向序列的前部移动。

冒泡排序

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++) {
		int exchange = 0;
		for (int j = 1; j < n - i; j++) {
			if (a[j - 1] >a[j]) {
				Swap(&a[j], &a[j - 1]);
				exchange = 1;
			}
		}

		if (exchange == 0)
			break;
	}
}

冒泡排序的特性总结

        1. 冒泡排序是一种非常容易理解的排序
        2. 时间复杂度 O(N^2)
        3. 空间复杂度 O(1)
        4. 稳定性稳定

         

快速排序

快速排序是Hoare 1962 年提出的一种二叉树结构的交换排序方法其基本思想为 任取待排序元素序列中 的某元素作为基准值按照该排序码将待排序集合分割成两子序列左子序列中所有元素均小于基准值右 子序列中所有元素均大于基准值然后最左右子序列重复该过程直到所有元素都排列在相应位置上为止。
按照基准值来对区间中数据进行划分的方式即可。
将区间按照基准值划分为左右两半部分的常见方式有
1. hoare版本

//hoare版本
int PartSort1(int* a, int begin, int end)
{
	int left = begin, right = end;
	int keyi = left;

	while (left < right) {
		while (a[right] >= a[keyi] && left < right)
			--right;
		while (a[left] <= a[keyi] && left < right)
			++left;

		Swap(&a[left], &a[right]);
	}

	Swap(&a[left], &a[keyi]);
	keyi = left;

	return left;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >end)
		return;

	int mid = PartSort1(a, begin,end);

	QuickSort(a, begin, mid - 1);
	QuickSort(a, mid + 1, end);
}
	

2.挖矿法

int PartSort2(int* a, int begin, int end)
{
	int key = a[begin];
	int piti = begin;

	while (begin < end) {
		while (begin < end && a[end] >= a[piti])
			--end;
		Swap(&a[end], &a[piti]);
		piti = end;

		while (begin < end && a[begin] <= a[piti])
			++begin;
		Swap(&a[begin], &a[piti]);
		piti = begin;
	}

	a[piti] = key;
	return piti;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >end)
		return;

	int mid = PartSort2(a, begin,end);

	QuickSort(a, begin, mid - 1);
	QuickSort(a, mid + 1, end);
}

 3.前后指针法

int PartSort3(int* a, int begin, int end)
{
	int prev = begin;
	int cur = begin + 1;
	int keyi = begin;

	while (cur <= end) {
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);
		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >end)
		return;

	int mid = PartSort3(a, begin,end);

	QuickSort(a, begin, mid - 1);
	QuickSort(a, mid + 1, end);
}
	
快速排序优化
1. 三数取中法选 key
2. 递归到小的子区间时可以考虑使用插入排序
int GetMidIndex(int* a, int begin, int end)
{
	int mid = (begin + end) / 2;

	if (a[begin] < a[mid]) {
		if (a[mid] < a[end])
			return mid;
		else if (a[begin] < a[end])
			return end;
		else
			return begin;
	}
	else
	{
		if (a[mid] > a[end])
			return mid;
		else if (a[begin] < a[end])
			return begin;
		else
			return end;
	}
}

int PartSort3(int* a, int begin, int end)
{
	int prev = begin;
	int cur = begin + 1;
	int keyi = begin;

	int mid = GetMidIndex(a, begin, end);

	Swap(&a[mid], &a[keyi]);

	while (cur <= end) {
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[prev], &a[cur]);
		
		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

void _QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	else if (end - begin > 10) {
		int keyi = PartSort3(a, begin, end);

		_QuickSort(a, begin, keyi - 1);
		_QuickSort(a, keyi + 1, end);
	}
	else
		InsertSort(a+begin, end-begin+1);
}

快速排序非递归
void QuickSortNonR(int* a, int begin, int end)
{
	ST st;
	StackInit(&st);
	StackPush(&st, end);
	StackPush(&st, begin);

	while (!StackEmpty(&st)) {
		int left = StackTop(&st);
		StackPop(&st);

		int right = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, left, right);

		if (keyi + 1 < right) {
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}

		if (left < keyi - 1) {
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}
	}

	StackDestroy(&st);
}
快速排序的特性总结
1. 快速排序整体的综合性能和使用场景都是比较好的所以才敢叫 快速 排序
2. 时间复杂度 O(N*logN)
3. 空间复杂度 O(logN)
4. 稳定性不稳定

归并排序

基本思想
归并排序 MERGE-SORT 是建立在归并操作上的一种有效的排序算法 , 该算法是采用分治法 Divide and Conquer 的一个非常典型的应用。将已有序的子序列合并得到完全有序的序列即先使每个子序列有 序再使子序列段间有序。若将两个有序表合并成一个有序表称为二路归并。 归并排序核心步骤

 

void _MergeSort(int* a, int begin, int end,int* tmp)
{
	if (begin >= end)
		return;

	int mid = (begin + end) / 2;

	_MergeSort(a,begin, mid,tmp);
	_MergeSort(a, mid + 1, end,tmp);

	int begin1 = begin, end1 = mid ;
	int begin2 = mid + 1, end2 = end;
	int i = begin1;

	while (begin1 <= end1 && begin2 <= end2) {
		if (a[begin1] < a[begin2])
			tmp[i++] = a[begin1++];
		else
			tmp[i++] = a[begin2++];
	}

	while (begin1 <= end1)
		tmp[i++] = a[begin1++];
	while (begin2 <= end2)
		tmp[i++] = a[begin2++];

	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);

	if (tmp == NULL) {
		printf("malloc fail!\n");
		exit(-1);
	}

	_MergeSort(a, 0, n - 1,tmp);

	free(tmp);
}

 归并非递归

/*void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		printf("malloc fail!\n");
		exit(-1);
	}

	int gap = 1;
	while (gap < n) {
		for (int i = 0; i < n; i += 2 * gap) {
			//[i,i+gap-1][i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (end1 >= n) {
				end1 = n - 1;
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n) {
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
				end2 = n - 1;

			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2) {
				if (a[begin1] < a[begin2])
					tmp[j++] = a[begin1++];
				else
					tmp[j++] = a[begin2++];
			}

			while (begin1 <= end1)
				tmp[j++] = a[begin1++];
			while (begin2 <= end2)
				tmp[j++] = a[begin2++];
		 }
		memcpy(a, tmp, sizeof(int) * n);
		gap *= 2;
	}
}*/
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL) {
		printf("malloc fail!\n");
		exit(-1);
	}

	int gap = 1;
	while (gap < n) {
		for (int i = 0; i < n; i += 2 * gap) {
			//[i,i+gap-1][i+gap,i+2*gap-1]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			if (end1 >= n || begin2 >= n)
				break;
			else if (end2 >= n)
				end2 = n - 1;

			int m = end2 - begin1 + 1;
			int j = begin1;
			while (begin1 <= end1 && begin2 <= end2) {
				if (a[begin1] < a[begin2])
					tmp[j++] = a[begin1++];
				else
					tmp[j++] = a[begin2++];
			}

			while (begin1 <= end1)
				tmp[j++] = a[begin1++];
			while (begin2 <= end2)
				tmp[j++] = a[begin2++];
			memcpy(a+i, tmp+i, sizeof(int) * m);
		 }
		
		gap *= 2;
	}
}

归并排序的特性总结
1. 归并的缺点在于需要 O(N) 的空间复杂度归并排序的思考更多的是解决在磁盘中的外排序问题。
2. 时间复杂度 O(N*logN)
3. 空间复杂度 O(N)
4. 稳定性稳定

非比较排序

思想计数排序又称为鸽巢原理是对哈希直接定址法的变形应用。 操作步骤
1. 统计相同元素出现次数
2. 根据统计的结果将序列回收到原来的序列中

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 1; i < n; i++) {
		if (a[i] < min)
			min = a[i];
		if (a[i] > max)
			max = a[i];
	}

	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);

	if (count == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	memset(count, 0, sizeof(int) * range);

	for (int i = 0; i < n; i++) {
		count[a[i]-min]++;
	}

	int j = 0;
	for (int i = 0; i < n; i++)
	{
		while (count[i]--)
			a[j++] = i + min;
	}
}

计数排序的特性总结

1. 计数排序在数据范围集中时效率很高但是适用范围及场景有限。
2. 时间复杂度 O(MAX(N, 范围 ))
3. 空间复杂度 O( 范围 )
4. 稳定性稳定

排序算法复杂度及稳定性分析

 

 

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6