数据结构之各大排序(C语言版)

我们这里话不多说,排序重要性大家都很清楚,所以我们直接开始。

我们就按照这张图来一一实现吧!

一.直接插入排序与希尔排序.

这个是我之前写过的内容了,大家可以通过链接去看看详细内容。

算法之插入排序及希尔排序(C语言版)-CSDN博客

这里就直接赋上代码了

//直接插入排序(升序)
void Insertsort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}
//希尔排序(升序)
void Shellsort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;
		//gap = gap / 3 + 1;
		//先一组组排好
		//for (int i = 0; i < gap; i++)
		//{
		//	for (int j = i; j < n - gap; j += gap)
		//	{
		//		int end = j;
		//		int tmp = arr[end + gap];
		//		while (end >= 0)
		//		{
		//			if (tmp < arr[end])
		//			{
		//				arr[end + gap] = arr[end];
		//				end-=gap;
		//			}
		//			else
		//			{
		//				break;
		//			}
		//		}
		//		arr[end + gap] = tmp;
		//	}
		//}
		//多组并排
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (tmp < arr[end])
				{
					arr[end + gap] = arr[end];
					end-=gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

我们还是分析下他们的时间复杂度吧!

插入排序是通过进行比较来插入的,最坏的情况就是都要比较,所以是O(N^2),最好情况就是本生就是顺序且有序的。

而希尔排序则不同,大家现在当下只需知道大概在O(1.3N)左右即可

直接插入排序的特性总结:
1. 元素集合越接近有序,直接插入排序算法的时间效率越高
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1),它是一种稳定的排序算法
4. 稳定性:稳定
希尔排序的特性总结:
1. 希尔排序是对直接插入排序的优化。
2. gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的比。
3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些书中给出的希尔排序的时间复杂度都不固定,大家就记为 O(1.3N)
4. 稳定性:不稳定

二.选择排序

选择排序的思想就是:找到最值的两个数,分别放在首尾,然后再选择次一级的,知道排好。是不是非常简单,所以这里我们上代码:

//选择排序(升序)
void Swap(int* p, int* q)
{
	int* tmp = *p;
	*p = *q;
	*q = tmp;
}
void Selectsort(int* arr, int n)
{
	int begin = 0;
	int end = n - 1;
	while (begin < end)
	{
		int min = begin;
		int max = begin;
		for (int i = begin + 1; i <= end; i++)
		{
			if (arr[i] < arr[min])
				min = i;
			if (arr[i] > arr[max])
				max = i;
		}
		Swap(&arr[begin], &arr[min]);
		//注意:这里一定要留意max的值是不是begin位置,如果是,前一个交换会影响到后一个,所以要找到正确的位置
		if (max == begin)
			max = min;
		Swap(&arr[end], &arr[max]);
		begin++;
		end--;
	}
}
直接选择排序的特性总结:
1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
2. 时间复杂度:O(N^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

三.堆排序

这个之前也实现过了,可以看链接:

堆排序(C语言版)-CSDN博客

这里还是贴下代码:

void Adjustup(int* arr, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
void Adjustdown(int* arr,int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] > arr[child])
		{
			child++;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
//堆排序
void Heapsort(int* arr, int n)
{
	//建大堆
	/*for (int i = 1; i < n; i++)
	{
		Adjustup(arr, i);
	}*/
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		Adjustdown(arr,i,n);
	}
	//堆删除
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[end], &arr[0]);
		Adjustdown(arr,0, end);
		end--;
	}
}
堆排序的特性总结:
1. 堆排序使用堆来选数,效率就高了很多。
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(1)
4. 稳定性:不稳定

四.冒泡排序和快速排序

这是一个可以说是最简单的排序了,实现的关键就是想好两层循环的条件就行了,我之前也实现过了,大家可以去之前我的文章看看,这里给大家一个链接吧!

冒泡排序即相关想法-CSDN博客

这里直接上代码了,学到这还不会冒泡,建议别在看这个文章了,需要去补就前面的了。

//冒泡排序(升序)
void Bubblesort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
				Swap(&arr[j], &arr[j + 1]);
		}
	}
}

时间复杂度:O(N^2)

下面开始我们可以说是非常难的部分了----快速排序

快速排序是 Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为: 任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

我们先直接学hoare版本吧!

注意:hoare的版本是将key取开头的元素

如果我们按照升序排列,我们要先走右边的,这样才能保证相遇的点其值一定比key点的小,原因如下:

相遇分为以下两种情况:

1.左边相遇右边,即右边停止后左边一直走,没有找到比key大的元素直到相遇,而相遇点是右边找小找到的,说明相遇点比key点小(升序)

2.右边相遇左边,即左边停止后右边一直走,没有找到比key小的元素直到相遇,而相遇点是左边找大找到的,说明相遇点比key点大。(降序)

下面我们实现吧!

