C语言分析基础排序算法——交换排序

目录

交换排序

冒泡排序

快速排序

Hoare版本快速排序

挖坑法快速排序

前后指针法快速排序

快速排序优化

快速排序非递归版


交换排序

冒泡排序

见C语言基础知识指针部分博客C语言指针-CSDN博客

快速排序

Hoare版本快速排序

Hoare版本快速排序的过程类似于二叉树前序遍历的过程,基本思想是:在需要排序的数据中确定一个值放入key中,接着使用左指针和右指针从数据的起始位置以及数据的末端位置向中间遍历,左指针找数值比key大的数据,右指针找数值比key小的数据,交换这两个指针的数据之后接着向中间移动,直到两个指针最后相遇时交换key所在的数值以及相遇位置的数值,完成第一趟排序,接着进行左边部分的排序,方法如同第一趟排序,左边排序完毕后进行右边部分的排序,方法如同第二趟排序,直到最后全部排序完成后为止。具体思路如下:

📌

注意key是数值的下标

//以下面的数组为例
int data[] = { 23,48,67,45,21,90,33,11 };

下面是完整过程递归图

参考代码

void swap(int* num1, int* num2)
{int tmp = *num1;*num1 = *num2;*num2 = tmp;
}//一趟排序,返回key指向的位置
int PartSort(int* data, int left, int right)
{int key = left;//使key指向区间的第一个元素位置while (left < right){//先让右侧指针先走,右指针找小while (left < right && data[right] >= data[key]){right--;}//再让左侧指针走,左侧指针找大while (left < right && data[left] <= data[key]){left++;}//交换右侧指针和左侧指针的数据swap(&data[right], &data[left]);}//执行完循环后,交换key所在位置的数据和right与left指针相遇的位置的数值swap(&data[key], &data[left]);//返回交换后的key指向的元素的位置return left;
}//Hoare版本
void QuickSort_Hoare(int* data, int left, int right)
{//当left和right指针指向同一个位置或后一个位置结束排序if (left >= right){return;}//获取到当前key的位置int key = PartSort(data, left, right);QuickSort_Hoare(data, left, key - 1);QuickSort_Hoare(data, key + 1, right);
}

Hoare版本问题分析

  • 在上面的过程分析中,使用第一个元素的位置作为key的位置,也可以使用最后一个元素作为key的位置,但是需要注意的是,以key指向第一个元素的位置为例,left指针一定需要指向第一个元素,而不是从第二个元素开始,例如下面的情况:当key位置的数据比其他数据都小时,right找小将一直向key所在位置移动

  • 在判断left指针或者right指针是否需要移动时,需要包括等于的情况,否则会进入死循环,例如下面的情况:当leftright指针同时指向一个等于key所在位置的元素

  • 对于递归结束的条件来说,需要出现left指针的值大于或者等于right指针的值,而不是仅仅一个大于或者等于,因为返回相遇的位置,即返回left指针或者right指针的位置而不是实际返回key所在位置,在交换过程中,只是交换key对应位置的数值和相遇位置的数值,并没有改变key指向的位置
  • 对于left指针和right指针相遇的位置的数值一定比key所在位置的数值小的问题,下面是基本分析:

分析主问题之前,先分析rightleft指针先走的原因:在初始位置时,left指针和right指针各指向第一个元素和最后一个元素但是left指针与key指针指向的位置相同,此时让right指针先走,而不是left指针先走,反之同理,具体原因如下:

接下来分析当right指针比left指针先走时,两个指针相遇时一定相遇到一个比key小的数值的问题

两个指针相遇的方式有两种情况

  • 第一种情况:left指针向right指针移动与其相遇
  • 第二种情况:right指针向left指针移动与其相遇

对于第一种情况,分析如下:

对于第二种情况,分析如下:

挖坑法快速排序

挖坑法快速排序相较于Hoare版本的快速排序没有效率上的优化,但是在理解方式上相对简单,其基本思路为:在数据中随机取出一个数值放入key变量中,此时该数值的位置即为一个坑位,接下来left指针从第一个元素开始key值大的数值,right指针从最后一个元素找比key值小的数值,此时不用考虑left指针和right指针谁先走,考虑right指针先走,当right指针找到小时,将该值放置到hole所在位置,更新holeright指针的位置,接下来left指针找大,当left指针找到较key大的数值时,将该数值存入hole中,更新holeleft所在位置,如此往复,直到第一趟排序结束。接着进行左边部分的排序,方法如同第一趟排序,左边排序完毕后进行右边部分的排序,方法如同第二趟排序,直到最后全部排序完成后为止。具体思路如下:

