【数据结构】八大排序(一)

目录

前言:

直接插入排序

直接插入排序代码实现

直接插入排序特性总结

希尔排序

希尔排序代码实现

希尔排序特性总结

直接选择排序

直接选择排序代码实现

直接选择排序特性总结

堆排序

堆的向下调整算法

建堆

堆排序代码实现

堆排序特性总结


前言:

排序即使得一串记录,按照其中某个或某些关键字的大小,递增或递减的排列起来的操作;

排序分为内部排序外部排序稳定排序非稳定排序

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

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

稳定性:假定在待排序的序列中,如果元素a位于元素b前面,且a=b,经过某种排序方法进行排序之后,元素a依然位于元素b前面,那么这种排序方法就是稳定的,否则就是不稳定的;

衡量一个排序方法的好坏可以根据时间复杂度与空间复杂度,稳定性等综合考虑

常见的排序算法:

本篇主要详细介绍前四种排序,均以升序为例 ;

直接插入排序

直接插入排序的基本思想:

对于一个长度为n的数组a,当插入第 i (i>=1)个元素时,前面的a[0],a[1],......,a[i-1]已经有序,此时只需将a[i]元素与a[i-1],a[i-2],.....的元素进行比较,找到插入位置先将原来位置上的元素依次向后移动,然后在插入位置插入元素a[i];

具体步骤如下

  1. 将待排序的序列分为有序区和无序区,初始时有序区只有一个元素,即序列的第一个元素,无序区包括除第一个元素外的所有元素;
  2. 从无序区中取出第一个元素,将其插入到有序区中的适当位置,使之成为新的有序区;
  3. 重复步骤2,直到无序区中的所有元素都插入到有序区中;
  • 单个数据必然有序,所以将元素3划分为有序区,其他元素均位于无序区,假设有序区最后一个元素下标为end,为防止移动过程中插入元素被覆盖,用tmp保存插入元素的数值;

  • 寻找插入位置,将tmp与数组有序区尾元素a[end]进行比较,若a[end]>tmp,先将有序区尾元素向后移动一位,然后通过控制end即end每次自减1,向左查找下一位,按此循环,直至a[end]<tmp出现或者end = -1;

  • 此时单趟排序已完成,end继续回到有序区的尾元素的位置,进行下一趟排序,若a[end]<tmp,直接插入到end的下一位置即a[end+1]=tmp;

 

 

.......

直接插入排序代码实现

void InsertSort(int* a, int n)
{
	//先写单趟,再写整体;
	for (int i = 0; i <n-1 ; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				//向后挪动一位
				a[end + 1] = a[end];
                end--;
			}
			else
			{
			//考虑极端情况,当插入的数值小于有序区数组每个元素,不断向前挪动过程中,直至end=-1;
			//出现插入的数值大于a[end];
				break;
			}
		}
		a[end + 1] = tmp;
	 }
} 

直接插入排序特性总结

1.时间复杂度

最好情况就是全部有序,此时只需遍历一次,最好的时间复杂度为O(n)
最坏情况全部逆序,内层每次遍历已排序部分,最坏时间复杂度为O(n^2)

综上,因此直接插入排序的平均时间复杂度为O(n^2)

2. 空间复杂度

额外开辟的空间为常数个,所以空间复杂度为O(1);

3.算法稳定性

稳定性指相同元素的前后顺序是否改变,当插入元素小于a[end]时,插入到比它大的数前面,所以直接插入排序是稳定的;

希尔排序

希尔排序的基本思想

希尔排序是一种改进的插入排序算法,其基本思想是将待排序的序列按照一定的间隔(gap)分成若干个子序列,通过插入排序对大间隔的子序列进行排序,使得整个序列基本有序,然后逐步缩小间隔,重复上述操作,直到间隔gap为1,最后对整个序列进行直接插入排序;每次排序让数组接近有序的过程叫做预排序,最后一次排序为直接插入排序

1.  取间隔gap=3对下述序列进行分组,该序列被分成3组,每组之间都是以3的等差数列;

2. 对每一组进行插入排序,得到如下数组

3.缩小间隔,取间隔gap=2对数组进行分组,数组被分成2份,每组之间都是2的等差数列;

4. 对每一组进行插入排序,得到如下数组

5.缩小间隔,取间隔gap=1对数组进行分组,数组相当于没有分组,进行插入排序,当gap=1时为直接插入排序;

  • 如何选择希尔增量gap?

