“手把手”教你C语言八大排序

C语言八大排序详解

排序的概念及其应用

排序的概念及其运用

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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

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

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

2、排序的运用
(1)、淘宝网上对商品的排序,可以根据品牌、销量、性能、价格等进行排序,我们可以根据不同的排序方式,筛选出自己想要的结果。
在这里插入图片描述

在这里插入图片描述
(2)2021年国内大学排名
在这里插入图片描述
3、常见的排序算法
在这里插入图片描述

常见的八大排序算法

直接插入排序

1、基本思想
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。

在这里插入图片描述

当插入第i(i>=1)个元素时,前面的arr[0],arra1],…,arr[i-1]已经排好序,此时用array[i]的排序码与arr[i-1],arr[i-2],…的排序码顺序进行比较,找到插入位置即将arr[i]插入,原来位置上的元素顺序后移
在这里插入图片描述

直接插入排序代码实现

#pragma once
#include<time.h>
#include<stdlib.h>
// 插入排序  时间复杂度O(N*N),数组逆序的情况下
void InsertSort(int* a, int n)
{
	//多趟排序
	int i = 0;
	for (i = 0; i < n - 1; i++)        //{ 5,2,4,6,1,3 }
	{
		//单层排序
		int end = i;                  //end的区间在[0,n-1),n-1的位置放tmp
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])          //将‘<’改成‘>'就是降序排列
			{
				a[end + 1] = a[end];  //记得要把比tmp大的数向后移一位
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

//打印函数
void Print(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
int main()
{
		int a[] = { 5,2,4,6,1,3 };
		int n = sizeof(a) / sizeof(a[0]);
		InsertSort(a, n);
		Print(a, n);
		return 0;
}

在这里插入图片描述

直接插入排序算法时间复杂度及稳定性分析

时间复杂度分析:

1、最坏情况,就是逆序情况下排序,例如要排升序,而所要排序的数组是降序,那么时间复杂度就是O(N^2).

在这里插入图片描述
在这里插入图片描述
如果要排升序,而数组本身就是升序,那么时间复杂度就最优,O(N).
而时间复杂度是以最坏标准来衡量,所以直接插入排序的时间复杂度是O(N^2).

插入排序是稳定排序算法吗?

所谓稳定,指的是当数组中出现相同数值元素的时候,排序是否会造成相同数值元素相对位置的改变。可以看出在插入排序算法中,对于相同数值的元素可以选择插入到已排序区间等值元素的后边,所以相同数值元素的相对位置不会发生改变,因此插入排序算法是稳定的排序算法。
在这里插入图片描述
直接插入排序的特性总结:

  1. 元素集合越接近有序,直接插入排序算法的时间效率越高
  2. 时间复杂度:O(N^2)
  3. 空间复杂度:O(1),它是一种稳定的排序算法
  4. 稳定性:稳定

希尔排序

基本排序算法中的插入排序虽然逻辑较为简单,但当排序规模较大时,经常需要将元素一位一位地从一端移动到另一端,效率非常低。于是Donald Shell设计了一种基于插入排序的改进版排序算法,故被命名为 Shell Sort 希尔排序。

希尔排序是根据插入排序推想出来的,因为插入排序在最好的情况下,时间复杂度可以达到O(N),所以希尔想到,能不能找到一种方法,使所要排的序列接近有序,这样再排序就可以大大降低时间复杂度。

希尔排序法的基本思想是:先选定一个整数gap,把待排序文件中所有记录分成gap个组,所有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达gap=1时,就是上述的插入排序。
在这里插入图片描述

gap越大,大的和小的数可以更快的挪到对应的方向去
gap越大,越不接近有序

gap越小,大的和小的数可以更慢的挪到对应的方向去
gap越小,就越接近有序

gap==1;
就是直接插入排序。

希尔排序代码实现

#pragma once
#include<time.h>
#include<stdlib.h>
void ShellSort(int* a, int n)
{
	// gap > 1的时候,预排序
	// gap == 1的时候,直接插入排序  O(N)
	int gap = n;
	while (gap > 1) // n/3/3/3.../3 == 1  -》 3^x = n  x就是这个while循环跑的次数
	{
		gap = (gap / 2 );    //一般写成gap = (gap / 2+1 );+1是为了保证,最后一次gap为1

		// 最坏的情况:逆序,gap很大的时-》O(N)
		// ...
		// 
		//                   gap很小时本来应该是O(N*N),但是经过前面的预排序,数组已经很接近有序的,所这里还是O(N)
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else 
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
		printf("gap:%d->", gap);
		Print(a, n);
	}
}
int main()
{
		int a[] = { 9,1,2,5,7,4,8,6,3,5 };
		int n = sizeof(a) / sizeof(a[0]);
		ShellSort(a, n);
		Print(a, n);
		return 0;
}

在这里插入图片描述

希尔排序算法时间复杂度及稳定性分析

时间复杂度分析:

希尔排序的时间复杂度比较难以计算,因为所选的gap不一样,时间复杂度也会有所区别,网上给出希尔排序的平均时间复杂度为O(N^1.3),在这里,根据上述所写代码,计算希尔算法的时间复杂度。
在这里插入图片描述
稳定性分析

在这里插入图片描述

希尔排序的特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 希尔排序的时间复杂度不好计算,需要进行推导,推导出来平均时间复杂度: O(N1.3—N2)
  4. 稳定性:不稳定

选择排序

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

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

在这里插入图片描述
上图排序是依次选择最大数放在最右边,成为升序,也可以依次选择最小数放左边,排成升序。当然排成降序也可以,只要稍微改动一下就行。

选择排序代码实现

这里代码实现与上图展示有一点区别,稍微改动一下:

同时选出最大数与最小数,最大数放在右边,最小数放在左边,这样排序的效率会增加一倍。

注意:

当将找到最大数与最小数时,开始交换,最大数与最右边数交换,最小数与最左边数交换。可能会出现一种情况,就是最大数与最右边数交换时,最右边数恰好时最小数,这样就把最小数换走了,原来标记最小数的位置也不是最小数了,最小数就被换成在最大数的位置了,这样排序就会产生错误。

例:
在这里插入图片描述

#pragma once
#include<time.h>
#include<stdlib.h>

//打印
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
// 选择排序
void SelectSort(int* a, int n)  //{ 0, 1, 5, 3, 6, 7, 4, 2, 8, 9 }
{
	int left = 0;
	int right = n - 1;
	while (left < right)
	{
		//选出最大的放在右边,最小的放在左边
		int max = left;
		int min = left;
		for (int i = left; i <= right; i++)
		{
			if (a[i] > a[max])
			{
				max = i;
			}
			else if (a[i] < a[min])
			{
				min = i;
			}
		}
		Swap(&a[max], &a[right]);
		//有可能最小的位置在最右边,被max换走了,换到最大的位置去了
		if (right == min)
		{
			min = max;      //把最大的位置给最小的位置
		}
		Swap(&a[min], &a[left]);
		left++;
		right--;
		//PrintArray(a, n);
	}
}
int main()
{
	int arr[] = { 5,8,6,3,9,2,1,7 };
	int n = sizeof(arr) / sizeof(arr[0]);
	SelectSort(arr,  n);
	PrintArray(arr, n);
	return 0;
}

在这里插入图片描述

选择排序算法时间复杂度及稳定性分析

时间复杂度分析:

因为选择排序,需要遍历选数,假设每次只选一个数,时间复杂度为:
N+(N-1)+(N-2)+…+2+1=((N+1)*N)/2,即时间复杂度为 O(N^2),
哪怕,每次选两个数,最大值和最小值排序进行排序,效率也只提升一倍,时间复杂度还是O(N^2)

稳定性分析:

举例说明:
在这里插入图片描述
直接选择排序的特性总结:

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

堆排序

基本思想

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

1、堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆(大堆或小堆),才能调整

向下调整
在这里插入图片描述

2、堆的创建
下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆
在这里插入图片描述
在此对数组 a[]={20,17,4,16,5,3}排升序。

排序要先建堆,排升序建大堆,排降序,建小堆,在排序的过程种会破环堆结构,需要进行向下调整,重新恢复堆结构

在这里插入图片描述

堆排序代码实现

#pragma once
#include <stdio.h>
#include<stdlib.h>
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

// 向下调整算法
void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child++;      //选出左右孩子中大的那一个
		}
		if (a[child] > a[parent])   //孩子大于父亲
		{
			//交换孩子与父亲位置
			Swap(&a[parent], &a[child]);
			//将孩子的位置给父亲,父亲再向下调整
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//O(N*log(N))
void HeapSort(int* a, int n)
{
	//先建堆
	 //从最后一个非叶子位置开始调整
	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;
	}
}

int main()
{
	int a[] = { 20,17,4,16,5,3 };
	int n = sizeof(a) / sizeof(a[0]);
	HeapSort(a, n);
	PrintArray(a, n);
	return 0;
}

在这里插入图片描述

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

时间复杂度分析:
可能有些难以理解堆排序的时间复杂度,记住就行O(N*logN)
在这里插入图片描述

稳定性分析:

在这里插入图片描述

堆排序的特性总结:

  1. 堆排序使用堆来选数,效率就高了很多。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(1)
  4. 稳定性:不稳定

冒泡排序

基本思想:

就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,冒泡排
序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
例:
在这里插入图片描述

冒泡排序代码实现

#pragma once
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#include<stdio.h>


void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void BubbleSort(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n - 1; i++)//总趟数
	{
		int exchange = 0;
		int j = 0;
		for (j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
				exchange = 1;
			}
		}
		//if (exchange == 0)
		//{
		//	break;       //如果本来就是有序的,直接退出循环
		//}
		printf("第%d趟排序:",i+1);
		PrintArray(a, n);
	}
}

