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;欢迎在评论区提出…

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

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

接口调用成功后端却一直返回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软件获取 全面的资源管…

内网穿透的应用-如何在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图 在…

如何更新Code::blocks的MinGW

前言 LVGL V9版本更新了很多新特性&#xff0c;其中windows平台部分也进行了优化&#xff0c;如果你是用的是Code::blocks体验LVGL那么在编译时会不通过&#xff1b;因为如果你使用的是 Code::blocks 20.03并且使用内置的MinGW&#xff0c;那么就会因为MinGW版本过低遇到下面所…

babyAGI(8)-babyCoder5主程序逻辑

前期代码都以阅读完毕&#xff0c;接下来我们来看主程序逻辑&#xff0c;建议大家好好看看流程图&#xff0c;有个流程的影响 1. 创建任务 下面一段代码主要用来创建任务以及打印相关信息&#xff0c;调用了四个agents code_tasks_initializer_agent 初始化任务code_tasks_…

信息系统项目管理师——第18章项目绩效域管理(二)

项目工作绩效域 预期目标 高效且有数的项目绩效 2.适合项目和环境的项目过程 3.干系人适当的沟通和参与 4.对实物资源进行了有效管理 5.对采购进行了有效管理 6.有效处理了变更 7.通过持续学习和过程改进提高了团队能力 绩效要点 1.项目过程 2.项目制约因素 3.专注于工作过…

React - 连连看小游戏

简介 小时候经常玩连连看小游戏。在游戏中&#xff0c;当找到2个相同的元素就可以消除元素。 本文会借助react实现连连看小游戏。 实现效果 实现难点 1.item 生成 1. 每一个图片都是一个item&#xff0c;items数组的大小为size*size。 item对象包括grid布局的位置&#xff0c;…

【爬虫开发】爬虫从0到1全知识md笔记第4篇:Selenium课程概要,selenium的介绍【附代码文档】

爬虫开发从0到1全知识教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;爬虫课程概要&#xff0c;爬虫基础爬虫概述,,http协议复习。requests模块&#xff0c;requests模块1. requests模块介绍,2. response响应对象,3. requests模块发送请求,4. request…

入门用Hive构建数据仓库

在当今数据爆炸的时代&#xff0c;构建高效的数据仓库是企业实现数据驱动决策的关键。Apache Hive 是一个基于 Hadoop 的数据仓库工具&#xff0c;可以轻松地进行数据存储、查询和分析。本文将介绍什么是 Hive、为什么选择 Hive 构建数据仓库、如何搭建 Hive 环境以及如何在 Hi…

类与对象(一)

目录 一、类的引入和定义 二、类的访问限定符及封装 1&#xff09;访问限定符 2&#xff09;封装 三、类的作用域和实例化 1&#xff09;类的作用域 2&#xff09;实例化 四、类的大小 1&#xff09;类的大小计算方式 2&#xff09;特殊的类的大小 五、this指针 1&…

C++设计模式:观察者模式(三)

1、定义与动机 观察者模式定义&#xff1a;定义对象间的一种1对多&#xff08;变化&#xff09;的依赖关系&#xff0c;以便当一个对象&#xff08;Subject&#xff09;的状态发生比改变时&#xff0c;所有依赖于它的对象都得到通知并且自动更新 再软件构建过程中&#xff0c…

小程序如何设置余额充值和消费功能

小程序中设置余额充值和消费功能非常重要的&#xff0c;通过让客户在小程序中进行余额充值&#xff0c;不仅可以提高用户粘性&#xff0c;还可以促进消费&#xff0c;增加用户忠诚度。以下是如何在小程序中设置余额充值和消费功能的步骤&#xff1a; 1. **设计充值入口**&…

关于Idea无法正常启动

编辑这个文件 最后一行 加上 pause 双击文件 会显示报错信息

npm install node-sass报错

前言 在使用 node-sass 时&#xff0c;你可能会遇到安装 node-sass 时出现各种错误的情况。在本文中&#xff0c;我们将探讨一些常见的 node-sass 安装错误&#xff0c;以及如何解决它们。 无论你是初学者还是有经验的开发者&#xff0c;本文都将为你提供有用的信息和技巧&…