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>right
            return mid;
        }
        else if (data[left] > data[right])
        {
            //left>right>mid
            return right;
        }
        else
        {
            //right>left>mid
            return left;
        }
    }
    else
    {
        if (data[mid] < data[right])
        {
            //right>mid>left
            return mid;
        }
        else if (data[right] > data[left])
        {
            //mid>right>left
            return right;
        }
        else
        {
            //mid>left>right
            return 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>right
            return mid;
        }
        else if (data[left] > data[right])
        {
            //left>right>mid
            return right;
        }
        else
        {
            //right>left>mid
            return left;
        }
    }
    else
    {
        if (data[mid] < data[right])
        {
            //right>mid>left
            return mid;
        }
        else if (data[right] > data[left])
        {
            //mid>right>left
            return right;
        }
        else
        {
            //mid>left>right
            return 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}),属于不稳定算法

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

程序员常用小工具推荐

前言 工作或者学习时&#xff0c;常常有一些工具能帮到我们很多&#xff0c;本次简单列举和说明&#xff0c;如果有更多更好用的&#xff0c;欢迎讨论补充。 工具大全 网络分析工具 Wireshark,可以很清晰的解析和过滤网络包&#xff0c;也有助于分析网络的的传输原理。linux环…

基于FPGA的HyperRam接口设计与实现

一 HyperRAM 针对一些低功耗、低带宽应用&#xff08;物联网、消费产品、汽车和工业应用等&#xff09;&#xff0c;涉及到外部存储&#xff0c;HyperRAM提供了更简洁的内存解决方案。 HyperRAM具有以下特性&#xff1a; 1、超低功耗&#xff1a;200MHz工作频率下读写不到50mW…

新书速览|Vue.js 3.x+Element Plus从入门到精通(视频教学版)

详解Vue.jsElement Plus框架各组件的用法&#xff0c;实战网上商城系统和图书借阅系统开发 本书内容 《Vue.js 3.xElement Plus从入门到精通&#xff1a;视频教学版》通过对Vue.js&#xff08;简称Vue&#xff09;的示例和综合案例的介绍与演练&#xff0c;使读者快速掌握Vue.j…

计算机网络—eNSP搭建基础 IP网络

目录 1.下载eNSP 2.启动eNSP 3.建立拓扑 4.建立一条物理连接 5.进入终端系统配置界面 6.配置终端系统 7.启动终端系统设备 8.捕获接口报文 9.生成接口流量 10.观察捕获的报文 1.下载eNSP 网上有许多下载eNSP的方式&#xff0c;记得还要下其它三个Virtual Box、Winpa…

HSCCTF 3th 2024 Web方向 题解wp

WEB-CHECKIN【*没出】 直接给了源码 <?php highlight_file(__FILE__); error_reporting(0); $a$_POST[1]; $b"php://filter/$a/resource/dev/null"; if(file_get_contents($b)"2024"){echo file_get_contents(/flag); }else{echo $b; }咋这么像 WEB…

python文件组织:包(package)、模块(module)、文件(file)

包&#xff1a; 模块所在的包&#xff0c;创建一个包用于组织多个模块&#xff0c;包文件夹中必须创建一个名为’__init__.py’的文件&#xff0c;以将其识别为包&#xff0c;否则只能算作是一个普通的目录。在使用该包时&#xff0c;init自动执行。 包可以多层嵌套&#xff…

使用 ReclaiMe Pro 进行 RAIDZ 数据恢复

天津鸿萌科贸发展有限公司是 ReclaiMe Pro 数据恢复软件授权代理商。 ZFS 是一个开源文件系统&#xff0c;主要用于 FreeNAS 和 NAS4Free 存储系统。在开发 ZFS 时&#xff0c;主要目标是可靠性&#xff0c;这是通过写时复制、冗余元数据、日志等不同功能来实现的。ZFS 使用自…

Redis核心数据结构之跳跃表

跳跃表 概述 跳跃表(skiplist)是一种有序数据结构&#xff0c;它通过在每个节点中维持多个指向其他节点的指针&#xff0c;从而达到快速访问节点的目的。跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找&#xff0c;还可以通过顺序性操作来批量处理节点。在大部分情况下&am…

