排序算法的实现


文章目录

  • 一、排序的概念及其运用
    • 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.常见的排序算法

排序算法的实现_第1张图片

二、常见排序算法的实现(跑排序算法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之后数值没有赋值)*/
				}
			}
		}
	}

排序算法的实现_第2张图片

 希尔排序的特性总结:

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 个元素 。
排序算法的实现_第3张图片

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) 是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。 需要注意的是排升序要建大堆,排降序建小堆。
排序算法的实现_第4张图片

 

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

	while (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. 稳定性:不稳定

交换排序

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

冒泡排序

排序算法的实现_第5张图片

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版本
排序算法的实现_第6张图片

//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.挖矿法

排序算法的实现_第7张图片

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.前后指针法

排序算法的实现_第8张图片

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 )的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:
排序算法的实现_第9张图片

 排序算法的实现_第10张图片

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. 根据统计的结果将序列回收到原来的序列中
排序算法的实现_第11张图片

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. 稳定性:稳定

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

排序算法的实现_第12张图片

 排序算法的实现_第13张图片

 

你可能感兴趣的:(数据结构,排序算法,数据结构)