int main()
{
	int a[] = { 3,6,4,2,11,10,5 };
	int n = sizeof(a) / sizeof(a[0]);
	BubbleSort(a, n);
	return 0;
}

在这里插入图片描述

冒泡排序算法时间复杂度及稳定性分析

时间复杂度分析:

由图可知:冒泡排序总共需要排(n-i)趟,每趟需要排(n-i)次;所以时间复杂度为:
(N-i)*(N-i),即O(N^2).

稳定性分析:
因为冒泡排序,是前一个数和后一个数相比,如果大于就交换,小于等于,就不交换,所以冒泡排序稳定。

在这里插入图片描述

冒泡排序的特性总结:

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

快速排序

快速排序基本思想

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

注意:基准值一般选取最左边元素或是最右边元素
1. hoare版本–左右指针法

选出一个基准量keyi,一般选最左边数的下标的或是最右边数的下标。
1、右指针right向左走,找比key小的数,找到后停下,
2、左指针left向右走,找比key大的数,找到后停下。
3、此时,交换左右指针所指向的数
4、接下重复1、2步骤。直至左右指针相遇
5、左右指针相遇后,交换keyi的值和left指向的值。
6、此时,单趟排序结束。以原keyi的值,将数组分为两个子区间,每个子区间重复步骤1、2、3、4、5、6,直至整个数组有序。