VB 数据质量诊断软件(分析数据的完整性,合理性,准确性)-139-(代码+程序说明)

转载地址http://www.3q2008.com/soft/search.asp?keyword139 前言: 为何口出狂言,作任何VB和ASP的系统, 这个就是很好的一个证明 :) 又有些狂了... 数据库操作谁都会,接触的多了也没什么难的,VB编程难在哪?算法上,这个是一个算法题的毕业设计 哈哈忙活了足足有一○小时, …

C++进阶之路---多态(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、多态的概念 1.概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为…

IPSec NAT穿越原理

一、IPSec VPN在NAT场景中存在的问题 当某些组网中&#xff0c;有的分支连动态的公网IP地址也没有&#xff0c;只能由网络中的NAT设备进行地址转换&#xff0c;才能访问互联网&#xff0c;然而IPsec是用来保护报文不被修改的&#xff0c;而NAT需要修改报文的IP地址&#xff0c…

9、组合模式(结构性模式)

组合模式又叫部分整体模式&#xff0c;它创建了对象组的树形结构&#xff0c;将对象组合成树状结构&#xff0c;以一致的方式处理叶子对象以及组合对象&#xff0c;不以层次高低定义类&#xff0c;都是结点类 一、传统组合模式 举例&#xff0c;大学、学院、系&#xff0c;它们…

崇法致行法律知识竞赛活动方案

赛程安排分两天&#xff0c;两场进行。 第一天&#xff08;第一场&#xff09;&#xff08;初赛&#xff09; 共 16 个二级分行&#xff0c;每行三人&#xff0c;共16 个战队参赛。 第一轮——必答轮 在大屏幕上显示10个选择题&#xff08;5个单选、5个多选&#xff09;&…

docker安装ollama

拉取镜像 docker pull ollama/ollama 运行容器 &#xff08;挂载路径 D:\ollama 改成你自己喜欢的路径&#xff09; CPU only docker run -d -v D:\ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama Nvidia GPU&#xff08;没试过这个&#xff09; doc…

Vue脚手架

Vue脚手架 学习目标&#xff1a; 理解Node.js基本使用方法理解包资源管理器NPM的使用理解webpack的作用理解 vue-cli 脚手架 (重点)Element-UI 组件库 1.vue的格式&#xff1a;new Vue({//作用的视图el:"id选择器",//vue中的数据/*data:{key:value,key:value,...}…

判断链表回文

题目&#xff1a; //方法一&#xff0c;空间复杂度O(n) class Solution { public:bool isPalindrome(ListNode* head) {vector<int> nums; //放进数组后用双指针判断ListNode* cur head;while(cur){nums.emplace_back(cur->val);cur cur->next;}for(int i0…

Microsoft SQL Server 编写汉字转拼音函数

目录 应用场景 举例 函数实现 小结 应用场景 在搜索应用中&#xff0c;我们一般会提供一个搜索框&#xff0c;输入关健字&#xff0c;点击查询按钮以获取结果数据。大部分情况我们会提供模糊查询的形式以在一个或多个字段进行搜索以获取结果。这样可以简化用户的操作&…

高分1、2号卫星原始遥感影像数据

高分一号 高分一号卫高分一号卫星是中国高分辨率对地观测系统的首发星&#xff0c;突破了高空间分辨率、多光谱与宽覆盖相结合的光学遥感等关键技术&#xff0c;设计寿命5至8年。 高分辨率对地观测系统工程是《国家中长期科学和技术发展规划纲要(2006&#xff5e;2020年)》确定…

专题二 - 滑动窗口 - leetcode 1004. 最大连续1的个数 III | 中等难度

leetcode 1004. 最大连续1的个数 III leetcode 1004. 最大连续1的个数 III | 中等难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理3. 时间复杂度 3. 代码实现4. 知识与收获 leetcode 1004. 最大连续1的个数 III | 中等难度 1. 题目详情 给定一个二…

Linux之线程控制

目录 一、POSIX线程库 二、线程的创建 三、线程等待 四、线程终止 五、分离线程 六、线程ID&#xff1a;pthread_t 1、获取线程ID 2、pthread_t 七、线程局部存储&#xff1a;__thread 一、POSIX线程库 由于Linux下的线程并没有独立特有的结构&#xff0c;所以Linux并…