📌

注意key是数值

//以下面的数组为例
int data[] = { 23,48,67,45,21,90,33,11 };

int PartSort_DigHole(int* data, int left, int right)
{int hole = left;int key = data[left];while (left < right){while (left < right && data[right] >= key){right--;}data[hole] = data[right];hole = right;while (left < right && data[left] <= key){left++;}data[hole] = data[left];hole = left;}data[hole] = key;return hole;
}//挖坑法
void QuickSort_DigHole(int* data, int left, int right)
{if (left >= right){return;}int hole = PartSort_DigHole(data, left, right);QuickSort_DigHole(data, left, hole - 1);QuickSort_DigHole(data, hole + 1, right);
}

前后指针法快速排序

前后指针法相对Hoare版本和挖坑法也没有效率上的优化,但是理解相对容易,基本思路如下:在前后指针法中,有一个key指针,该指针指向一个随机的数值的下标位置,接下来是一个prev指针,指向数据的第一个元素的下标位置,以及一个cur指针指向第二个元素的下标位置,cur指针和prev指针向前遍历,当遇到比key小的数值时,prev指针先移动柜,再进行curprev进行对应位置的数值交换,接着cur指着移动,否则只让cur指针移动,当cur走到数据的结尾时结束循环,交换prevkey指针的数据,完成第一趟排序。接着进行左边部分的排序,方法如同第一趟排序,左边排序完毕后进行右边部分的排序,方法如同第二趟排序,直到最后全部排序完成后为止。具体思路如下:

📌

注意key是数值的下标,并且用leftright控制递归区间

