c语言数据结构(10)——冒泡排序、快速排序

news2024/11/18 18:31:22

欢迎来到博主的专栏——C语言数据结构
博主ID:代码小豪

文章目录

    • 冒泡排序
    • 冒泡排序的代码及原理
    • 快速排序
    • 快速排序的代码和原理
    • 快速排序的其他排序方法
    • 非递归的快速排序

冒泡排序

相信冒泡排序是绝大多数计科学子接触的第一个排序算法。作为最简单、最容易理解的排序算法,冒泡排序虽然效率不高,但是冒泡排序的思路给了其他排序算法一些启发。

冒泡排序的思路如下:
(1)从第一个数据开始,向后继元素比较大小、若满足条件(大于或小于),则交换数据的位置,然后从后一个位置开始继续比较。
(2)当N-1与N进行数据比较后、完成一趟冒牌排序,然后从头开始继续进行冒牌排序
(3)完成N-1趟排序后、冒牌排序结束。

在这里插入图片描述
在这里插入图片描述
可以发现、在第一趟冒泡排序结束后,最大值7排到了最后一位,而升序数组中的7也是排到最后一位。这一趟冒泡排序就确定了升序数组中的一位。

当第二趟冒泡排序开始时,由于最后一位的数据已经确定了,因此不再需要参与排序,将end向前移动一位,以此类推。
在这里插入图片描述

冒泡排序的代码及原理

void Swap(int* e1, int* e2)//交换函数
{
	int tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}
void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n-1; i++)//共计排n-1趟
	{
		int end = n - i - 1;
		for (int begin = 0;//每趟冒泡排序从0开始
			begin < end;//当begin=end时结束
			begin++)
		{
			if (a[begin] > a[begin + 1])//满足条件交换数据
			{
				Swap(&a[begin], &a[begin + 1]);
			}
		}
	}
}

冒牌排序的原理如下:
每一趟冒泡排序都能使至少一个数据处于正确的升降序的位置。即每趟排序都能让当前范围的最大值,位于最后一个位置,好比水中的鱼吐出的泡泡,从水底到水面渐渐变大。
在这里插入图片描述

快速排序

快速排序和冒泡排序都有一个相似之处,或者是一种排序的思路,即一趟排序完成单个或多个数据的定位(将数据排在最终确定的位置上),多次排序后将所有数据都完成定位,最终完成排序操作。

冒泡排序的思路是每次确定最后一位,因此每趟排序都需要将整个数组遍历一遍,导致时间复杂度非常高。快速排序作为最著名昭著的排序算法,以高效率深受程序员喜爱,这里讲讲快排的优化之处。

先来了解一下快速排序的步骤:
(1)选取数组中的任意一个数据作为关键数据(key)
(2)让key左边的数据小于(大于)key,右边的数据大于(小于)key
(3)将整体分割为两部分,一部分是key左边,另一部分是key右边,以这些部分作为一个整体,重复(1)(2)(3)操作,直到所以数据不可分割为止。

这个思路单凭讲述时很难理解其中奥义的。我们先拆分步骤逐渐讲解。
(1)选取key可以选择区间中的任意数据,选取方法通常有三种:取队首、取队尾、随机值取中间数,通常来说结合三数取中(即在队首、队尾、随机之间选择中间数作为key)是最合理的。但这里为了方便讲解,选择取队首作为key

(2)中让key左边的数据小于key,右边的数据大于key有多种方法,总体而言对效率影响不大,我们先来了解快排发明者霍尔的方法。

首先将待排序的区间选出,队首第一个值为key值。区间最左边设置一个left,最右边设置一个right。如图:
在这里插入图片描述

让right往左边遍历,若遍历的数据小于key,则right停留在该数据处。
在这里插入图片描述

当right找到比key小的数后,让left向右遍历找到比key大的数。找到比key大的数后,停留在该数据处。
在这里插入图片描述
当left和right都停留时,交换left和right
在这里插入图片描述
交换结束后,重复上述步骤,让right向左遍历。
在这里插入图片描述
此时left继续向右遍历,left与right重合,此时将key与重合位置的数据交换。
在这里插入图片描述
以key为分割线,将这段区间分为两个区间。
在这里插入图片描述
再对这两个区间进行快排。以此类推。
在这里插入图片描述
当某个区间的left和right重合,数据不可再分割,不再需要排序。
在这里插入图片描述

