数据结构与算法 五大算法

文章目录

1,时间复杂度与空间复杂度

2,插入排序

3,希尔排序

4,选择排序

1,单趟排序

2,选择排序PLUS版本

5,冒泡排序

6,快速排序

1,hoare版本

2,挖坑法


前言

本期将学习五种基本的排序方法,学完c语言的基本知识就可以学习这个算法思想,我们会以空间复杂度和时间复杂度来对比这几个算法,我们应该要学会这些算法的思想来帮助我们来破解一些算法的题目得出最优的解法


一,时间复杂度与空间复杂度

时间复杂度的计算(描述算法执行的时间随输入数据的规模增长而增长的变化趋势)通常用大写的字母O进行表示

计算时间复杂度的步骤如下:

1,确定基本操作:

找出算法中最关键的步骤,找到他执行的次数最多的操作(一般为循环和递归的深度)

2,计算基本操作执行的次数:

根据输入的数据的规模N,计算机基本操作执行的次数,这可能涉及到循环和递归中的操作

3,最后用O进行标记,将基本操作的执行的次数用O进行标记,忽略常数项,低阶项,保留最大项


这里我们那插入排序来举一个里子怎么计算时间复杂度即可

以最坏的情况来举例子,也就是完全逆序,循环的外层是需要执行的次数,内存循环执行的是用来移动元素来插入

最坏的打算,完全逆序

内存循环需要执行i次(i是从1~n-1)这个是随着循环次数逐步增加的,所以我们来算内存循环执行的次数就是用等差数列的公式即可,算出的答案为(n-1)n/2,我们忽略常数项和低阶项,所以得出来为n的二次方则空间复杂度则为O(N的二次方)

那为什么这个外出循环不算进去呢?这不也是有次数嘛?其实呀这个为常数时间复杂度就被忽略了,我们来学习常数时间复杂度

O(1)为常数时间复杂度,通常有着几个情况:

1,固定循环的次数,这是固定的次数

2,基本的操作,如赋值运算,四则运算,这也是固定的次数

3,访问数组的元素和列表的元素,通过索引直接访问数组和列表的元素,时间为常数,因为数组和列表元素,在内存是连续储存的,可以通过计算偏移量来访问数组的元素

4,递归调用的栈空间

5,访问哈希表

以最好的情况来举例子,顺序是完全有顺序的

我们可以知道,这个只是遍历一次的,因为后面那个去一个一个去找空根本不用找,直接就是固定在原地的,所以就是n次,所以时间复杂度就是O(N)

一般情况下插入排序不会出现这个最后的情况,哪有全是顺序的呢?所以一般都是O(n的二次方)


空间复杂度

空间复杂度是衡量一个算法在运行过程中占用存储空间大小的度量,它与时间复杂度类似,都是用来描述算法的效率,空间复杂度关注的是算法执行过程中临时占用的存储空间,通常用输入规模的函数来表示

计算空间复杂度的步骤如下:

1. 确定基本单位:首先确定算法中占用空间的基本单位,比如变量、数据结构等

2. 分析变量:分析算法中的变量,包括局部变量、全局变量等,确定它们的占用空间

3. 分析数据结构:分析算法中使用到的数据结构,如数组、链表、树、图等,确定它们的占用空间

4. 考虑递归:如果算法是递归的,需要考虑递归调用时的栈空间占用

5. 考虑输入输出:有时算法的输入输出也会影响空间复杂度,但通常不考虑输入输出本身占用的空间

6. 确定空间复杂度:将以上所有占用的空间加起来,得到算法的空间复杂度

空间复杂度通常用大O表示法来表示,例如O(1)表示常数空间复杂度,O(n)表示线性空间复杂度,O(n^2)表示平方空间复杂度等


二,插入排序

1,步骤(从小到大排序):

1.需要一个有序的数列来进行插入,所以我们默认把第一个元素视为有序的一个数列

2.取下一个元素tem,从已经排好的元素从后面往前排

