[数据结构基础]排序算法第二弹 -- 选择排序、堆排序和冒泡排序

目录

一. 选择排序

1.1 选择排序的实现思路

1.2 选择排序函数代码 

1.3 选择排序的时间复杂度分析

二. 堆排序

2.1 堆排序的实现思路

2.2 堆排序函数代码

2.3 堆排序的时间复杂度分析

三. 冒泡排序

3.1 冒泡排序的基本思想

3.2 冒泡排序函数代码

3.3 冒泡排序的时间复杂度分析


前置说明:本文中所有排序算法均以排升序为例进行讲解。

一. 选择排序

1.1 选择排序的实现思路

选择排序,就是在一组数据中通过选出最大值(最小值的方法进行排序),假设要对具有n个数据的数组arr中的数据进行排序,具体实现步骤为:

  1. 取整个数组中的数据为一数据集,设begin = 0为头部数据下标,end = n - 1为尾部数据的下标,在数据集中找出最大的数据和最小的数据,排升序,最大的数据与尾部数据交换,最小的数据与头部数据交换。
  2. 经过上步,头部数据为数组中的最小值,尾部数组为数组中的最大值。取此时数组中的第2个数据到第n-1个数据为一数据集,设begin = 1为头部数据下标,end = n - 2为尾部数据的下标,最大的数据与尾部数据交换,最小的数据与头部数据交换。
  3. 重复进行步骤2,直到begin >= end时终止。

如,对arr[6] = {3,4,0,5,1,6}排升序的过程为:

  1. begin = 0,end = 5,数据集为:{3,4,0,5,1,6},最大为6,最小为0,arr[6]={0,4,3,5,1,6}。
  2. begin = 1,end = 4,数据集:{4,3,1,5},最大为4,最小为1,arr[6] = {0,1,3,4,5,6}。
  3. begin = 2,end = 3,数据集:{3,4},最大为4,最小为3,arr[6] = {0,1,3,4,5,6}。
  4. begin = 3,end = 4,排序完成。
图1.1 选择排序过程图解

1.2 选择排序函数代码 

//数据交换函数
void swap(int* px, int* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}

//选择排序函数
//参数a为指向存储待排序数组首元素的指针,n为待排序元素的个数
void SelectSort(int* a, int n)
{
	//选择排序
	//每次筛选出未排序数据中最大和最小的那个,存在数组的前部和后部
	int begin = 0;  //前部数据下标
	int end = n - 1;  //后部数据下标

	while (begin < end)
	{
		int mini = begin;   //初始化最小的数据的下标
		int maxi = begin;   //初始化最大的数据的下标

		int i = 0;  //循环参数
		//for循环遍历每一个数据,找出最大的数据和最小的数据的下标
		for (i = begin + 1; i <= end; ++i)
		{
			if (a[i] > a[maxi])
			{
				maxi = i;
			}

			if (a[i] < a[mini])
			{
				mini = i;
			}
		}

		//将最小的数据放在队首,队首数据放在原最小数据位置处
		//将最大的数据放在队尾,队尾数据放在原最大数据位置处
		swap(&a[begin], &a[mini]);
		if (maxi == begin)
		{
			//如果首元素是最大的元素,那么swap(&a[begin], &a[mini])使队首数据不再是最大的数据
			//此时最大的数据被换到下标为mini的位置,更新maxi为mini
			maxi = mini;
		}
		swap(&a[end], &a[maxi]);

		--end;
		++begin;
	}
}

1.3 选择排序的时间复杂度分析

假设要对n个数据进行选择排序,总共要进行n/2次最大值最小值筛选操作,第一次筛选要遍历n个数据,第二次筛选要遍历n-2个数据,...,最后一次筛选要遍历2个数据或3个数据,设遍历一个数据就表示进行一次操作,总共要进行的操作次数为(假设最后一次遍历2个数据):

F(N) = N + (N-2)+(N-4) +...+4+2=\frac{1}{2}N^2+N

根据大O渐进法规则,选择排序的时间复杂度为O(N^2)

注:当待排序数据为偶数个时,最后一次筛选要遍历2个数据,当待排序数据为奇数个时,最后一次筛选要遍历3个数据。

二. 堆排序

2.1 堆排序的实现思路

首先,要明确对于排升序和排降序,应该建大堆还是小堆,记住结论:

  • 排升序,建大堆
  • 排降序,建小堆

堆排序的操作流程如下(排升序):

  • 建大堆,这里不再新开辟空间来建堆,而是将给定数据的数据顺序调整为满足大堆结构的排列。采用向下调整的方法来进行数据调整,从最后一个非叶子节点(度不为0的节点)开始向下调整,调整到根节点结束,此时数据的排列顺序已满足大堆的结构要求。
  • 交换首尾节点的数据值,此时末尾节点为堆中的最大数据。
  • 将末尾的节点排除出堆,从根节点开始,对堆进行向下调整操作。
  • 重复步骤2和步骤3,直到堆中仅剩一个数据为止。