//快速排序(升序)
void Swap(int* p, int* q)
{
	int* tmp = *p;
	*p = *q;
	*q = tmp;
}
void Quicksort(int* arr, int begin, int end)//注意end到底是啥?如果是元素个数,下面的right要减1,如果是最后一个元素下标,end=right
{
	//递归结束条件
	if (begin >= end)
		return;
	int left = begin;
	int right = end - 1;
	int key = begin;
	while (left < right)
	{
		//先右
		while (left < right && arr[right] >= arr[key])//右找小
		{
			right--;
		}
		//再左
		while (left < right && arr[left] <= arr[key])//左找大
		{
			left++;
		}
		Swap(&arr[left], &arr[right]);
	}
	//相遇点和key交换
	Swap(&arr[left], &arr[key]);
	//第一次完成
	//下面是递归部分
	key = left;
	Quicksort(arr, 0, key);
	Quicksort(arr, key + 1,end);
}

这个就是hoare版本了,学到这你可能会问一下问题:

1.为什么左边要找大,右边要找小?

我们如果要排升序,是不是从小到大的顺序,如果交换的左右不是大和小,那么你确定是在排序

2.key为啥就是数组开头元素呢?

这个其实只是hoare版本的key找法,实际上还有其他方法的,下面我们就讲解下其他key找法。

//三数取中法
int Mid(int* arr,int begin, int end)
{
	int mid = (begin + end-1) / 2;
	if (arr[begin] > arr[end])
	{
		if (arr[mid] > arr[end])
		{
			if (arr[begin] > arr[mid])
				return mid;
			else
				return begin;
		}
		return end;
	}
	else
	{
		if (arr[mid] > arr[begin])
		{
			if (arr[end] > arr[mid])
				return mid;
			else
				return end;
		}
		return begin;
	}
}

对于上面的内容要注意我们都是end表示为最后一个元素是第几个元素,而非所在的数组下标。

下面我们将其改成数组下标,再写一遍hoare版本的快排。

//三数取中法
int Mid(int* arr,int begin, int end)
{
	int mid = (begin + end) / 2;
	if (arr[begin] > arr[end])
	{
		if (arr[mid] > arr[end])
		{
			if (arr[begin] > arr[mid])
				return mid;
			else
				return begin;
		}
		return end;
	}
	else
	{
		if (arr[mid] > arr[begin])
		{
			if (arr[end] > arr[mid])
				return mid;
			else
				return end;
		}
		return begin;
	}
}
void Quicksort(int* arr, int begin, int end)//end表示最后一个元素下标
{
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	int key = begin;
	while (left < right)
	{
		while (left < right && arr[right] >= arr[key])
			right--;
		while (left < right && arr[left] <= arr[key])
			left++;
		Swap(&arr[left], &arr[right]);
	}
	Swap(&arr[left], &arr[key]);
	key = left;
	Quicksort(arr, 0, key - 1);
	Quicksort(arr, key + 1, end);
}

注意改变之处。

大家可能会发现hoare版本的快排有非常多的点需要注意,于是后人就写出了以下两种不同的写法,对其进行改进。

挖坑法

挖坑法是指先将一个数据储存在数组外面,然后还是右边找小,左边找大,找到就将该位置的数放在原先取出的位置,然后现在的位置即是一个新的坑,一直上述操作直到左右相遇,然后将最开始保存的数据放进相遇位置。

下面我们实现它:

//挖坑法
void partQuicksort(int* arr, int begin, int end)//end表示最后一个元素下标
{
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	int key = arr[begin];
	int hole = begin;//坑
	while (left < right)
	{
		while (left < right && arr[right] >= key)
			right--;
		arr[hole] = arr[right];
		hole = right;
		while (left < right && arr[left] <= key)
			left++;
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	partQuicksort(arr, begin, hole - 1);
	partQuicksort(arr, hole + 1, end);
}
前后指针版本

//前后指针法
void part2Quicksort(int* arr, int begin, int end)//end表示下标
{
	if (begin >= end)
		return;
	int prev = begin;
	int cur = begin + 1;
	int key = begin;
	while (cur <= end)
	{
		if (arr[cur] < arr[key] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}
		cur++;
	}
	Swap(&arr[prev], &arr[key]);
	key = prev;
	part2Quicksort(arr, 0, key - 1);
	part2Quicksort(arr, key + 1, end);	
}

对于快排还可以优化,例如:我们这里实现时发现,大部分的递归都是在元素非常小的时候,所以如果我们可以将这部分改成其他排序,是不是可以节省一部分空间。

我们以hoare版本为例:

void Quicksort2(int* arr, int begin, int end)//end表示最后一个元素下标
{
	if (begin >= end)
		return;
	//如果元素小于等于10,利用其他排序,这里我们选择插入排序
	if (end - begin + 1 <= 10)
	{
		Insertsort(arr+begin, end - begin + 1);//注意这里数组也要变
	}
	else
	{
		int left = begin;
		int right = end;
		int key = begin;
		while (left < right)
		{
			while (left < right && arr[right] >= arr[key])
				right--;
			while (left < right && arr[left] <= arr[key])
				left++;
			Swap(&arr[left], &arr[right]);
		}
		Swap(&arr[left], &arr[key]);
		key = left;
		Quicksort(arr, 0, key - 1);
		Quicksort(arr, key + 1, end);
	}
}

快排学到现在了,大家是不是觉得自己行了???现在我请你实现快排的非递归,请问你如何实现呢?

//快排非递归
int Quicksort3(int* arr, int begin, int end)
{
	//我们这里就用挖坑法实现
	int hole = begin;
	int key = arr[begin];
	while (begin < end)
	{
		while (begin < end && arr[end] >= key)
			end--;
		arr[hole] = arr[end];
		hole = end;
		while (begin < end && arr[begin] <= key)
			begin++;
		arr[hole] = arr[begin];
		hole = begin;
	}
	arr[hole] = key;
	return hole;
}
void QuicksortNone(int* arr, int begin, int end)
{
	SS s1;
	StackInit(&s1);
	StackPush(&s1,begin);
	StackPush(&s1, end);
	while (!StackEmpty(&s1))
	{
		int right = StackTop(&s1);
		StackPop(&s1);
		int left = StackTop(&s1);
		StackPop(&s1);
		int key = Quicksort3(arr, left, right);
		if (left < key - 1)
		{
			StackPush(&s1, left);
			StackPush(&s1,key-1);
		}
		if (right > key + 1)
		{
			StackPush(&s1,key+1);
			StackPush(&s1,right);
		}
	}
	StackDestory(&s1);
}

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

五.归并排序

基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
//归并排序
void _Mergesort(int* arr, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;
	//先递归
	int mid = (begin + end) / 2;
	_Mergesort(arr, begin, mid, tmp);
	_Mergesort(arr, mid + 1, end,tmp);
	//并
	int begin1 = begin;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[i++] = arr[begin1++];
		}
		else
		{
			tmp[i++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = arr[begin2++];
	}
	memcpy(arr + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}
void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	//检查
	if (tmp == NULL)
	{
		perror(tmp);
		return;
	}
	_Mergesort(arr, 0, n-1, tmp);
	free(tmp);
	tmp = NULL;
}

当然,归并排序也可以非递归实现,由于实现过于复杂,所以我们现在就先不是实现了,以后我们会讲的。

大家加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/293634.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring中的工厂类

目录 1.ApplicationContext 4.2.BeanFactory 1.ApplicationContext ApplicationContext的实现类&#xff0c;如下图&#xff1a; ClassPathXmlApplicationContext&#xff1a;加载类路径下 Spring 的配置文件 FileSystemXmlApplicationContext&#xff1a;加载本地磁盘下 S…

安徽2024考试公告一览表!有需要的速收藏

考公群体&#xff0c;越来越多&#xff01; 考研减少的很多学生转来考公&#xff0c;24年国考人数突破291万&#xff0c;较23年增长约40万 考试难度&#xff0c;逐年增大&#xff0c;逐年创新&#xff01; 结束的24国考&#xff0c;江浙省考&#xff0c;学生普遍反映难度增大&a…

[C#]使用OpenCvSharp实现二维码图像增强超分辨率

【官方框架地址】 github.com/shimat/opencvsharp 【算法介绍】 借助于opencv自带sr.prototxt和sr.caffemodel实现对二维码图像增强 【效果展示】 【实现部分代码】 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin…

代码随想录-刷题第四十七天

139. 单词拆分 题目链接&#xff1a;139. 单词拆分 思路&#xff1a;本题可以使用记忆化回溯&#xff0c;不再过多介绍&#xff0c;主要讲解完全背包方法。 单词就是物品&#xff0c;字符串s就是背包&#xff0c;单词能否组成字符串s&#xff0c;就是问物品能不能把背包装满。…

VSCode编辑器下载与安装

1、下载 官网下载地址&#xff1a; 打开下载地址&#xff0c;如下图&#xff0c;根据自己的平台选择相应版本下载&#xff08;本文只针对Windows系统的安装&#xff0c;所以下载Windows版的&#xff09;。 点击会自动下载&#xff0c;下载完成文件如下图&#xff1a; 2、安装…

python入门,list列表详解

目录 1.list的定义 2.index查找某元素的下标 3.修改 ​编辑 4.插入 ​编辑 5.追加元素 1.append,追加到尾部 2.extend,追加一批元素 ​编辑 6.删除元素 1.del 列表[下标] 2.列表.pop(下标) 3.列表.remove(元素) 7.清空列表 8.统计某一元素在列表内的数量 9.计算…

计算机组成原理 控制器

控制器 #mermaid-svg-cDexVavlf0QIZRSF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-cDexVavlf0QIZRSF .error-icon{fill:#552222;}#mermaid-svg-cDexVavlf0QIZRSF .error-text{fill:#552222;stroke:#552222;}#me…

iOS苹果和Android安卓测试APP应用程序的差异

Hello大家好呀&#xff0c;我是咕噜铁蛋&#xff01;我们经常需要关注移动应用程序的测试和优化&#xff0c;以提供更好的用户体验。在移动应用开发领域&#xff0c;iOS和Android是两个主要的操作系统平台。本文铁蛋讲给各位小伙伴们详细介绍在App测试中iOS和Android的差异&…

设计模式之装饰者模式【结构型模式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档> 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某…

Linux内存管理:(五)反向映射RMAP

文章说明&#xff1a; Linux内核版本&#xff1a;5.0 架构&#xff1a;ARM64 参考资料及图片来源&#xff1a;《奔跑吧Linux内核》 Linux 5.0内核源码注释仓库地址&#xff1a; zhangzihengya/LinuxSourceCode_v5.0_study (github.com) 1. 前置知识&#xff1a;page数据结…

【unity】基于Obi的绳/杆蓝图、绳杆区别及其创建方法

绳索 是通过使用距离和弯曲约束将粒子连接起来而形成的。由于规则粒子没有方向(只有位置)&#xff0c;因此无法模拟扭转效应(维基百科)&#xff0c;绳子也无法保持其静止形状。然而&#xff0c;与杆不同的是&#xff0c;绳索可以被撕裂/劈开&#xff0c;并且可以在运行时改变其…

LeetCode(36)有效的数独 ⭐⭐

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff09; 注…

微商城怎么弄才能开通呢?

​微商城的开通&#xff0c;对于许多商家来说&#xff0c;是进入移动电商领域的重要一步。它不仅能帮助你扩大销售渠道&#xff0c;还能让你更好地管理和服务你的客户。那么&#xff0c;微商城怎么弄才能开通呢&#xff1f; 1、注册微信公众号&#xff1a;首先&#xff0c;你需…

奇数码问题

title: 奇数码问题 date: 2024-01-05 11:52:04 tags: 逆序对 cstefories: 算法进阶指南 题目大意 解题思路 将二维转化为一维&#xff0c;求他的逆序对&#xff0c;如果逆序对的奇偶性相同&#xff0c;则能够实现。 代码实现 #include<iostream> #include<string.h&…

《MySQL系列-InnoDB引擎05》MySQL索引与算法

文章目录 第五章 索引与算法1 InnoDB存储引擎索引概述2 数据结构与算法2.1 二分查找法2.2 二分查找树和平衡二叉树 3 B树3.1 B树的插入操作3.2 B树的删除操作 4 B树索引4.1 聚集索引4.2 辅助索引4.3 B树索引的分裂 5 Cardinality值5.1 什么是Cardinality5.2 InnoDB存储引擎的Ca…

本地引入Element UI后导致图标显示异常

引入方式 npm 安装 推荐使用 npm 的方式安装&#xff0c;它能更好地和 webpack 打包工具配合使用。 npm i element-ui -SCDN 目前可以通过 unpkg.com/element-ui 获取到最新版本的资源&#xff0c;在页面上引入 js 和 css 文件即可开始使用。 <!-- 引入样式 --> <…

vue项目使用vue-pdf插件预览pdf文件

1、安装vue-pdf&#xff1a;npm install --save vue-pdf 2、使用 具体实现代码&#xff1a;pdfPreview.vue <template><div class"container"><pdfref"pdf":src"pdfUrl":page"currentPage":rotate"pageRotate&qu…

uniapp中的导入zzx-calendar日历的使用

需求&#xff1a; 一周的日历&#xff0c;并且可以查看当月的 &#xff0c;下个月的&#xff0c;以及可以一周一周的切换日期 借助&#xff1a;hbuilder插件市场中的zzx-calendar插件库 在父组件中引入 并注册为子组件 <template><zzx-calendar selected-change&qu…

C#编程-实现数组

实现数组 数组是相同数据类型的值得集合。例如,您可以创建存储10个整数类型值的数组。数组中的变量称为数组元素。通过使用单个名称和代表数组中元素位置的索引号来访问数组元素。数组是引用类型的数据类型。下图显示系统内存中的数组结构。 声明数组 在程序中使用数组之前需…

Java中的SPI机制

Java中的SPI&#xff08;Service Provider Interface&#xff09;机制是一种服务发现机制。它允许服务提供者在运行时被发现和加载&#xff0c;而不是在编译时。这种机制主要用于实现解耦&#xff0c;使得接口的定义与实现可以独立变化&#xff0c;增强了系统的可扩展性和可替换…