注意:选最左边数作为基准值,让右指针先走,选最右边数作为基准值,让左指针先走
在这里插入图片描述
2. 挖坑法
1、选择最左边数或是最右边数作为key,此时被选走的数就留下一个坑(hole)。
2、选左边的数做key,让右指针先走,找小于key的数,放入坑中,此时右指针成为新的坑。左指针再走,找大于key的数,放入新的坑中。再右指针找比key小的数…直至,左右指针相遇,将key放入坑中,第一趟排序结束。
3、再对第一趟所选的key的左右区间递归行排序,直至整个数组有序。
在这里插入图片描述
3、前后指针法
cur跟prev开始一前一后,cur去找比keyi位置小的值,找到以后,++prev,再交换prev和cur位置的值,直到数组尾,最后交换prev和keyi对应的值。一趟排序结束。
在这里插入图片描述

快速排序三种方法代码实现

1. hoare版本–左右指针法

#pragma once
#include <stdio.h>
#include<stdlib.h>

//打印
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] < a[mid])
	{
	     //a[left]<a[mid]<a[right]
		if (a[mid] < a[right])  //mid在中间

		{
			return mid;
		}
		//a[right]<a[left]<a[mid]
		else if (a[left] > a[right])  //right最小
		{
			return left;
		}
		//a[left]<a[right]<a[mid]
		else
		{
			return right;
		}
	}
	else
	{
	   //a[right]<a[mid]<a[left]
		if (a[mid] > a[right])
		{
			return mid;
		}
		//a[mid]<a[right]<a[left]
		else if (a[right] < a[left])
		{
			return right;
		}
		//a[mid]<a[right]<a[left]
		else
		{
			return left;
		}
	}

}
// 快排  双指针法
// hoare版本 -- 左右指针法
int PartSort1(int* a, int left, int right)
{
	// 三数取中
	int midIndex = GetMidIndex(a, left, right);
	Swap(&a[left], &a[midIndex]);

	int keyi = left;
	while (left < right)
	{
		// 找小
		while (left < right && a[right] >= a[keyi])
			--right;

		// 找大
		while (left < right && a[left] <= a[keyi])
			++left;

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

	return left;
}

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi = PartSort1(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

int main()
{
	int a[] = { 6,1,2,7,9,3,4,5,10,8 };
	int n = sizeof(a) / sizeof(a[0]);
	QuickSort(a, 0, n - 1);
	PrintArray(a, n);
	return 0;
}

在这里插入图片描述

2. 挖坑法

#pragma once
#include <stdio.h>
#include<stdlib.h>

//打印
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])  //mid在中间

		{
			return mid;
		}
		else if (a[left] > a[right])  //mid最小
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}

}