2.2 堆排序函数代码

//数据交换函数
void swap(DataType* px, DataType* py)
{
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
 
//向下调整函数
//a为存储待排序数据的数组,n为待排序数据的个数
void Adjustdown(DataType* a, int n, int parent)
{
	assert(a);
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
			++child;
 
		if (a[parent] < a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
 
//堆排序函数(升序)
void HeapSort(DataType* a, int n)
{
	assert(a);
 
	//先采用向下调整的方式将a中的数据建立为大堆
	//从后往前调整,叶子节点不用单独调整
	//因此,从第一个度不为0的节点开始往前调整到第一个节点即可
	int end = (n - 1 - 1) / 2;
	while(end >= 0)
	{
		Adjustdown(a, n, end);
		--end;
	}
 
	//将a排为大堆后
	//将a的数据首尾交换,排除最后一个节点,将堆进行向下调整
	//重复进行上述操作n-1次,堆(数组)中的数据变为升序
	end = n - 1;
	while (end)
	{
		swap(&a[0], &a[end]);
		Adjustdown(a, end, 0);
		--end;
	}
}

2.3 堆排序的时间复杂度分析

对N个数据进行堆排序,要先后进行建堆操作和向下调整操作。

建堆操作的时间复杂度

在堆排序中,采用向下调整的方法进行建堆。从倒数第二层开始将每个节点向下调整,假设层数为h,倒数第二层最多进行下调1次下调操作,倒数第三层最多进行2次下调操作,...,第二层最多进行h-2次下调操作,第一层最多进行h-1次下调操作,每层节点数和层数的关系为:n = 2^{h-1}n为第h层节点数。综上,通过向下调整建堆要进行调整的次数最多为:

F(N)=2^{h-2}\times 1+2^{h-3}\times2+2^{h-4}\times3...+2^1\times (h-2)+2^0\times(h-1)        (1)

2\times F(N)=2^{h-1}\times 1+2^{h-2}\times 2+2^{h-3}\times3+....+2^2\times(h-2)+2^1\times(h-1)     (2)

(2)-(1)得:

F(N)=2^{h-1} +2^{h-2}+2^1-h+1=\frac{2-2^{h}}{1-2}-h+1=2^{h}-h-1

根据二叉树结构的性质:N=2^h-1h=log(N+1)

因此:F(N)=N-log(N+1)

根据大O渐进法,建堆操作的时间复杂度为:O(N)

向下调整的时间复杂度

堆排序的基本思想是在建立堆之后交换首尾节点数据,然后向下调整根节点数据来实现的,假设堆中共有N个数据,总共要对N-1个数据向下调整,设二叉树的层数为h,每次向下调整最多调整h-1次,总共进行的向下调整操作的最多次数为:

F(N)=(N-1)\times h = (N-1)log(N+1)

根据大O渐进法,堆排序中调整过程的时间复杂度为:O(NlogN)

整个堆排序的时间复杂度

整个堆排序要进行建堆操作和向下调整操作,时间复杂度记O(N+NlogN),根据大O渐进法规则,堆排序时间复杂度可表示为:O(NlogN)

三. 冒泡排序

3.1 冒泡排序的基本思想

冒泡排序的基本思想是交换,通过相邻两数的交换,达到排升序(降序)的目的,给定含有n个数据的数组arr,对arr中的数据排升序的操作步骤为:

  1. 从arr中的第二个数据开始到最后一个数据,每个数据与它前面的那个数据进行比较,如果前面的数据大于后面的数据,则交换两个数据,经过这一趟排序,数组中最后面的数为最大的数据。
  2. 从数组中第二个数据开始到倒数第二个数据,每个数据与它前面的那个数据进行比较,如果前面的数据大于后面的数据,则交换两个数据,此时数组中最后一个数据和倒数第二个数据分别为最大的数据和次大的数据。
  3. 重复步骤1和2,直到完成排序。
图3.1 冒泡排序的第一趟排序实现流程

图3.1展示了对数组arr[6] = {6,5,4,3,2,1}的第一趟排序的实现过程,经过一趟排序,arr中最大的值到了最后面的位置,第二趟排序对arr中的前5个数据进行相同的操作即可,第二趟排序结束后arr应当变为arr[6]={4,3,2,1,5,6}。

  • 冒泡排序的优化:

设想,如果单趟排序未发生数据交换,那就表示数据已经有序,数据有序就可以终止循环。因此,可以定义一个变量exchange来表示单趟排序是否发生数据交换,可以取exchange=1表示发生了数据交换,exchange=0表示没发生数据交换,每结束一次单趟循环都对exchange的值进行检查,如果exchange==0成立,则排序终止。

3.2 冒泡排序函数代码

//冒泡排序函数
void bubbleSort(int* a, int n)
{
	int i = 0;  //循环参数,表示冒泡排序趟数
	int j = 0;  //循环参数,表示下标

	for (i = 0; i < n - 1; ++i) //单趟排序
	{
		int exchange = 0;   //数据交换标识符,如果单趟排序发生数据交换为1,否则为0
		for (j = 0; j < n - 1 - i; ++j)
		{
			//排升序,如果前面的数大于后面则交换
			if (a[j] > a[j + 1])
			{
				exchange = 1;
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
			}
		}

		//如果单趟排序未发生数据交换flag=0,则表示数据已经有序,终止排序即可
		if (0 == exchange)
		{
			break;
		}
	}
}

3.3 冒泡排序的时间复杂度分析

设待排序的数据个数为N,总共要进行N-1趟冒泡排序,假设冒泡排序过程中不发生break终止循环,第一趟冒泡排序最多进行N-1次数据交换,第二趟冒泡排序最多进行N-2次数据交换,...,最后一趟冒泡排序最多进行1次数据交换,因此,总共要进行数据交换的次数为:

F(N)=(N-1)+(N-2)+...+2+1=\frac{(N-1)(N-1+1)}{2}=\frac{1}{2}N^2-\frac{1}{2}N

根据大O渐进法规则,冒泡排序的时间复杂度为O(N^2)

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
《编程实训》 实验报告书 专 业:计算机科学与技术 班 级:151班 学 号: 姓 名: 指导教师: 日 期:2016年6月30日 目录 一、需求分析…………………………………………………………………………………3 1.任务要求……………………………………………………………………………………3 2.软件功能分析………………………………………………………………………………3 3.数据准备……………………………………………………………………………………3 二、概要设计…………………………………………………………………………………3 1.功能模块图………………………………………………………………………………4 2.模块间调用关系…………………………………………………………………………4 3.主程序模块………………………………………………………………………………5 4.抽象数据类型描述…………………………………………………………………………5 三、详细设计…………………………………………………………………………………6 1.存储结构定义………………………………………………………………………………6 2.各功能模块的详细设计……………………………………………………………………7 四、实现和调试………………………………………………………………………………7 1.主要的算法………………………………………………………………………………7 2.主要问题及解决…………………………………………………………………………8 3.测试执行及结果……………………………………………………………………………8 五、改进………………………………………………………………………………………9 六、附录……………………………………………………………………………………9 1.查找源程序………………………………………………………………………………9 2.排序源程序………………………………………………………………………………9 目录 1 需求分析 1.1 任务要求 对于从键盘随机输入的一个序列的数据,存入计算机内,给出各种查找算法的实 现;以及各种排序算法的实现。 1.2 软件功能分析 任意输入n个正整数,该程序可以实现各类查找及排序的功能并将结果输出。 1.3 数据准备 任意输入了5个正整数如下: 12 23 45 56 78 2 概要设计(如果2,3合并可以省略2.4) 2.1 功能模块图(注:含功能说明) 2.2 模块间调用关系 2.3 主程序模块 2.4 抽象数据类型描述 存储结构:数据结构在计算机中的表示(也称映像)叫做物理结构。又称为存储结构。 数据类型(data type)是一个"值"的集合和定义在此集合上的一组操作的总称。 3 详细设计 3.1 存储结构定义 查找: typedef int ElemType ; //顺序存储结构 typedef struct { ElemType *elem; //数据元素存储空间基址,建表时按实际长度分配,号单元留空 int length; //表的长度 }SSTable; 排序: typedef struct { //定义记录类型 int key; //关键字项 }RecType; typedef RecType SeqList[Max+1]; //SeqList为顺序表,表中第0个元素作为哨兵 3.2 各功能模块的详细设计 查找: void Create(SSTable *table, int length); // 构建顺序表 void FillTable(SSTable *table) // 无序表的输入 int Search_Seq(SSTable *table, ElemType key); //哨兵查找算法 void Sort(SSTable *table ) // 排序算法 int Search_Bin(SSTable *table, ElemType key) // 二分法查找(非递归) 排序: void InsertSort(SeqList R) //对顺序表R中的记录R[1 n]按递增序进行插入排序 void BubbleSort(SeqList R) //自下向上扫描对R做冒泡排序 int Partition(SeqList R,int i,int j)//对R[i j]做一次划分,并返回基准记录的位置 void QuickSort(SeqList R,int low,int high) //R[low..high]快速排序 void SelectSort(SeqList R) //直接选择排序 void Heapify(SeqList R,int low,int high) //大根堆调整函数 v
线性表 某软件公司大约有30名员工,每名员工有姓名、工号、职务等属性,每年都有员工离职和入职。 把所有员工按照顺序存储结构建立一个线性表,建立离职和入职函数,当有员工离职或入职时,修改线性表,并且打印最新的员工名单。 约瑟夫(Josephus)环问题:编号为1,2,3,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数的上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一人开始重新从1报数,如此下去,直到所有人全部出列为止。 建立n个人的单循环链表存储结构,运行结束后,输出依次出队的人的序号。 栈和队列 某商场有一个100个车位的停车场,当车位未满时,等待的车辆可以进入并计时;当车位已满时,必须有车辆离开,等待的车辆才能进入;当车辆离开时计算停留的的时间,并且按照每小时1元收费。 汽车的输入信息格式可以是(进入/离开,车牌号,进入/离开时间),要求可以随时显示停车场内的车辆信息以及收费历史记录。 某银行营业厅共有6个营业窗口,设有排队系统广播叫号,该银行的业务分为公积金、银行卡、理财卡等三种。公积金业务指定1号窗口,银行卡业务指定2、3、4号窗口,理财卡业务指定5、6号窗口。但如果5、6号窗口全忙,而2、3、4号窗口有空闲时,理财卡业务也可以在空闲的2、3、4号窗口之一办理。 客户领号、业务完成可以作为输入信息,要求可以随时显示6个营业窗口的状态。 5、4阶斐波那契序列如下:f0=f1=f2=0, f3=1,…,fi=fi-1+fi-2+fi-3+fi-4, 利用容量为k=4的循环队列,构造序列的前n+1项(f0, f1 , f2 ,… fn ),要求满足fn ≤200而fn+1 >200。 6、八皇后问题:设8皇后问题的解为 (x1, x2, x3, …,x8), 约束条件为:在8x8的棋盘上,其中任意两个xi 和xj不能位于棋盘的同行、同列及同对角线。要求用一位数组进行存储,输出所有可能的排列。 7、迷宫求解:用二维矩阵表示迷宫,自动生成或者直接输入迷宫的格局,确定迷宫是否能走通,如果能走通,输出行走路线。 8、英国人格思里于1852年提出四色问题(four colour problem,亦称四色猜想),即在为一平面或一球面的地图着色时,假定每一个国家在地图上是一个连通域,并且有相邻边界线的两个国家必须用不同的颜色,问是否只要四种颜色就可完成着色。现在给定一张地图,要求对这张地图上的国家用不超过四种的颜色进行染色。 要求建立地图的邻接矩阵存储结构,输入国家的个数和相邻情况,输出每个国家的颜色代码。 9、以下问题要求统一在一个大程序里解决。 从原四则表达式求得后缀式,后缀表达式求值,从原四则表达式求得中缀表达式,从原四则表达式求得前缀表达式,前缀表达式求值。 数组与广义表 鞍点问题: 若矩阵A中的某一元素A[i,j]是第i行中的最小值,而又是第j列中的最大值,则称A[i,j]是矩阵A中的一个鞍点。写出一个可以确定鞍点位置的程序。 稀疏矩阵转置: 输入稀疏矩阵中每个元素的行号、列号、值,建立稀疏矩阵的三元组存储结构,并将此矩阵转置,显示转置前后的三元组结构。 用头尾链表存储表示法建立广义表,输出广义表,求广义表的表头、广义表的表尾和广义表的深度。 树和二叉树 以下问题要求统一在一个大程序里解决。 按先序遍历的扩展序列建立二叉树的存储结构 二叉树先序、中序、后序遍历的递归算法 二叉树中序遍历的非递归算法 二叉树层次遍历的非递归算法 求二叉树的深度(后序遍历) 建立树的存储结构 求树的深度 图 输入任意的一个网,用普里姆(Prim)算法构造最小生成树。 要求建立图的存储结构(邻接表或邻接矩阵),输入任意的一个图,显示图的深度优先搜索遍历路径。 要求建立图的存储结构(邻接表或邻接矩阵),输入任意的一个图,显示图的广度优先搜索遍历路径。 查找 设计一个读入一串整数构成一颗二叉排序树的程序,从二叉排序树中删除一个结点,使该二叉树仍保持二叉排序树的特性。 24、设定哈希函数 H(key) = key MOD 11 ( 表长=11 ),输入一组关键字序列,根据线性探测再散列解决冲突的方法建立哈希表的存储结构,显示哈希表,任意输入关键字,判断是否在哈希表中。 排序 以下问题要求统一在一个大程序里解决。 25、折半插入排序 26、冒泡排序 27、快速排序 28、简单选择排序 29、归并排序 30、堆排序

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值