最初Shell提出的增量是 gap = [n / 2],每一次排序完让增量减少一半gap =[ gap / 2],直到gap = 1时排序变成了直接插入排序;后来Knuth提出的gap = [gap / 3] + 1,每次排序让增量成为原来的三分之一,加一是防止gap <= 3时gap = [gap / 3] = 0的发生,导致希尔增量最后不为1,无法完成插入排序;

上述无论哪一种主张都没有得到证明,本文代码实现部分采取gap=[n/2],gap=[gap/2];

希尔排序代码实现

  • 长度为n的数组间距为gap分为一组,共计gap组,定义遍历变量i,首先遍历第一组数据,i从0开始遍历,i每次移动gap步,由于数组尾元素下标为n-1,则i+gap=n-1,为防止数组越界访问,i最后访问位置为 n-1-gap ;

......

第一组排序代码如下:

    for (int i = 0; i < n - gap; i += gap)
		{
			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;
		}
  • 第一组排序完成,外面套一层循环,遍历其余组,便可完成第一趟排序,定义循环变量j,j从0开始,每次自增1,一共有gap组,j小于gap即可排序其他组;
	for (int j = 0; j < gap; j++)
	{
		for (int i = j; i < n - gap; i += gap)
		{
			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;
	}
  • 然后缩小增量 gap = gap /2 直到gap = 1,继续插入排序,直到排序完成;
void ShellSort(int* arr, int n)
{
	int gap = n;
	//预排序:分组排序,间距为gap分为一组,共计gap组;
	//最后进行直接插入排序,即gap==1;
	while (gap > 1)
	{
		gap = gap / 2;
		//其次排序其余gap组,一次预排序完成,会进行多次预排序;
		for (int j = 0; j < gap; j++)
		{
			//首先排序以第一个元素为起点,间距为gap的第一组;相当于整个数组排序;
			for (int i = j; i < n - gap; i += gap)
			{
				//保证[0,end]有序,每个元素间隔为gap,控制[0,end+gap]有序;
				int end = i;
				int tmp = arr[end + gap];
				while (end >= 0)
				{
					if (tmp < arr[end])
					{
						arr[end + gap] = arr[end];
						end = end - gap;
					}
					//else会出现俩种情况;
					//第一种为第一个元素以后且间距为gap的某个位置插入;
					//第二种会不断满足tmp<arr[end],直至end=-gap;
					else
					{
						break;
					}
					//end=-gap的位置,直接将arr[end+gap]=tmp;
					arr[end + gap] = tmp;
				}
			}
		}
	}
}

希尔排序特性总结

1.时间复杂度

希尔排序的时间复杂度取决于增量序列的选择,由于gap取值有多种方法,导致希尔排序的时间复杂度并不固定,一般来说,希尔排序的平均时间复杂度界于 O(n)到 O(n^2)之间, 最好的时间复杂度为 O(n^1.3)

2. 空间复杂度

希尔排序的空间复杂度为O(1),因为在希尔排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

希尔排序是一种非稳定的排序算法;希尔排序的精髓在于按照某个增量来划分子序列,实现跳跃式移动来提高排序效率,而也正是这种跳跃性带来了不稳定性。如果不同子序列中有相等的数,但它们不在同一子序列中,可能会因为子序列内部排序而改变原有的顺序

直接选择排序

直接选择排序的基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,通过交换存放在序列的起始位置(或最后一个位置),直到全部待排序的数据元素排完;

具体来说,就是在待排序序列中,首先找到最小的元素和最大的元素,然后将最小的元素与序列的第一个元素交换位置,最大的元素与序列的最后一个元素交换位置,接着在剩下的元素中找到最小的元素与最大的元素,将最小元素与序列的第二个元素交换位置,最大元素与序列的倒数第二个元素交换位置,以此类推,直到序列中只剩一个元素为止

具体步骤如下:

  1.   遍历整个序列,找到最小的元素与最大的元素;
  2.   将最小的元素与序列的第一个元素交换位置,最大的元素与序列尾元素交换位置;
  3.   序列遍历范围从第二个元素开始到倒数第二个元素,遍历序列,找到剩余元素中的最   小元素与最大元素;
  4.   将最小的元素与序列的第二个元素交换位置,最大的元素与序列倒数第二个元素交换位置;
  5.   重复上述步骤,直到整个列表都被排序;

 1. 定义变量begin,end,确定遍历范围[begin,end] ;定义maxi,mini记录遍历范围内的最大值下标  和最小值下标,最初maxi ,mini 指向遍历范围内的首元素,当在遍历范围内查找到最大值,最小值时更新maxi,mini ;

2. 遍历结束后查找到最大值,最小值,此时将最小的元素与遍历范围内的第一个元素交换位置,最大的元素与遍历范围内的尾元素交换位置;

3.交换完成后,begin自增1,end自减1,确定新的遍历范围,同时maxi ,mini 指向遍历范围内的首元素,当在遍历范围内查找到最大值,最小值时更新maxi,mini ;

4. 交换时先将遍历范围内的最小值与遍历范围的首元素交换,其次将遍历范围内的最大值与遍历范围内的尾元素交换;

当出现上述情况即出现 最大的位置恰好位于begin位置,说明mini是和最大的交换位置,这个时候max的位置就发生了变换, maxi变到了mini的位置,需要 更新max的位置,否则易导致错误,正确做法如下图所示
 

 

......

直接选择排序代码实现

//交换
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)
	{
		//单趟排序,将[begin,end]范围内最大与最小的数据调整于左右;
		int maxi = begin;//最大元素的下标,假设最大元素在最左边;
		int mini = begin;//最小元素的下标,假设最小元素在最左边;
		for (int i = begin+1; i <=end; i++)
		{
			if (arr[i]>arr[maxi])
			{
				maxi = i;
			}
			if (arr[i]<arr[mini])
			{
				mini = i;
			}
		}
		swap(&arr[begin], &arr[mini]);
        //如果最大元素的位置位于begin位置,说明mini和最大的交换位置;
        //maxi的位置变化到mini的位置,要更新maxi;
		if (maxi == begin)
		{
			maxi = mini;
		}
		swap(&arr[end], &arr[maxi]);
		end--;
		begin++;
	}
}