3.该的元素大于tem的话那么tem就要往后面移动,知道找到比他小的元素

4.找到之后就直接插入到这个小的元素的前面


思想:

插入排序是必须需要一个有序的数列的,然后从无序的数列进行逐个在有序的数列进行插入,所以我们就默认数列里面的第一个元素为有序的数列

我们来举一个例子

这样进行逐个的插入就可以了接下来我们就来用代码实现一下


#include<stdio.h>
void InsertSort(int *arr,int n) {
	for (int i = 0; i < n-1; i++) {
		int end = i;
		int tem = arr[end + 1];
		while (end >= 0) {
			if (tem < arr[end]) {
				arr[end + 1] = arr[end];
				end--;
			}
			else
				break;
		}
		arr[end + 1] = tem;
	}
}
int main() {
	int a[6] = { 8,7,5,3,4,1};
	int n = sizeof(a)/sizeof(a[0]);
	InsertSort(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

我们根据这个代码来看看怎么实现这个插入排序的 

对main函数的写法,想必大家都已经熟练掌握了,但是这个插入排序呢?

int end = i;
		int tem = arr[end + 1];
		while (end >= 0) {
			if (tem < arr[end]) {
				arr[end + 1] = arr[end];
				end--;
			}
			else
				break;
		}
		arr[end + 1] = tem;

1,定义:这里定义了一个end,这个是用来整有序序列最后一个值的,然后tem就是在有序数列后面那个待插入的元素

2,while循环:这里是利用end>=0来当条件,当tem小于的话则执行if语句里面的语句,这个意思就是把元素往后面移动,然后进行end--,如果不是的话,则执行else语句,注意这个插入排序是一个有序的数列,所以有一次不符合if语句的条件就可以直接跳出来了,因为后面都不符合,是有序的

3,为什么最后一个要加一个1,因为这个加一个1是在当前这个较小的元素前面进行插入,所以要+1


代码总结

1,我们需要有一个变量保留最后一项这样就方便逐个排序

2,我们用一个tem来表示这个end后面的一个元素,用这个来逐个判断

3,循环我们要知道这个前面插入的是一个有序的序列,所以我们只要if语句那个不成立,后面也就是一样的,直接跳出循环来进行赋值

4,外层循环记得要n-1


时间复杂度:

最坏的情况就是O(N的2次方),此时待排序的是逆序,或者接近逆序

最好的情况就是O(N的1次方),此时待排序的是顺序,或者接近逆序


三,希尔排序

 步骤:

这里比较抽象,我们结合图来讲 

上面那个是动图,如果没有看懂也没关系,我们来仔细理解一下

这里是希尔排序的详细解释,我们可以理解一下,这里的gep是任意取的,但是我们用一个除法,进行有规律的解更好,也很具有可读性,下面我们用代码实现一下这个希尔排序


#include<stdio.h>
void ShellSort(int A[],int n) {
	int gep, j, i;
	int num=0;
	for (gep = n / 2; gep <= 1; gep = gep / 2) {
		for (i = gep; gep <= n; gep++){
			if (A[i] > A[i - gep]) {
				num = A[i];
			}
			for (j = i - gep; num < A[j] && j>0; j = j - gep) {
				A[j + gep] = A[j];
				A[j] = num;
			}
		}
	}
}
int main() {
	int a[6] = { 8,7,5,3,4,1};
	int n = sizeof(a)/sizeof(a[0]);
	ShellSort(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

这里又是一个也有很多的难点,我们逐步来分析

第一个循环是每一个gep的取值都要详细的说明,所以为什么要有规律的取gep就是因为这样的话你的代码具有很强的可读性

第二个循环是针对每一组的数据的调序的

第三个循环是利用一个插入排序来求解的,num<A[ j ]这个玩意就是如果有这个的话就是一直我那个后面进行移动元素,然后把num放到那个元素里面去


代码总结

我们子啊写这个代码的时候,我们要融入插入排序的思想,进行排序,然后有一个循环控制gep,一个循环控制每一组,一个循环控制排序

时间复杂度平均:O(N^1.3)
空间复杂度:O(1)


四,选择排序

1,单趟选择排序法

步骤:

先取一个元素,然后在其他元素里面去寻找找到最值,放到取到的元素里面

思想:

先取a[ 0 ],然后在a[ 1 ]~a[ n ]去寻找到的最值(这里看用户的需求,是按照从小到大还是从大到小,如果是从小到大就是寻找到最小值,然后把最小值放到a[ 0 ]处)

再取a[ 1 ],然后在a[ 2 ]~a[ n ]取寻找最值,然后把最值放入到a[ 1 ],然后后面就是重复上面的动作了

我们再来看一下代码该怎么写,用这个思想

这里有两种代码,一个是我写的,一个是网上的,我们来看看这两个代码有什么不同之处

我写的代码

#include<stdio.h>
void SelectSort(int A[],int n) {
	int k=0,i=0;
	for(k=0;k<=n-1;k++) {
		int idenx = k;
		for (i = k + 1; i <= n-1; i++) {
			if (A[i] < A[idenx]) {
				idenx = i;
			}
	      }
		int temp = A[k];
		A[k] = A[idenx];
		A[idenx] = temp;
     }
}
int main() {
	int a[6] = { 5,2,3,7,6,4};
	int n = sizeof(a)/sizeof(a[0]);
	SelectSort(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

这个代码我就把这个思想转换到代码里面来实现了,我这里使用下角标来寻址,首先用一个外循环来定位每一个就是a【0】,a【1】等等,然后用一个内循环来寻找最小值的下角标,然后进行换值操作

这个是网上的代码

#include<stdio.h>
#include<utility>
using namespace std;
void SelectSort(int A[],int n) {
	for (int i = 0; i < n; i++) {
		int start = i;
		int min = i;
		while (start < n) {
			if (A[min] > A[start]) {
				min = start;
			}
			start++;
		}
		swap(A[i], A[min]);
	}
}
int main() {
	int a[6] = { 5,2,3,7,6,4};
	int n = sizeof(a)/sizeof(a[0]);
	SelectSort(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

这个是利用c++这个的函数swap,这个是用来交换这个值的,我们先用start和min来存储这个下角标,然后用while循环来寻找这个i值存储到min,最后用swap来交换这个值 


我们来对代码进行一下总结,我们要用一个数组遍历这个次数,就是有n个元素,要遍历n-1次,然后再用另外一个循环来寻找这个最值的下角标,最后进行交换即可

时间复杂度:最坏情况:O(N^2)
      最好情况:O(N)
空间复杂度:O(1) 


2,选择排序PLUS版本

我们在序列中依次找到最大值和最小值,然后放入头和尾

#include<stdio.h>
#include<utility>
using namespace std;
void SelectSortPLUS(int A[],int n) {
	int begin = 0; int end = n - 1;
	while (end > begin) {
		int Maxi = begin;
		int Endi = begin;
		for (int i = begin + 1; i <= end; i++) {
			if (A[i] > A[Maxi]) {
				Maxi = i;
			}
			if (A[i] < A[Endi]) {
				Endi = i;
			}
		}
		swap(A[begin], A[Endi]);
		if (begin == Maxi) {
			Maxi = Endi;
		}
		swap(A[end], A[Maxi]);
		++begin;
		--end;
	}
}
int main() {
	int a[6] = { 5,2,3,7,6,4};
	int n = sizeof(a)/sizeof(a[0]);
	SelectSortPLUS(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

代码总结:

我们这里定义一个 begin  end来存放这个开头和结尾,定义一个Maxi  Endi来存放这个最大值和最小值,然后要考虑一个情况,当我们把这个最小值和数组的0下角标互换之后,这个最大值如果在0那个位置,就要把Maxi的位置改为Endi,然后再进行互换 


五,冒泡排序

这个冒泡排序非常的简单,这个就是把值逐渐冒泡冒上来

由于过于简单所以直接上代码看

#include<stdio.h>
#include<utility>
using namespace std;
void SortPLUS(int A[],int n) {
	for (int i = 0; i < n - 1; i++) {
		for (int j = 0; j < n - 1 - i; j++) {
			if (A[j] > A[j + 1]) {
				int temp = A[j];
				A[j] = A[j + 1];
				A[j + 1] = temp;
			}
		}
	 }
}
int main() {
	int a[6] = { 5,2,3,7,6,4};
	int n = sizeof(a)/sizeof(a[0]);
	SortPLUS(a, n);
	for (int i = 0; i < 6; i++) {
		printf("%d ", a[i]);
	}
}

这里的冒泡排序这里的j<n-1-i,因为这里是因为没冒泡一次就是固定了一个数据,所以每次就是减少一个数据,然后进行互换值,注意这个if语句,这个if语句,里面是如果一直大于的话就一直把这个值冒泡上去,如果不是则为该j的值来进行冒泡,把最大的冒泡上去

六,快速排序 

1,hoare版本

步骤

这个是选出一个key的值(一般选最左边和最右边,也可以任意取,但是最左边和最右边更加方便)

然后定义一个begin和end,begin从左往右走,end从右往左走

然后让begin找到比key大的数字,让end找到比key小的数字进行互换,注意选取最左边,end先走,选取最右边最右边先走

我们以一个例子来分析一下这个hoare版本

这个是分析序列的方法,利用快速排序的方法,来解决这个排序的问题 

接下来我们看代码来理解 

//快速排序   hoare版本(左右指针法)
void QuickSort(int* arr, int begin, int end)
{
	//只有一个数或区间不存在
	if (begin >= end)
		return;
	int left = begin;
	int right = end;
	//选左边为key
	int keyi = begin;
	while (begin < end)
	{
		//右边选小   等号防止和key值相等    防止顺序begin和end越界
		while (arr[end] >= arr[keyi] && begin < end)
		{
			--end;
		}
		//左边选大
		while (arr[begin] <= arr[keyi] && begin < end)
		{
			++begin;
		}
		//小的换到右边,大的换到左边
		swap(&arr[begin], &arr[end]);
	}
	swap(&arr[keyi], &arr[end]);
	keyi = end;
	//[left,keyi-1]keyi[keyi+1,right]
	QuickSort(arr, left, keyi - 1);
	QuickSort(arr,keyi + 1,right);
}

挖坑法就是多练一个变量而已

//快速排序法  挖坑法
void QuickSort1(int* arr, int begin, int end)
{
	if (begin >= end)
		return;
	int left = begin,right = end;
	int key = arr[begin];
	while (begin < end)
	{
		//找小
		while (arr[end] >= key && begin < end)
		{
			--end;
		}
		//小的放到左边的坑里
		arr[begin] = arr[end];
		//找大
		while (arr[begin] <= key && begin < end)
		{
			++begin;
		}
		//大的放到右边的坑里
		arr[end] = arr[begin];
	}
	arr[begin] = key;
	int keyi = begin;
	//[left,keyi-1]keyi[keyi+1,right]
	QuickSort1(arr, left, keyi - 1);
	QuickSort1(arr, keyi + 1, right);
}

总结

我们学习了各种各样的算法来解决问题,我们要熟练掌握每个方法

1,插入排序就是要的一个end来逐步的进行元素的下角标的减少,然后根据end+1来进行插入,这里需要注意的是前面为有序的数列

2,希尔排序就是要gep,外围的循环就是要用来gep的计算,内部的循环就是第一个是要用来分组的用gep,然后最后一个循环就是一个插入排序,这里的for循环写入一个a[j]>num,这个是由于前面为有序的序列,然后里面就是就是要不断的推进与赋值

3,选择排序就是要有一个起始的下角标,然后用循环找到最值的下角标,然后进行互换即可,PLUS版本就是多了一个if和一个判断如果最值在最开始的地方的话就要取走end的值

4,冒泡排序就是要逐步冒泡上去注意第二个循环要-i,把固定的元素给减掉,if语句的互换是可以判断最值的,因为在排序的过程中,我们如果符合if语句就一定暂时是最值,不成立,下一次就是另外一个了

5,快速排序就是要把那个key学会找和begin和end是怎么移动的,所以我们要好好理解这个begin和end是怎么进行移动的才可以正确理解这个

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

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

相关文章

子类有多个父类的情况下Super不支持指定父类来调用方法

1、Super使用方法 super()函数在Python中用于调用父类的方法。它返回一个代理对象&#xff0c;可以通过该对象调用父类的方法。 要使用super()方法&#xff0c;需要在子类的方法中调用super()&#xff0c;并指定子类本身以及方法的名称。这样就可以在子类中调用父类的方法。 …

Java项目实战II基于微信小程序的消防隐患在线举报系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着城市化进程的加快&…

python学opencv|读取视频(一)灰度视频制作和保存

【1】引言 上一次课学习了用opencv读取图像&#xff0c;掌握了三个函数&#xff1a;cv.imread()、cv.imshow()、cv.imwrite() 相关链接如下&#xff1a; python学opencv|读取图像-CSDN博客 这次课我们继续&#xff0c;来学习用opencv读取视频。 【2】学习资源 首先是官网…

buuctf:被嗅探的流量

解压后用wireshark查看 flag{da73d88936010da1eeeb36e945ec4b97}

数据清洗代码:缺失值,异常值,离群值Matlab处理

目录 基本介绍程序设计参考资料基本介绍 一、过程概述 本过程适用于处理SCADA系统采集到的数据,以及具有类似需求的数据集。处理步骤包括缺失值处理、异常值处理和离群值处理,旨在提升数据质量,增强数据的相关性,同时保持数据的原始特征和随机性。 二、缺失值处理 对于SC…

深入浅出:Go语言中的错误处理

深入浅出&#xff1a;Go语言中的错误处理 引言 在任何编程语言中&#xff0c;错误处理都是一个至关重要的方面。它不仅影响程序的稳定性和可靠性&#xff0c;还决定了用户体验的质量。Go语言以其简洁明了的语法和强大的并发模型而著称&#xff0c;但其错误处理机制同样值得关…

青海摇摇了3天,技术退步明显.......

最近快手上的青海摇招聘活动非常火热&#xff0c;我已经在思考是否备战张诗尧的秋招活动。开个玩笑正片开始&#xff1a; 先说一下自己的情况&#xff0c;大专生&#xff0c;20年通过校招进入杭州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c…

【计算机网络】 —— 数据链路层(壹)

文章目录 前言 一、概述 1. 基本概念 2. 数据链路层的三个主要问题 二、封装成帧 1. 概念 2. 帧头、帧尾的作用 3. 透明传输 4. 提高效率 三、差错检测 1. 概念 2. 奇偶校验 3. 循环冗余校验CRC 1. 步骤 2. 生成多项式 3. 例题 4. 总结 四、可靠传输 1. 基本…

敏捷开发之路

1. 引言 最近有个企业软件开发项目&#xff0c;用户要求采用敏捷开发的方法实施项目。以前也参加过敏捷方法的培训&#xff0c;结合最近找的敏捷开发材料&#xff0c;形成了下面的敏捷实施过程内容。 以下采用了QAD量化敏捷开发方法&#xff0c;关于此方法详细参考内容见最后…

Linux-音频应用编程

ALPHA I.MX6U 开发板支持音频&#xff0c;板上搭载了音频编解码芯片 WM8960&#xff0c;支持播放以及录音功能&#xff01;本章我们来学习 Linux 下的音频应用编程&#xff0c;音频应用编程相比于前面几个章节所介绍的内容、其难度有所上升&#xff0c;但是笔者仅向大家介绍 Li…

电影院订票选座小程序+ssm

题目&#xff1a;电影院订票选座小程序的设计与实现 摘 要 由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致…

【网络】网络基础知识(协议、mac、ip、套接字)

文章目录 1. 计算机网络的背景2. 认识网络协议2.1 协议分层2.2 OS与网络的关系 3. 网络传输基本流程3.1 局域网通信流程3.2 跨网络通信流程 4. Socket 编程预备4.1 理解源IP地址和目的IP地址4.2 端口号与Socket4.3传输层的典型代表4.4 网络字节序 5. socket 编程接口5.1 介绍5.…

Kubernetes(K8S) + Harbor + Ingress 部署 SpringBoot + Vue 前后端分离项目

文章目录 1、环境准备2、搭建 K8S3、搭建 Harbor4、搭建 MySQL5、构建 SpringBoot 项目镜像6、构建 Vue.js 项目镜像7、部署项目 7.1、配置 NameSpace7.2、配置 Deployment、Service7.3、配置 Ingress-Nginx7.4、访问测试 1、环境准备 本次整体项目部署使用的是阿里云ECS服…

自回归模型(AR )

最近看到一些模型使用了自回归方法&#xff0c;这里就学习一下整理一下相关内容方便以后查阅。 自回归模型&#xff08;AR &#xff09; 自回归模型&#xff08;AR &#xff09;AR 模型的引入AR 模型的定义参数的估计方法模型阶数选择平稳性与因果性条件自相关与偏自相关函数优…

学习Python的笔记--面向对象-继承

1、概念 多个类之间的所属关系&#xff0c;即子类默认继承父类的所有属性和方法。 注&#xff1a;所有类默认继承object类&#xff0c;object类是顶级类或基类&#xff1b; 其他子类叫做派生类。 #父类A class A(object):def __init__(self):self.num1def info_print(self)…

2024数字科技生态大会 | 紫光展锐携手中国电信助力数字科技高质量发展

2024年12月3日至5日&#xff0c;中国电信2024数字科技生态大会在广州举行&#xff0c;通过主题峰会、多场分论坛、重要签约及合作发布等环节&#xff0c;与合作伙伴共绘数字科技发展新愿景。紫光展锐作为中国电信的战略合作伙伴受邀参会&#xff0c;全面呈现了技术、产品创新进…

基于STM32的智慧宿舍系统(DAY5)_光照传感器、MQ2、电流传感器、紫外线传感器

注意上述右图的配置需要根据我们实际所使用的通道来&#xff0c;239.5这个是采样时间&#xff0c;根据需要调整&#xff0c;然后我们打到DMA配置项&#xff0c;如下图 这个地方只有DMA模式需要调整&#xff0c;完成上述配置后我们就可以生成代码了&#xff0c;然后我们按照如下…

NDK编译(使用Android.mk)C/C++程序和库

1、编译可执行目标文件 1.1、编写源代码 源代码可以是c或cpp文件&#xff0c;但一定要包含main函数&#xff0c;否则会报错。例如&#xff1a; //test.c #include <stdio.h> int main() {printf("Hello,NDK!"); } 1.2 编写Android.mk文件 编写Android.mk文…

使用Excel 对S型曲线加减速算法进行仿真

项目场景&#xff1a; 项目场景&#xff1a;代码中写了S型加减速算法&#xff0c;相查看生成的加减速数组&#xff0c;直观的展示出来&#xff0c;USB通信一次64字节&#xff0c;对于我几个个32位的频率值不太方便&#xff0c;于是采用Excel进行仿真。 代码中如何生成S加减速曲…

SwiftUI 列表(或 Form)子项中的 Picker 引起导航无法跳转的原因及解决

概述 在 SwiftUI 的界面布局中&#xff0c;列表&#xff08;List&#xff09;和 Form 是我们秃头码农们司空见惯的选择。不过大家是否知道&#xff1a;如果将 Picker 之类的视图嵌入到列表或 Form 的子项中会导致导航操作无法被触发。 从上图可以看到&#xff1a;当在 List 的…