int PartSort_Prev_postPointer(int *data, int left, int right)
{int key = left;int cur = left + 1;int prev = left;while (cur <= right){//++prev != cur可以防止cur和自己本身交换导致多交换一次if (data[cur] < data[key] && ++prev != cur){prev++;swap(&data[cur], &data[prev]);}cur++;}swap(&data[prev], &data[key]);return prev;
}//前后指针法
void QuickSort_Prev_postPointer(int* data,int left, int right)
{if (left >= right){return;}int key = PartSort_Prev_postPointer(data, left, right);QuickSort_Prev_postPointer(data, left, key - 1);QuickSort_Prev_postPointer(data, key + 1, right);
}

快速排序优化

在快速排序优化部分,采用三数取中的思路对快速排序有大量重复数据或者有序情况下进行优化,所谓三数取中,即第一个元素的位置和最后一个元素的位置加和取一半的数值在数据中的位置,但是此时需要注意的是key当前位置为mid所在位置,为了不改变原来的快速排序代码,获得中间值下标时,交换key位置的值和mid位置的值即可

//三数取中
int GetMidIndex(int* data, int left, int right)
{int mid = (left + right) / 2;//获取左、中、有三个数中的中间数if (data[left] > data[mid]){if (data[mid] > data[right]){//left>mid>rightreturn mid;}else if (data[left] > data[right]){//left>right>midreturn right;}else{//right>left>midreturn left;}}else{if (data[mid] < data[right]){//right>mid>leftreturn mid;}else if (data[right] > data[left]){//mid>right>leftreturn right;}else{//mid>left>rightreturn left;}}
}

以前后指针版本修改为例

#define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <assert.h>void swap(int* num1, int* num2)
{int tmp = *num1;*num1 = *num2;*num2 = tmp;
}//三数取中
int GetMidIndex(int* data, int left, int right)
{int mid = (left + right) / 2;//获取左、中、有三个数中的中间数if (data[left] > data[mid]){if (data[mid] > data[right]){//left>mid>rightreturn mid;}else if (data[left] > data[right]){//left>right>midreturn right;}else{//right>left>midreturn left;}}else{if (data[mid] < data[right]){//right>mid>leftreturn mid;}else if (data[right] > data[left]){//mid>right>leftreturn right;}else{//mid>left>rightreturn left;}}
}int PartSort_Prev_postPointer(int *data, int left, int right)
{int mid = GetMidIndex(data, left, right);swap(&data[left], &data[mid]);int key = left;int cur = left + 1;int prev = left;while (cur <= right){//++prev != cur可以防止cur和自己本身交换导致多交换一次if (data[cur] < data[key] && ++prev != cur){swap(&data[cur], &data[prev]);}cur++;}swap(&data[prev], &data[key]);return prev;
}//前后指针法
void QuickSort_Prev_postPointer(int* data,int left, int right)
{if (left >= right){return;}int key = PartSort_Prev_postPointer(data, left, right);QuickSort_Prev_postPointer(data, left, key - 1);QuickSort_Prev_postPointer(data, key + 1, right);
}int main()
{int data[] = { 23,48,67,45,21,90,33,11 };int sz = sizeof(data) / sizeof(int);QuickSort_Prev_postPointer(data, 0, sz - 1);for (int i = 0; i < sz; i++){printf("%d ", data[i]);}return 0;
}
输出结果:
11 21 23 33 45 48 67 90

快速排序非递归版

由于递归的函数栈帧空间是在栈上创建的,而且栈上的空间较堆空间小,所以当数据量太大时,可以考虑用快速排序的非递归版,一般用栈来实现,基本思路如下:首先将数据的头和尾进行入栈操作,再在循环中通过出栈和获取栈顶元素控制左区间和右区间,可以先执行左区间或者右区间,本处以先右区间再左区间为例,通过需要拆分数据的部分出栈排序,再接着重复步骤最后排序完成,具体思路分析如下:

void QuickSort_NotRecursion(int* data, int left, int right)
{ST st = { 0 };STInit(&st);//压入第一个元素和最后一个元素所在位置STPush(&st, left);STPush(&st, right);while (!STEmpty(&st)){//获取区间int right = STTop(&st);STPop(&st);int left = STTop(&st);STPop(&st);//单趟排序int key = PartSort_Hoare(data, left, right);//更新区间//先压右侧区间if (key + 1 < right){STPush(&st, key + 1);STPush(&st, right);}//再压左侧区间if (left < key - 1){STPush(&st, left);STPush(&st, key - 1);}}STDestroy(&st);
}

快速排序的时间复杂度为O(N\times log_{2}{N}),空间复杂度为O(log_{2}{N}),属于不稳定算法

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

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

相关文章

安卓玩机工具推荐----MTK芯片读写分区 备份分区 恢复分区 制作线刷包 工具操作解析

安卓玩机工具推荐----高通芯片9008端口读写分区 备份分区 恢复分区 制作线刷包 工具操作解析 安卓玩机工具推荐----ADB状态读写分区 备份分区 恢复分区 查看分区号 工具操作解析 前面做了两期教程。分别解析了下ADB端口与高通9008端口备份分区一些基础的常识&#xff0c;那么…

【探索程序员职业赛道:挑战与机遇】

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

经典面试题HTTP请求主要有以下几种方式

GET&#xff1a;用于请求服务器发送指定资源。GET请求通常用于获取信息&#xff0c;而不会对资源进行修改&#xff0c;因此它是幂等的。 POST&#xff1a;用于向服务器提交数据&#xff0c;通常用于创建新资源或更新现有资源。POST请求不是幂等的&#xff0c;因为它可能会多次…

EMC技术:基础概念到应用的解读?|深圳比创达电子

电磁兼容性&#xff08;Electromagnetic Compatibility&#xff0c;简称EMC&#xff09;作为一项重要的技术领域&#xff0c;在现代电子设备中扮演着至关重要的角色。本文将从基础概念开始&#xff0c;逐步深入探讨EMC技术的原理、应用和意义。 一、EMC的基础概念 EMC是指电子…

ELFK 分布式日志收集系统

ELFK的组成&#xff1a; Elasticsearch: 它是一个分布式的搜索和分析引擎&#xff0c;它可以用来存储和索引大量的日志数据&#xff0c;并提供强大的搜索和分析功能。 &#xff08;java语言开发&#xff0c;&#xff09;logstash: 是一个用于日志收集&#xff0c;处理和传输的…

收割机案例-简单的动态规划

#include<iostream> using namespace std; // 创建土地 short land[32][32]; short n,m;// 实际使用的土地大小 short landA[32][32];//用A收割机收割数量记录 short landB[32][32];// 用B收割机收割数量记录 int main(){cin>>n>>m;// 存储农作物产量for(sho…

LeetCode:猜数字游戏

解决方案的性能&#xff1a; 时间复杂度&#xff1a;O&#xff08;n&#xff09; 题目描述&#xff1a; 写出一个秘密数字&#xff0c;并请朋友猜这个数字是多少。朋友每猜测一次&#xff0c;你就会给他一个包含下述信息的提示&#xff1a; 猜测数字中有多少位属于数字和确切…

C#,子集和问题(Subset Sum Problem)的算法与源代码

1 子集和问题&#xff08;Subset Sum Problem&#xff09; 给定一组非负整数和一个值和&#xff0c;确定给定集合中是否存在和等于给定和的子集。 示例&#xff1a; 输入&#xff1a;set[]{3&#xff0c;34&#xff0c;4&#xff0c;12&#xff0c;5&#xff0c;2}&#xff…

基于智慧灯杆的智慧城市解决方案(1)

背景概述 智慧城市要求充分运用信息和通信技术手段感测、分析、整合城市运行核心系统的各项关键信息,从而对于包括民生、城市服务、工商业活动在内的各种需求做出智能的响应。在这个过程中,迫切需要一个信息采集、信息路灯作为城市中密度最大、数信息发布的载体。处理、量最…

【RHCSA问答题】第十二章 管理网络

系列文章目录 第一章 红帽企业Linux入门 第二章 访问命令行 第三章 从命令行管理文件&#xff08;上&#xff09; 第三章 从命令行管理文件&#xff08;下&#xff09; 第五章 创建、查看和编辑文本文件&#xff08;上&#xff09; 第五章 创建、查看和编辑文本文件&#xff0…

【快速入门 Vue 框架:从基础到实践】

在现代的 Web 开发中&#xff0c;Vue.js 已经成为了一种非常流行的 JavaScript 框架。它的简洁性和灵活性使得开发者能够快速构建交互性强、高效的用户界面。本文将带领读者从基础开始&#xff0c;逐步掌握 Vue 框架的核心概念&#xff0c;并通过实例演示如何快速上手 Vue 框架…

WPF LinearGradientBrush立体效果

WPF LinearGradientBrush立体效果 渐变方向 1. 默认是左上角到右下角 2.从左到右 <Border Height"35" Width"120"><Border.Background><LinearGradientBrush EndPoint"1,0"><GradientStop Color"Yellow"Offs…

28.基于SpringBoot + Vue实现的前后端分离-在线文档管理系统(项目 + 论文PPT)

项目介绍 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;在线文档管理当然也不能排除在外。在线文档管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&…

Qt插件之输入法插件的构建和使用(一)

文章目录 输入法概述输入法插件实现及调用输入键盘搭建定义样式自定义按钮实现自定义可拖动标签数字符号键盘候选显示控件滑动控件手绘输入控件输入法概述 常见的输入法有三种形式: 1.系统级输入法 2.普通程序输入法 3.程序自带的输入法 系统级输入法就是咱们通常意义上的输入…

【深度学习数学基础】隐变量条件概率建模

P θ ( y ∣ x ) ∫ z P θ ( y ∣ z , x ) p θ ( z ∣ x ) d z P_\theta(\mathbf{y}|\mathbf{x})\int_\mathbf{z}P_\theta(\mathbf{y}|\mathbf{z},\mathbf{x})p_\theta(\mathbf{z}|\mathbf{x})d\mathbf{z} Pθ​(y∣x)∫z​Pθ​(y∣z,x)pθ​(z∣x)dz 上面的公式是一个条件…

select poll epoll的区别

I/O多路复⽤通常通过select、poll、epoll等系统调⽤来实现。 select&#xff1a; select是⼀个最古老的I/O多路复⽤机制&#xff0c;它可以通过轮询的方式监视多个⽂件描述符的可读、可写和错误状态。然而&#xff0c;但是它的效率可能随着监视的⽂件描述符数量的增加⽽降低。…

蓝桥杯2022年第十三届省赛真题-求阶乘

二分法 1.定义left为0&#xff0c;right为Long.MAX_VALUE。之后再进行while循环来进行查找精准。之后在调用方法来计算二分查找的值中有几个5的倍数的个数。如果这个值中5的倍数的个数不等于条件就返回-1 符合条件就返回这个二分查找的数。 import java.util.Scanner;public…

C/C++生态工具链——编译构建工具CMake/CMakeList初探

一&#xff0c;CMake简介 CMake的全称是Cross-platform Make。我第一次参与Linux C开发时使用的工具是Make&#xff0c;而后开始切换到CMake&#xff0c;一开始以为CMake是和C语言有关&#xff0c;原来开头的C表示它可以跨平台。 CMake的使用场景&#xff1a; 跨平台编译运行&…

爬虫练习:获取某招聘网站Python岗位信息

一、相关网站 二、相关代码 import requests from lxml import etree import csv with open(拉钩Python岗位数据.csv, w, newline, encodingutf-8) as csvfile:fieldnames [公司, 规模,岗位,地区,薪资,经验要求]writer csv.DictWriter(csvfile, fieldnamesfieldnames)writer…

springboot262基于spring boot的小型诊疗预约平台的设计与开发

小型诊疗预约平台 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本小型诊疗预约平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理…