直接选择排序特性总结

1.时间复杂度

数据的比较次数为(n-1)+(n-2)+(n-3)+......1 = n*(n-1)/2,数据的交换次数为n-1,所以 时间复杂度为O(n^2)

2. 空间复杂度

选择排序的空间复杂度为O(1),因为在选择排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

选择排序是一种非稳定的排序算法,具体参见上图红五与白五排序前后的位置;

堆排序

大根堆:任意父节点的数值大于等于孩子节点的数值(a[i] >= a[2i+1] && a[i] >= a[2i+2]);

对堆中的结点按层进行编号,映射到数组便可得其存储结构

小根堆:任意父节点的数值小于等于孩子节点的数值(a[i] <= a[2i+1] && a[i] <= a[2i+2]);

对堆中的结点按层进行编号,映射到数组便可得其存储结构

结论: 大堆堆顶数据的数值最大; 小堆堆顶数据数值最小

堆排序是利用大堆或者小堆堆顶数据最大或最小的特性来进行排序的,所以,我们排序的对象必须是大堆或者小堆,但给定的数据不一定是大堆或者小堆,所以必须先建堆;

堆的向下调整算法

建堆的核心为堆的向下调整算法,堆的向下调整算法的基本思想

从根结点开始,利用假设法寻找同一高度处值最小的孩子,根据其左右子树为小堆还是大堆,按照小堆或大堆的原则将其交换,若为小堆,父节点处的数值大于孩子结点出的数值,则交换彼此位置;若为大堆,父节点处的数值小于孩子结点出的数值,则交换彼此位置;直至叶节点为止或出现满足其逻辑上不满足大小堆的数值为止;

