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

欢迎来到博主的专栏——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;//队首取keywhile (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.mzph.cn/news/794504.shtml

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

相关文章

【保姆级讲解如何安装与配置Node.js】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

Vit模型初始化参数。余弦退火(Cosine Annealing)。模型训练。VisualDL。模型测试。

目录 Vit模型初始化参数。 余弦退火(Cosine Annealing)。 模型训练。 VisualDL。

YuanDaiMa2048博客文章总览

YuanDaiMa2048博客文章总览 不定期更新学习中遇到的问题以及学习笔记… 一、基础概念 最新流行IT技术正则化概念及使用正则表达式基本概念正则表达式与正则化[日常使用] Win R[日常使用] Shell常用命令dos和cmd 二、科研工具 [实验室服务器使用]使用VSCode、PyCharm、MobaX…

【JAVA】postman import certificates in project 导入证书pfx

1. 打开这个按钮 2. File ->Settings 3. 打开“certificates”, Add certificates 添加证书 4. 输入证书地址&#xff0c;然后选择证书文件pfx , 输入证书密码。点击添加就可以了。 特别提醒&#xff1a; 推荐本地自己证书验证软件&#xff0c;“KeyStore” 这个软件可以…

富格林:关注正规手段防卫虚假伎俩

富格林悉知&#xff0c;黄金市场瞬息万变&#xff0c;虽然有交易机会&#xff0c;但也伴随着一定的风险。投资者进入市场应学习应对市场风险&#xff0c;避免虚假猫腻的伎俩。尤其是对于刚进入市场的新手投资者&#xff0c;更需要一些实用的、正规的方法来降低损失的概率&#…

Selenium 饼图自动化测试

目录 前言 从实例获取饼图原始数据 实例名词解释 确定饼图与坐标轴的象限关系 计算饼图坐标 测试代码 前言 在前面已经说过折线图和柱状图的自动化测试,本期来讨论一下饼图的自动化测试(如果没有做特别说明,说的都是以echarts为基础的图表自动化测试)。 基本套路都差…

设计模式|责任链模式(Chain of Responsibility Pattern)

文章目录 结构优点缺点使用责任链的步骤示例有哪些知名框架采用了责任链模式责任链模式和链表有什么关联常见面试题 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;它允许你创建一个对象链。请求将沿着这个链传递&#xff…

OJ练习第190题——坐标移动

坐标移动 HJ17 坐标移动 题目描述 开发一个坐标计算工具&#xff0c; A表示向左移动&#xff0c;D表示向右移动&#xff0c;W表示向上移动&#xff0c;S表示向下移动。从&#xff08;0,0&#xff09;点开始移动&#xff0c;从输入字符串里面读取一些坐标&#xff0c;并将最终…

[报错解决]No bean named ‘userService‘ available

目录 具体报错报错解决 具体报错 Exception in thread “main” org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘userService’ available 报错解决 <!--spring没有注入userService的bean对象--> <!--依赖注入--> <bean…

接口调用成功后端却一直返回404

vuespringboot 我在vue.config.js中配置了向后端的反向代理 然后使用了axios向后端发送post请求 可以看到可以接收到前端传来的值 但是前端控制台却报了 “xhr.js:245POST http://localhost:7777/api/login 404 (Not Found)” 最后询问我那智慧的堂哥... ... 解决办法是把C…

深入了解 Python 中标准排序算法 Timsort

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ Timsort&#xff1a;一个非常快速的、时间复杂度为 O ( n l o g n ) O (n \ log\ n) O(n log n)、稳健&#xff08;即不改变等值元素间的相对顺序&#xff09;的排序算法&#xff0c;在处理真实世界数…

RDD算子(四)、血缘关系、持久化

1. foreach 分布式遍历每一个元素&#xff0c;调用指定函数 val rdd sc.makeRDD(List(1, 2, 3, 4)) rdd.foreach(println) 结果是随机的&#xff0c;因为foreach是在每一个Executor端并发执行&#xff0c;所以顺序是不确定的。如果采集collect之后再调用foreach打印&#xf…

SpringMVC --- 老杜

1、什么是SpringMVC&#xff1f; SpringMVC是一个基于Java实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&#xff0c;把复杂的web应用分成逻辑清晰的及部分&#xff0c;…

Adobe Bridge 2024:连接创意,探索无限可能 mac/win版

Adobe Bridge 2024&#xff0c;作为Adobe家族中的一款强大的创意管理工具&#xff0c;再次革新了数字资产管理和工作流程优化的标准。这款软件不仅继承了Adobe Bridge一贯的直观界面和强大功能&#xff0c;更在多个方面进行了突破性的改进。 Bridge 2024软件获取 全面的资源管…

idea常用代码模板

1、非空判断 变量.null&#xff1a;if(变量 null)变量.nn&#xff1a;if(变量 ! null)变量.notnull&#xff1a;if(变量 ! null)ifn&#xff1a;if(xx null)inn&#xff1a;if(xx ! null) 2、遍历数组和集合 数组或集合变量.fori&#xff1a;for循环数组或集合变量.for&am…

突破编程_C++_网络编程(TCPIP 四层模型(传输层))

1 传输层的功能与作用 在 TCP/IP 四层模型中&#xff0c;传输层位于网络层之上和应用层之下&#xff0c;负责在源主机和目标主机之间提供端到端的可靠数据传输服务。传输层的主要功能与作用体现在以下几个方面&#xff1a; 分段与重组&#xff1a;由于网络层的数据包大小有限制…

内网穿透的应用-如何在Android Termux上部署MySQL数据库并实现无公网IP远程访问

文章目录 前言1.安装MariaDB2.安装cpolar内网穿透工具3. 创建安全隧道映射mysql4. 公网远程连接5. 固定远程连接地址 前言 Android作为移动设备&#xff0c;尽管最初并非设计为服务器&#xff0c;但是随着技术的进步我们可以将Android配置为生产力工具&#xff0c;变成一个随身…

labview如何创建2D多曲线XY图和3D图

1如何使用labview创建2D多曲线图 使用“索引与捆绑簇数组”函数将多个一维数组捆绑成一个簇的数组&#xff0c;然后将结果赋值给XY图&#xff0c;这样一个多曲线XY图就生成了。也可以自己去手动索引&#xff0c;手动捆绑并生成数组&#xff0c;结果是一样的 2.如何创建3D图 在…

pix2pix GAN

import os os.environ[TF_CPP_MIN_LOG_LEVEL] = 2#设置tensorflow的日志级别 from tensorflow.python.platform import build_info import tensorflow as tf import os # 用于处理文件系统路径的面向对象的库。pathlib 提供了 Path 类, #该类表示文件系统路径,并提供了很多方…

Vue2 —— 学习(一)

&#xff08;二&#xff09;简单案例 1.实现过程 容器设置 Vue 实例设置 2.实现结果 3.注意事项 &#xff08;三&#xff09;Vue 插件 ​编辑三、Vue 模板语法 &#xff08;一&#xff09;插值语法 {{ }}&#xff1a; &#xff08;二&#xff09;指令语法 v- 四、…