// 挖坑法
int PartSort2(int* a, int left, int right)
{
	int midIndex = GetMidIndex(a, left, right);
	Swap(&a[left], &a[midIndex]);

	int key = a[left];
	while (left < right)
	{
		// 找小
		while (left < right && a[right] >= key)
		{
			--right;
		}
		// 放到左边的坑位中,右边就形成新的坑
		a[left] = a[right];
		// 找大
		while (left < right && a[left] <= key)
		{
			++left;
		}
		// 放到右边的坑位中,左边就形成新的坑
		a[right] = a[left];
	}
	a[left] = key;
	return left;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi = PartSort2(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

int main()
{
	int a[] = { 6,1,2,7,9,3,4,5,10,8 };
	int n = sizeof(a) / sizeof(a[0]);
	QuickSort(a, 0, n - 1);
	PrintArray(a, n);
	return 0;
}

在这里插入图片描述
3、前后指针法

#pragma once
#include <stdio.h>
#include<stdlib.h>

//打印
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])  //mid在中间

		{
			return mid;
		}
		else if (a[left] > a[right])  //mid最小
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}

}

// 前后指针法
int PartSort3(int* a, int left, int right)
{
	//int midIndex = GetMidIndex(a, left, right);
	//Swap(&a[left], &a[midIndex]);

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

		++cur;
	}

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

	return prev;
}
void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
		return;
	int keyi = PartSort3(a, begin, end);
	QuickSort(a, begin, keyi - 1);
	QuickSort(a, keyi + 1, end);
}