快速排序的代码和原理

快速排序的原理如下:
和冒泡排序有个类似的点,每一趟冒泡排序都有一个数据被定位,快速排序也是如此,key位置的左边小于key,右边大于key,那么key这个位置就是符合排序要求的位置的。那么快速排序比冒泡排序有效率的点在哪呢?

冒泡排序每趟排序都需要将整个数组遍历一遍。快速排序使用了一种分治的方法,将区间分割成多个小区间进行排序,减少了需要遍历的元素个数。
在这里插入图片描述
此时快速排序就不再需要遍历整个数组了,而是遍历小区间,遍历区间1只用遍历4个数据,定位一个,区间2只需遍历2个数据,定位1,当数据数量变得很多时,快速排序的优势更加明显,效率更快。

代码如下:

void QuickSort(int* a, int begin, int end)
{
	int left = begin;
	int right = end-1;
	if (left >= right)//区间不可再分割,结束递归
	{
		return;
	}
	int key = left;//队首取key
	while (left < right)//遍历条件
	{
		while (left < right && a[key] < a[right])//right向左寻找比key小的值
		{
			right--;
		}
		while (left < right&&a[key]>a[left])//left向右寻找比key大的值
		{
			left++;
		}
		Swap(&a[left], &a[right]);//找到之后交换
	}
	Swap(&a[key], &a[left]);//left与right相等,与key交换。
	key = left;
	QuickSort(a, begin, key);//分割
	QuickSort(a, key + 1, end);//分割
}

快速排序的其他排序方法

这里介绍另外一种排序方式——前后指针法。霍尔使用left和right的找寻方法非常巧妙,但是代码较复杂。这里讲一种较为简洁的找寻方法(对效率影响不大)。

定义一个prev和cur指向队首与队首的后一个元素,key值仍取队首。

步骤如下:
(1)判断cur的值是否大于prev
若大于:cur往前移动一位,prev不变。
若小于:prev往前移动一位,与cur交换数据,cur再往前移动一位。(若prev指针与cur相遇,不交换)。
(2)当cur超出区间后,让key与prev交换数据。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
剩下也是将key分割两个区间继续分治,只是找寻方法改变了。

为什么这种方法能完成key的左边比key小,右边比key大呢?
首先,我们可以发现,cur与prev之间的元素都是比key大的。我们重新回顾一下步骤。

(1)最初开始时,prev与cur之间不存在任何数据(因为他们相邻)。
(2)cur与prev最初是挨着的,当cur遍历到比key大的树据才会有所距离(此时距离之间的数据都是比key大到数)。
(3)为了保持cur与key之间的数都是大于key值,需要将prev后一位的数据(一定大于key),与小于key值的cur进行交换
(4)最后prev的值一定是小于key的(因为此时prev的值是从小于key的cur值里交换而来的)。所以prev一定小于key。

如果prev和cur中间的数据保持比key大,那么当cur超出范围时,key左边一定小于key,右边一定大于key

代码如下:

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)//区间不可分割时,停止递归
	{
		return;
	}
	int prev = begin;
	int cur = begin + 1;
	int key = begin;

	while (cur < end)//cur没超出区间
	{
		if (a[cur] <= a[key])//当cur小于key的值时
		{
			prev++;
			if (prev != cur)
			{
				Swap(&a[prev], &a[cur]);
			}
		}
		cur++;//不管是大于key还是小于key,cur最后都要++
	}
	Swap(&a[prev], &a[key]);
	key = prev;
	QuickSort(a, begin, key);
	QuickSort(a, key+1, end);
}

非递归的快速排序

前面讲了快排的递归形式,但是递归就说明需要大量的调用堆栈,一旦递归深度过高,就会导致栈溢出,因此有人想出了快速排序的非递归方法。

想要替代递归的作用,就得先找到为什么使用递归以及递归是为了实现什么。

快速排序中的递归起的作用是为了记录分割的区间的起始点与结束点

QuickSort(a, begin, key);
QuickSort(a, key+1, end);

如果我们有办法将每趟快速排序的分割区间记录下来。就能取代递归的作用。

我们可以创建一个栈,用来记录快速排序的区间。这样子就不再需要递归了。

记录的步骤如下:
(1)将左区间和右区间压入栈中
(2)将左区间和右区间弹出。将弹出的数据作为一个区间进行一趟快速排序
(3)完成快速排序后,将新分割好的节点压入栈中
(4)若取出的左右区间不构成新区间,不执行这个操作。
循环往复,直到栈变成空栈。