//堆的向下调整算法(建大堆)
void AdjustDown(int* nums, int n, int parent)
{
	//假设法求同一深度左右孩子最小的一个,假设左孩子为最小
	int child = parent * 2 + 1;
	while (child<n)
	{
		if (child + 1 < n && nums[child] < nums[child + 1])
		{
			child++;
		}
		//无论左右孩子,child为最大,调整为大堆;
		if (nums[child] > nums[parent])
		{
			swap(&nums[child], &nums[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

建堆

由于向下调整算法的使用前提是左右子树必须是大堆或者小堆,从倒数第一个非叶子结点开始调整,一定能够保证待排序序列的左右子树都是大堆或者小堆,此时即可看作大堆又可以看作小堆,根据需要进行调整即可;

倒数第一个非叶子结点恰好是最后一个结点的父亲,最后一个结点的下标为n-1,套用公式:parent=(child-1)/2,则该结点下标为:(n-1-1) / 2;

	//建堆 升序建大堆,降序建小堆
    for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}

 详细图解可参考:数据结构之堆-CSDN博客

堆排序的基本思想:

  1. 将待排序序列构造成一个大根堆
  2. 整个序列的最大值就是堆顶的根节点
  3. 堆顶元素数组尾元素进行交换,此时数组尾元素就为最大值;
  4. 将数组尾元素不看做堆里面的数据,前n-1个数继续向下调整为堆,选出次大的数,和倒数第二个位置的数交换;反复执行,得到升序序列;

堆排序代码实现

void HeapSort(int* arr, int n)
{
    //建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, n, i);
	}
    //排序
	int end = n - 1;
	while (end > 0)
	{
		swap(&arr[0], &arr[end]);
		AdjustDown(arr, end, 0);
		end--;
	}
}

堆排序特性总结

1.时间复杂度

堆向下调整算法的时间复杂度为O(logn),堆排序由建堆与排序俩部分组成,建堆的时间复杂度O(n) ;  假设节点数为n,需要进行n-1次交换,每次调整的时间复杂度O(logn),总的时间复杂度为(n-1)*O(logn),所以 堆排序的时间复杂度O(n)+O(n*logn)=O(n*logn)

2. 空间复杂度

堆排序的空间复杂度为O(1),因为在堆排序时是对原数组进行直接排序,并没有创建其他新的数组;

3.算法稳定性

堆排序是一种非稳定的排序算法,堆排序时需要交换堆顶和堆尾的数据,交换时两个相等的数据在序列中的相对位置就可能发生改变,所以堆排序不是一个稳定排序;

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

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

相关文章

【论文阅读】TACAN:控制器局域网中通过隐蔽通道的发送器认证

文章目录 摘要一、引言二、相关工作三、系统和对手模型3.1 系统模型对手模型 四、TACAN4.1 TACAN 架构4.2 发送方认证协议4.3 基于IAT的隐蔽通道4.4 基于偏移的隐蔽通道&#xff08;本节公式格式暂未整理&#xff09;4.5 基于LSB的隐蔽通道 摘要 如今&#xff0c;汽车系统与现…

「Verilog学习笔记」信号发生器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 方波的实现&#xff0c;较为简单&#xff0c;只需要设置一个计数器&#xff0c;使输出保持10个时钟为0&#xff0c;跳变为20&#xff0c;再保持10个时钟。依次循环。可以按…

【python海洋专题四十八】500hpa位势高度场

【python海洋专题四十八】500hpa位势高度场 # -*- coding: utf-8 -*- # ---导入数据读取和处理的模块------- import astimport pandas as pd from netCDF4 import Dataset from pathlib import Path import xarray as xr from datetime import datetime import numpy as np #…

excel单元格内换行按什么快捷键

如果我们使用excel软件的时候&#xff0c;因为一些日常的操作太过繁琐想要简化自己的操作步骤的话&#xff0c;其实是有很多快捷方式在其中的。那么对excel单元格内换行按什么快捷键这个问题&#xff0c;据小编所知我们可以在表格中使用Alt Enter来进行换行。详细内容就来看下…

LangChain 13输出解析Output Parsers 自动修复解析器

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

scanpy 读取mtx文件

import scanpy as sc adata sc.read("./GSE144136_GeneBarcodeMatrix_Annotated.mtx") ##

【bmp文件怎么批量改成JPG?】

操作 在需要修改格式的图片文件夹中新建一个TXT文本文档 文档中输入(ren *.原图片类型 *.需要修改成的图片类型) ren *.bmp *.jpg 输入完成后保存 将刚刚新建的文档重命名 修改为.bat后缀的文件 弹出弹窗&#xff0c;点击是 双击此程序&#xff0c;即可将文件夹中的BMP图…

UniPro集成华为云WeLink 为企业客户构建互为联接的协作平台

华为云WeLink是华为开启数字化办公体验、帮助企业实现数字化转型的实践&#xff0c;类似钉钉。UniPro的客户企业中&#xff0c;有使用WeLink作为协作工具的&#xff0c;基于客户的实际业务需求&#xff0c;UniPro实现了与WeLink集成的能力&#xff0c;以帮助客户企业丰富和扩展…

Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…

【【Linux下的Petallinux 以及其他的配置】】

Linux下的Petallinux 以及其他的配置 sudo apt-get install iproute2 gawk python3 python build-essential gcc git make net-tools libncurses5-dev tftpd zlib1g-dev libssl-dev flex bison libselinux1 gnupg wget git-core diffstat chrpath socat xterm autoconf libtoo…

C语言--每日选择题--Day27

第一题 1. 对于代码段&#xff0c;问下面不可以表示a[1]地址的是&#xff08;&#xff09; int a[10]; A&#xff1a;&a[0] 1 B&#xff1a;a sizeof(int) C&#xff1a;(int*)&a 1 D&#xff1a;(int*)((char*)&a sizeof(int)) 答案及解析 A A&#xff1a;取到…

SpringBoot : ch06 整合 web(二)

前言 SpringBoot作为一款优秀的框架&#xff0c;不仅提供了快速开发的能力&#xff0c;同时也提供了丰富的文档和示例&#xff0c;让开发者更加容易上手。在本博客中&#xff0c;我们将介绍如何使用SpringBoot来整合Web应用程序的相关技术&#xff0c;并通过实例代码来演示如何…

全新爱蜗影视优码双端影视源码v9.1/影视视频APP源码+支持代理/在线支付+支持对应苹果CMS

源码简介&#xff1a; 这个是最新爱蜗影视优码双端影视源码&#xff0c;作为实用方便的影视视频APP源码&#xff0c;它不仅支持代理/在线支付&#xff0c;而且也支持对应苹果CMS。 爱蜗影视优码双端影视支持对应苹果CMS支持代理在线支付 带图文教程&#xff0c;全新美化多功能…

盘点最新的十大地推拉新app推广接单平台,一手接单渠道分享

小编主推&#xff1a;聚量推客 一手官签直营 随着网络的发展&#xff0c;越来越多的app运应而生社交类、购物类、娱乐类、资讯类、教育类、健康类、金融类为了将这些应用程序推广给更多的人使用&#xff0c;地推拉新app推广接单平台应运而生。本文将介绍十大地推拉新app推广接…

几何教学工具 Sketchpad几何画板 mac软件特色

Sketchpad几何画板 for Mac是一款适用于macOS系统的几何教学工具&#xff0c;用户可以在其画板上进行各种几何图形的绘制、演示&#xff0c;帮助教师了解学生的思路和对概念的掌握程度。此外&#xff0c;Sketchpad更深层次的功能则是可以用来进行几何交流、研究和讨论&#xff…

单片机学习11——矩阵键盘

矩阵键盘&#xff1a; 这个矩阵键盘可以接到P0、P1、P2、P3都是可以的。 使用矩阵键盘是能节省单片机的IO口。 P3.0 P3.1 P3.2 P3.3 称之为行号。 P3.4 P3.5 P3.6 P3.7 称之为列号。 矩阵键盘检测原理&#xff1a; 1、检查是否有键按下&#xff1b; 2、键的抖动处理&#xf…

电机工作制

电机工作制 1.什么是电机工作制2.电机工作制的分类 最近在做电机控制器&#xff0c;看到很多电机铭牌上写着工作制&#xff1a;S*&#xff0c;有S1&#xff0c;有S2&#xff0c;查了一下&#xff0c;学习一下是什么意思。 1.什么是电机工作制 根据GBT 755-2019《旋转电机定额…

机器学习的复习笔记4-岭回归与多项式回归

一、岭回归 在简单的线性回归中&#xff0c;一味追求平方误差最小化&#xff0c;R2值尽可能大&#xff0c;可能会受到噪声的严重干扰。噪声&#xff0c;即偶发的错误的值。 如图&#xff0c;若为满足所有点的拟合&#xff08;虚线&#xff09;&#xff0c;表面上看R2值小&…

springboot+vue实现websocket通信实例,进入页面建立连接

springbootvue实现websocket通信实例 进入页面建立连接 前端代码&#xff1a; <template><div class"app-container"><el-form :model"queryParams" ref"queryForm" size"small" :inline"true" v-show&qu…

数据治理:数据交换与数据集成

数据交换 基本概念 数据交换是将符合一个源模式的数据转换为符合目标模式数据的问题&#xff0c;该目标模式尽可能准确并且以与各种依赖性一致的方式反映源数据。 早期数据交换的一个主要方向是在关系模式之间从数据交换的上下文中寻求一阶查询的语义和复杂性。2008 年&…
最新文章