int main()
{
	int a[] = { 6,1,2,7,9,3,4,5,10,8 };
	int n = sizeof(a) / sizeof(a[0]);
	QuickSort(a, 0, n - 1);
	PrintArray(a, n);
	return 0;
}

在这里插入图片描述

快速排序算法时间复杂度及稳定性分析

时间复杂度分析:
在这里插入图片描述
稳定性分析:
在这里插入图片描述
快速排序的特性总结:

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

归并排序

归并排序基本思想

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

在这里插入图片描述

归并排序代码实现

#pragma once
#include<stdlib.h>
#include<stdio.h>
// 时间复杂度:O(N*logN)
// 空间复杂度:O(N)

void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

void _MergeSort(int* a, int left, int right, int* tmp)
{
	if (left >= right)
		return;

	int mid = (left + right) >> 1;
	// [left, mid][mid+1,right]
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid + 1, right, tmp);

	// 两段有序子区间归并tmp,并拷贝回去
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;
	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++];

	// 归并完成以后,拷贝回到原数组
	for (int j = left; j <= right; ++j)
		a[j] = tmp[j];
}

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);
}

int main()
{
	int a[] = { 10,6,7,1,3,9,4,2 };
	int n = sizeof(a) / sizeof(a[0]);
	MergeSort( a, n);
	PrintArray(a, n);
	return 0;
}

在这里插入图片描述

归并排序算法时间复杂度及稳定性分析

时间复杂度分析:

在这里插入图片描述

稳定性分析:
在这里插入图片描述

归并排序的特性总结:

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

计数排序

计数排序基本思想
思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  1. 统计相同元素出现次数
  2. 根据统计的结果将序列回收到原来的序列中

绝对映射:
1、统计数组a中的最大值max,根据最大值,开辟计数数组空间count,
int* count=(int*)malloc(sizeof(int)*(max+1))
2、统计出数组a中每个数出现的次数,出现一次就在
count数组中对应出现数的下标的位置+1,
for(int i=0;i<n;i++)
count[a[i]]++;

绝对映射

在这里插入图片描述
相对映射
在这里插入图片描述

计数排序代码实现

#pragma once
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#include<string.h>


//打印
void PrintArray(int* a, int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}
void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for (int i = 0; i < n; ++i)
	{
		if (a[i] > max)
			max = a[i];

		if (a[i] < min)
			min = a[i];
	}

	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	for (int i = 0; i < n; ++i)
	{
		count[a[i] - min]++;
	}

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

	free(count);
}

int main()
{
	int a[] = { 2,5,3,0,2,3,0,3 };
	int n = sizeof(a) / sizeof(a[0]);
	CountSort(a, n);
	PrintArray(a, n);
	return 0;
}

计数排序算法时间复杂度分析

时间复杂度分析:
在这里插入图片描述

计数排序的特性总结:

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

八大排序“华山论剑”,谁能称帝

排10万个数,看各大排序的性能,计数排序局限性太大,这里不予考虑

#define _CRT_SECURE_NO_WARNINGS
#include "Sort.h"


void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	int* a8 = (int*)malloc(sizeof(int) * N);

	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
		a8[i] = a1[i];

	}

	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();

	
	int begin7 = clock();
	CountSort(a7, N);
	int end7 = clock();

	int begin8 = clock();
	BubbleSort(a8, N);
	int end8 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("CountSort:%d\n", end7 - begin7);
	printf("BubbleSort:%d\n", end8 - begin8);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
	free(a8);
}


int main()
{
	TestOP();
	return 0;
}

在这里插入图片描述
去掉几个时间复杂度是O(N^2)的排序,排100万个数在这里插入图片描述
排1000万个数
在这里插入图片描述
从图综合看出,快速排序是最优的排序,归并排序要开辟O(N)的空间,综合来说不如快速排序。

总结

在这里插入图片描述
至此,整个排序算法终于结束!欢迎来讨论

  • 21
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值