你不得不看,巨详细王大爷表示都看得懂

Posted 程序猿是小贺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你不得不看,巨详细王大爷表示都看得懂相关的知识,希望对你有一定的参考价值。

今天是6月8号高考第二天,先祝考生们金榜题名哈,接着进入正题…

C语言版八大排序算法

1.插入排序

插入排序,一般也被称为直接插入排序。

1.1基本思想

插入排序是指在待排序的元素中,假设前面n-1 (其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。. 按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。

1.2动图演示

静态图解

1.3代码实现

//插入排序
void InsertSort(int arr[], int size)

	//表示当前要插入元素在数组中的下标
	//并且此时i位置的元素一定是要往i之前的位置插入
	for (int i = 1; i < size; i++)
	
		//在i之前找插入的位置,因为此时i之前的位置是有序的
		int end = i - 1;
		int key = arr[i];
		while (end >= 0 && key < arr[end])
		
			arr[end + 1] = arr[end];
			end--;
		
		//判断并插入元素
		arr[end + 1] = key;
	

1.4插入排序特性:

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

判断稳定性方法:如果排序后两个相同的数的相对顺序不会发生改变,则该算法是稳定的;如果排序后,数据的相对次序发生了变化,则该算法是不稳定的。

2.希尔(Shell)排序

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。
2.1基本思想

该方法实质上是一种分组插入方法。比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。算法先将要排序的一组数按某个增量gap分成若干组,每组中记录的下标相差gap对每组中全部元素进行排序,然后再用一个较小的增量对它进行分组,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。
一般的初次取序列的一半为增量,以后每次减半,直到增量为1。

补充:希尔排序的性能依赖于增量序列的选择,源于这点许多大佬给出了很多不同的方法,有位大佬通过大量的实验,给出了较好的结果gap=gap/3+1这一方法时,算法比较和移动的次数约在n(1.25)到(1.6n)1.25之间
2.2动图演示
此处采用的是gap/=2,大家参考

静态图解

2.3代码实现

//希尔排序
void ShellSort(int arr[], int size)

	int gap = size ;
	//gap最小值为1,循环条件必须大于1
	while (gap >1)
	
		gap = gap / 3 + 1;//Knuth大佬提出的取值方式
		//下面类似插入排序,只是此时插入时要间隔gap个元素进行插入
		//i的取值可以按顺序往下取,按顺序插入
		//取元素的时候不需要间隔gap个,不然还需在外面加一重循环
		for (int i = gap; i < size; i++)
		
			int end = i - gap;
			int key = arr[i];
			while (end >= 0 && key < arr[end])
			
				arr[end + gap] = arr[end];
				end -= gap;
			
			arr[end + gap] = key;
		
	

2.4希尔排序特性

  1. 希尔排序是对直接插入排序的进一步优化。
  2. 希尔排序的时间复杂度不好计算,平均时间复杂度: O(N1.3~N2
  3. 稳定性:不稳定
  4. 针对元素数据比较大,杂乱的场景

3.选择排序

选择排序(Selection sort)是一种简单直观的排序算法。
3.1基本思想

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

3.2动图演示

静态图解

3.3代码实现

//交换函数
void Swap(int *left, int *right)

	int tmp = *left;
	*left = *right;
	*right = tmp;

//选择排序
//法一:找到数组中元素最大元素的位置,与数组末尾元素交换,交换完毕size减一
//重复以上步骤,直至size=1
void SelectSort(int arr[], int size)

	while (size >1)
	
		int max = 0;
		//在数组中找出最大元素的位置,然后标记
		for (int i = 1; i < size; i++)
		
			if (arr[i] > arr[max])
			
				max = i;
			
		
		//此时如果max没有在数组末尾,则将max与数组中最后一个元素交换位置
		if (max != size - 1)
		
			Swap(&arr[max], &arr[size - 1]);
		
		//size减一,减去已经拍好的那个最大值的位置,其他位置还是无序的,则循环继续
		//当size等于一时,表示此时数组中只有一个元素,循环结束
		size--;
	

方法改进思路:
我们既然可以找其最大或者最小的位置,那么我能不能同时找最大和最小的位置。这样可以一次找出最大最小两个值,效率更高。
改进代码实现

//方法二:同时找出数组中最大和最小元素的位置,
//循环将i所在位置的元素分别与max,min位置所在的元素进行比较,
//如果该元素大于max所在位置的元素则让max标记该元素
//如果该元素小于民所在位置元素则让min标记该元素
//(注意:此时要先判断min是否在end位置,如果在则将max赋给min)
//此时若max不在end位置,则将max位置的元素与end位置的元素交换
//此时若min不在begin位置,则将min位置的元素与begin位置的元素交换
void SelectSort2(int arr[], int size)

	int begin = 0;
	int end = size - 1;
	//从两端往中间循环,相遇则循环结束
	while (begin < end)
	
		int max = begin;
		int min = begin;
		for (int i = 1; i <= end;i++)
		
			if (arr[i]>arr[max])
			
				max = i;
			
			if (arr[i] < arr[min])
			
				min = i;
			
		
		if (min == end)
		
			min = max;
		
		if (max!= end)
		
			Swap(&arr[max], &arr[end]);
		
		if (min != begin)
		
			Swap(&arr[min], &arr[begin]);
		
		//两端元素排好,中间还是无序,数组缩短往中间靠
		begin++;
		end--;
	

3.4选择排序的特性

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

3.5选择排序的缺陷

存在大量的重复比较,优化----堆排序

4.堆排序

堆排序(Heapsort)是指利用堆这一数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
4.1基本思想

略…

4.2静态图解

4.3代码实现
在我数据结构C语言 《四》二叉树,堆的基本概念以及堆的相关操作实现(上)这一文中有关于堆排序代码实现这里也就不做详细解释了。

4.4堆排序的特性:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

5.冒泡排序

算法名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
5.1基本思想

1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

5.2动图演示

静态图解

5.3代码实现

//冒泡排序
void BubbleSort(int arr[], int size)

	//此层循环控制冒泡的趟数,最后一趟时只剩一个元素,不用比较
	for (int i = 0; i < size - 1; i++)
	
		//定义一个标志位,表示这一趟期间有没有发生交换
		int flag = 0;
		//冒泡方式:相邻两个元素比较,不满足则交换
		for (int j = 0; j < size - i - 1; j++)
		
			if (arr[j]>arr[j + 1])
			
				flag = 1;//有交换将flag赋值为1
				Swap(&arr[j], &arr[j + 1]);
			
		
		//判断此时flag 的值,若为0表示没有交换说明数组已经有序,则直接退出
		if (flag == 0)
			return;
	

5.4冒泡排序特性

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

6.快速排序

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法。

6.1递归方法

基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

实现代码

void QuickSort(int arr[], int left,int right)

	//当数组中数据小于两个时,不用交换
	if (right - left < 2)
		return;
	else
	
		//按基准值div来对区间进行划分
		int div = Divide(arr, left, right);//hoare法
		//int div = Fill(arr, left, right);//填坑法
		//int div = Point(arr, left, right);//前后指针法
		//递归排左边部分
		QuickSort(arr, left, div);
		//递归排右边部分
		QuickSort(arr, div + 1, right);
	

6.2.1Hoare方法

基本思想

见代码段…

动图演示

实现代码

//方法一:hoare版本
int Divide(int arr[], int left, int right)

	int begin = left;
	int end = right - 1;
	int key = arr[end];
	while (begin < end)
	
		//让 begin从前往后找,找到比基准值大的数则停下
		while (begin < end && arr[begin] <= key)
		
			begin++;
		
		//让end从后往前找,找到比基准值小的数停下
		while (begin < end && arr[end] >= key)
		
			end--;
		
		if (begin < end)
		
			//将begin的位置与end位置的数据交换
			Swap(&arr[begin], &arr[end]);
		
	
	if (begin != right-1)
	
		//将此时begin或者end的位置的数据与最后一个位置的数据交换
		Swap(&arr[begin], &arr[right-1]);
	
	return begin;

6.2.2填坑法

动图演示

代码实现

int Fill(int arr[], int left, int right)

	int begin = left;
	int end = right - 1;
	int key = arr[end];//把末尾的值放在key位置,此时end位置是个坑
	while (begin < end)
	
		//让begin往后走,找到比基准值小的数停下
		while (begin < end && key >= arr[begin])
		
			begin++;
		
		//将此时位置的值填在end位置,此位置出现新的坑
		if (begin < end)
		
			arr[end] = arr[begin];
			end--;
		
		//让end从后往前找,找到比基准值小的数停下
		while (begin < end && key <= arr[end])
		
			end--;
		
		//将此时位置的值填在begin位置,此位置出现新的坑
		if (begin < end)
		
			arr[begin] = arr[end];
			begin++;
		
	
	//用key位置的值将这个位置的坑填上
	arr[begin] = key;
	return begin;

6.2.3前后指针法

动图演示

实现代码

//前后指针法
int Point(int arr[], int left, int right)

	int cur = left;
	int prev = cur - 1;
	int key = arr[right - 1];
	//cur从前往后走
	while (cur < right)
	
		//将比基数值大的数往后放,比基数值小的数往前放
		//如果此时cue的值小于基数值并且prev走一步不等于cur,则交换
		if (arr[cur] < key && prev++ != cur)
		
			Swap(&arr[prev], &arr[cur]);
		
		//cur往后走
		cur++;
	
	//最后把基数值的位置的数与prev的下一个位置的数交换
	if (++prev != right - 1)
	
		Swap(&arr[prev], &arr[right - 1]);
	
	return prev;

我们不能保证每次选取的key都是值正中间的那个数,如果基准值取得比较好,则每次都可以划分为在左右均等的两部分,如果序列有序或者接近有序,快排的性能会非常差,结构就会从平衡二叉树转变为单支树。

6.2.4快排优化

尽量避免取到max值或min值,可以使用三数取中法来实现。

三数取中法基本思想

从左侧,中间,右侧各取一个值,比较其大小,取其值大小位于中间的那个树作为基准值。

代码实现

int GitMid(int arr[], int left, int right)

	//mid等于左侧加上右侧除以2,
	int mid = (left + (right - left)) / 2;
	//左比右小
	if (arr[left] < arr[right - 1])
	
		//左比右小且左大于中间,返回最左侧的那个值
		if (arr[left] > arr[mid])
		
			return left;
		
		//左比右小但中间那个比右大,返回右侧的那个值
		else if (arr[mid]>arr[right - 1])
		
			return right - 1;
		
		else
		
			return mid;
		
	
	//与上面比较方法一致
	else
	
		if (arr[right - 1] > arr[mid])
			return right - 1;
		else if (arr[mid] > arr[left])
			return left;
		else
			return mid;
	

优化后以Hoare方法为例,其余方法与其类似,所以在此只实现一种参考。

代码实现

int Divide(int arr[], int left, int right)

	int begin = left;
	int end = right - 1;
	//找到三数取中法取到的中间值,即为基准值
	int mid = GitMid(arr, left, right);
	//将该值与末尾值交换
	Swap(&arr[mid], &arr[right-1]);
	int key = arr[right-1];

	while (begin < end)
	
		//让 begin从前往后找,找到比基准值大的数则停下
		while (begin < end && arr[begin] <= key)
		
			begin++;
		
		//让end从后往前找,找到比基准值小的数停下
		while (begin < end && arr[end] >= key)
		
			end--;
		
		if (begin < end)
		
			//将begin的位置与end位置的数据交换
			Swap(&arr[begin], &arr[end]);
		
	
	if (begin != right-1)
	
		//将此时begin或者end的位置的数据与最后一个位置的数据交换
		Swap(&arr[begin], &arr[right-1]);
	
	return begin;

再次进行优化,如果数据量比较小或者接近有序的时候,最优的方法应该是插入排序,则可设置一个临界条件,将代码再次优化如下,后面与上述实现方式相同

if (right - left <= 一个临界值)

	InsertSort(arr + left, right-left);

快排分析

快速排序基本上被认为是相同数量级的所有排序算法中,平均性能最好的。快速排序是在冒泡排序的基础上改进而来的,冒泡排序每次只能交换相邻的两个元素,而快速排序是跳跃式的交换,交换的距离很大,因此总的比较和交换次数少了很多,速度也快了不少。

6.2非递归方法

借助栈实现
关于栈的创建初始化在我之前的文章数据结构C语言篇《三》栈和队列概念,模拟函数实现,以及相关OJ面试题文中已经实现过,有问题的小伙伴可以去看看,我这里就不过多赘述了。
实现代码

void QuickSortNor(int arr[], int size)

	Stack s;
	StackInit(&s);
	int left = 0;
	int right = size;
	//压栈
	StackPush(&s, left);
	StackPush(&s, right);
	while (!StackEmpty(&s))
	
		//根据栈的特性只能先取右边并出栈再取左边
		right = StackTop(&s);
		StackPop(&s);
		left = StackTop(&s);
		StackPop(&s);
		if (right - left > 1)
		
			//用上述三种方法任意之一即可
			int div = Divide(arr, left, right);
			//此时要想先取左半侧在取右半侧,则需根据栈的特性先要将右半侧压栈再将左半侧压栈
			StackPush(&s, div+1);
			Linux楼下大爷都看得懂的用户权限,文件权限相关指令及其示范!!

Linux楼下大爷都看得懂的用户权限,文件权限相关指令及其示范!!

Python爬虫超详细讲解,零基础入门,老年人都看得懂

人人都看得懂的C语言进阶系列之数据存储

人人都看得懂的前端正则表达式教程

莽村李青都看得懂的Vue响应式原理