代码如下:

void QuickSort(int* a, int begin, int end)
{
	stack s1;
	StackInit(&s1);//初始化栈
	StackPush(&s1,begin);//压入左区间
	StackPush(&s1,end);//压入右区间

	while (!StackEmpty(&s1))
	{
		int right = StackTopData(&s1);//出栈
		StackPop(&s1);
		int left = StackTopData(&s1);//出栈
		StackPop(&s1);


		int cur = left+1;
		int prev = left;
		int key = left;

		while (cur < right)
		{
			if (a[cur] < a[key])
			{
				prev++;
				if (prev != cur)
				{
					Swap(&a[cur], &a[prev]);
				}
			}
			cur++;
		}
		Swap(&a[prev], &a[key]);
		key = prev;
		
		//[left,key],[key+1,right]
		if (left < key)
		{
			StackPush(&s1, left);//压入分割后的左区间
			StackPush(&s1, key);//压入分割后的右区间
		}
		if (key + 1 < right)
		{
			StackPush(&s1, key + 1);//压入分割后的左区间
			StackPush(&s1, right);//压入分割后的右区间
		}
	}
	StackDestory(&s1);
}

栈的实现函数

void StackInit(stack* ps)//栈的初始化
{
	ps->data = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void StackPush(stack* ps, datatype n)//压栈
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		datatype* tmp = (datatype*)realloc(ps->data, sizeof(stack) * newcapacity);
		assert(tmp);
		ps->data = tmp;
		ps->capacity = newcapacity;
	}

	ps->data[ps->top] = n;
	ps->top++;
}

void StackPop(stack* ps)//出栈
{
	if (StackEmpty(ps))
	{
		perror("Stack is empty\n");
		return;
	}

	ps->top--;
}

bool StackEmpty(stack* ps)//判断是否空栈
{
	return ps->top == 0;
}

datatype StackTopData(stack* ps)//取栈顶
{
	if (StackEmpty(ps))
	{
		perror("Stack is empty\n");
		exit(1);
	}
	return ps->data[ps->top - 1];
}

void StackDestory(stack* ps)//销毁栈
{
	assert(ps);
	free(ps->data);
	ps->data = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创新性的智慧公厕技术研发与应用

智慧公厕&#xff0c;作为城市基础设施的重要组成部分&#xff0c;扮演着提供舒适便捷卫生服务的角色。智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;通过技术创新与应用升级&#xff0c;打造标杆性的智慧公厕整体解决方案。通过创新性的技术应用&#xff0c;智慧公厕…

字节新作:图像生成质量超越DiT

&#x1f31f;每日更新最新高质量论文&#xff0c;关注我&#xff0c;时刻关注最新大模型进展。&#x1f31f; &#x1f4cc; 元数据概览&#xff1a; 标题&#xff1a;Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction作者&#xff1a…

Jupyter IPython帮助文档及其魔法命令

1.IPython 的帮助文档 使用 help() 使用 ? 使用 &#xff1f;&#xff1f; tab 自动补全 shift tab 查看参数和函数说明 2.运行外部 Python 文件 使用下面命令运行外部 Python 文件&#xff08;默认是当前目录&#xff0c;也可以使用绝对路径&#xff09; %run *.py …

数据湖概述:大数据演进阶段-数据湖

文章目录 一. 大数据发展过程1. 离线大数据平台2. Lambda架构&#xff1a;速度层批层3. Kappa架构&#xff1a;流批一体4. 大数据架构痛点总结 二. 数据湖助力于解决数据仓库痛点问题1. 数据湖特点2. 开源数据湖的架构 三. 数据湖和数据仓库理念的对比1. 数据湖和数据仓库对比2…

LeetCode 热题 100 | 贪心算法

目录 1 121. 买卖股票的最佳时机 2 55. 跳跃游戏 3 45. 跳跃游戏 II 4 763. 划分字母区间 菜鸟做题&#xff0c;语言是 C 1 121. 买卖股票的最佳时机 解题思路&#xff1a; 维护一个变量 max_pricemax_price 用于存储排在 i 天之后的股票最高价格第 i 天的最高利润 …

Day5-Hive的结构和优化、数据文件存储格式

Hive 窗口函数 案例 需求&#xff1a;连续三天登陆的用户数据 步骤&#xff1a; -- 建表 create table logins (username string,log_date string ) row format delimited fields terminated by ; -- 加载数据 load data local inpath /opt/hive_data/login into table log…

armlinux-外部中断

s3c2440的中断框图 如果我们单纯配置一个按键的外部中断&#xff0c;就不存在子中断与优先级的问题。 由于是按键的外部中断&#xff0c;通过引脚的高低电平来触发。所以我们要先配置引脚的功能。 我们使用按键1&#xff0c;终端源为EINT8&#xff0c;对应引脚GPG0 通过用户手…

Stable diffusion 加载扩展列表报错解决方法

项目场景&#xff1a; 在使用Stable diffusion webui时&#xff0c;使用扩展列表出现错误 问题描述 点击loadfrom后&#xff0c;出现加载扩展列表报错 原因分析&#xff1a; 下载的扩展的时候&#xff0c;都是github 的url&#xff0c;需要科学上网&#xff0c;如果不能科学…

深澜计费管理系统 任意文件读取漏洞复现

0x01 产品简介 深澜计费管理系统是是一套完善的领先的具有复杂生物型特征的弹性认证计费系统。系统主要由 AAA 认证计费平台、系统运营维护管理平台、用户及策略管理平台、用户自助服务平台、智能客户端模块、消息推送模块、数据统计模块组成。目前在全球为超过 2500 家客户提…

MicroPython 树莓派 RP2 入门教程

系列文章目录 前言 Raspberry Pi Pico 开发板&#xff08;图片来源&#xff1a;Raspberry Pi 基金会&#xff09;。 以下是 Raspberry Pi RP2xxx 板的快速参考资料。如果您是第一次使用该开发板&#xff0c;了解微控制器的概况可能会对您有所帮助&#xff1a; 一、关于 RP2xxx…

【项目实战】——商品管理的制作完整代码

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

数据结构:详解【树和二叉树】

1. 树的概念及结构&#xff08;了解&#xff09; 1.1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝…

Vue项目登录页实现获取短信验证码的功能

之前我们写过不需要调后端接口就获取验证码的方法,具体看《无需后端接口,用原生js轻松实现验证码》这个文章。现在我们管理后台有个需求,就是登录页面需要获取验证码,用户可以输入验证码后进行登录。效果如下,当我点击获取验证码后能获取短信验证码: 这里在用户点击获取…

Cortex-M7 异常处理与返回

1 前言 当CM3开始响应一个中断时&#xff0c;会在它小小的体内奔涌起三股暗流&#xff1a;  入栈&#xff1a; 把8个寄存器的值压入栈;  取向量&#xff1a;从向量表中找出对应的服务程序入口地址;  选择堆栈指针MSP/PSP&#xff0c;更新堆栈指针…

LeetCode 19.删除链表的倒数第N个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[] 示例 3&#x…

Leetcode 39.组合总和

题目 思路 1.确定递归函数的返回值及参数&#xff1a; 返回值是void,参数这里还是先设定两个全局变量。一个是path存放符合条件单一结果。如&#xff1a;&#xff08;1&#xff0c;2&#xff09;。一个是result&#xff0c;是所有path的集合[(1,2),(1,3)…]。 此外再设定一个…

前端学习之DOM编程-docmument对象、操作DOM对像内容、操作DOM对象属性方式、操作DOM对象的样式

docmument对象 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>document对象</title> </head> <body><div id"container" nameparent><ul name"parent&qu…

计算机系统结构速成,期末和自考必备

【拯救者】计算机系统结构速成(期末自考)均可用 1️⃣先讲每章对应的基础 2️⃣接着会讲对应的题目巩固 &#x1f357;提供文档下载 这里讲的是【 &#x1f337;速成&#x1f337; 速成&#x1f337; 速成】版本&#xff0c;按课本章节来&#xff0c; 抽取重点&#xff0c;翻…

数据分析——数据规范化

数据规范化是数据分析中的一个重要步骤&#xff0c;其目的在于确保数据的一致性和可比性&#xff0c;提高数据质量和分析结果的准确性。以下是一些数据规范化的常见方法和技术&#xff1a; 数据清洗&#xff1a;此步骤主要清除数据中的重复项、空格、格式错误等&#xff0c;确…

Transformer Based Multi-view Network for Mammographic Image Classification

“C-Tk” means “Classification Token” 辅助信息 作者未提供代码