排序合集之快排详解(二)

news2025/2/13 18:43:25

摘要:快速排序是一种在实践中广泛使用的高效排序算法。它基于分治策略,平均时间复杂度为O(n log n),使其成为处理大型数据集的理想选择。本文将深入探讨快速排序的各种实现方式、优化技巧以及非递归实现,并通过C语言代码示例进行详细讲解。

一、经典快速排序

1. 基本思想

快速排序的核心在于分而治之。想象一下,你要整理一堆书,你可以随机选一本书作为“基准”,然后把其他的书分成两堆:一堆是书名排在基准之前的,另一堆是排在基准之后的。接着,你再分别对这两堆书重复这个过程。这就是快速排序的基本思想!

2. 步骤
  1. 选择基准(Pivot Selection):从数组中选取一个元素作为基准(pivot)。基准的选择会影响排序效率,后面会介绍优化方法。

  2. 分区操作(Partitioning):重新排列数组,使得所有小于基准的元素位于基准之前,所有大于基准的元素位于基准之后。基准元素在此过程中会被放置在其最终排序位置。

  3. 递归排序(Recursive Sorting):递归地对基准元素左右的两个子数组进行快速排序。

3. C语言代码示例
void quickSort(int arr[], int low, int high) 
{ 
    if (low < high) 
    {
        int pi = partition(arr, low, high); 
        quickSort(arr, low, pi - 1); 
        quickSort(arr, pi + 1, high); 
    } 
} 

int partition(int arr[], int low, int high) 
{ 
    int pivot = arr[high]; 
    int i = low - 1; 
    for (int j = low; j < high; j++) 
    { 
        if (arr[j] < pivot) 
        { 
            i++; 
            swap(&arr[i], &arr[j]);
        } 
    }
    swap(&arr[i + 1], &arr[high]); 
    return i + 1; 
} 

void swap(int* a, int* b) 
{ 
    int t = *a; 
    *a = *b; 
    *b = t; 
}
4. 代码解释
  • quickSort(arr[], low, high):递归函数,对数组arr中从索引lowhigh的元素进行排序。递归结束的条件是low >= high,意味着子数组已经为空或者只包含一个元素,不需要再排序。

  • partition(arr[], low, high):关键函数!它选择最后一个元素作为基准,并执行分区操作,将数组划分为两个部分。i用于追踪小于基准的元素的索引。swap函数用于交换两个元素的位置。

  • swap(int* a, int* b):一个简单的交换函数,用于交换两个整数的值。

二、快速排序 - 挖坑法

1. 基本思想

挖坑法是一种巧妙的分区策略。可以这样想象:你先选一个基准数,把它“挖”出来,形成一个“坑”。然后,从数组两端开始,找到合适的数来填补这个“坑”,同时产生新的“坑”,直到左右指针相遇。

2. 步骤
  1. 选择基准(Pivot Selection):选择数组中的某个元素作为基准(通常选择第一个元素)。
  2. 挖坑填数(Digging and Filling)
  • 将基准元素挖出,形成第一个“坑”。
  • 从数组右端开始,寻找小于基准的元素,找到后填入左边的“坑”,并形成新的“坑”。

  • 从数组左端开始,寻找大于基准的元素,找到后填入右边的“坑”,并形成新的“坑”。

  • 重复上述过程,直到左右指针相遇

   3.放置基准(Pivot Placement):将基准元素放入左右指针相遇的位置,完成分区。

3. C语言代码示例
void quickSort(int arr[], int low, int high) { 
    if (low < high) { 
        int pivot = arr[low]; 
        int i = low;
        int j = high; 

        while (i < j) { 
            while (i < j && arr[j] >= pivot) j--;
            if (i < j) { 
                arr[i] = arr[j];
                i++;
            }
            while (i < j && arr[i] <= pivot) i++;
            if (i < j) { 
                arr[j] = arr[i];
                j--;
            }
        }
        arr[i] = pivot; 
        quickSort(arr, low, i - 1); 
        quickSort(arr, i + 1, high); 
    }
}
4. 代码解释

关键在于while (i < j)循环中的填坑操作。理解指针ij的移动和值的覆盖是理解这个算法的关键。

三、快速排序 - 前后指针法

1. 基本思想

前后指针法使用两个指针,ij。指针i指向小于基准的子数组的末尾,而指针j用于遍历整个数组。如果j遇到的元素小于基准,则将其交换到i的后面,并增加i

2. 步骤
  1. 选择基准(Pivot Selection):选择数组中的某个元素作为基准(通常选择最后一个元素)。

  2. 移动指针(Moving Pointers)

    • 使用指针j遍历数组。

    • 如果arr[j]小于基准,则将arr[j]arr[i+1]交换,并递增i

  3. 放置基准(Pivot Placement):将基准元素放到正确的位置。

3. C语言代码示例
void quickSort(int arr[], int low, int high) { 
    if (low < high) { 
        int pivot = arr[high]; 
        int i = low; 

        for (int j = low; j < high; j++) { 
            if (arr[j] < pivot) { 
                swap(&arr[i], &arr[j]);
                i++;
            }
        } 
        swap(&arr[i], &arr[high]); 

        quickSort(arr, low, i - 1); 
        quickSort(arr, i + 1, high); 
    }
}
4. 代码解释

i始终指向小于pivot的区域的下一个位置。在循环过程中,i之前的元素都小于pivot

四、性能优化

1. 三数取中
  • 问题:如果基准元素选择不当(例如,总是选择最大或最小元素),快速排序可能会退化到O(n²)的时间复杂度。

  • 解决方案:三数取中法。从数组的第一个、中间和最后一个元素中选择中间值作为基准。这可以有效避免最坏情况的发生。

  • 步骤:

  1. 计算中间位置:mid = (low + high) / 2
  2. 比较 arr[low]arr[mid] 和 arr[high],并将中间值与 arr[high] 交换。
2. C代码示例
void quickSort(int arr[], int low, int high) { 
    if (low < high) { 
        int mid = (low + high) / 2;

        if (arr[mid] < arr[low]) swap(&arr[mid], &arr[low]);
        if (arr[high] < arr[low]) swap(&arr[high], &arr[low]);
        if (arr[mid] < arr[high]) swap(&arr[mid], &arr[high]);

        int pivot = arr[high]; 
        int i = low; 

        for (int j = low; j < high; j++) { 
            if (arr[j] < pivot) { 
                swap(&arr[i], &arr[j]);
                i++;
            }
        } 
        swap(&arr[i], &arr[high]); 

        quickSort(arr, low, i - 1); 
        quickSort(arr, i + 1, high); 
    }
}
3. 针对重复值优化
  • 问题:当数组中存在大量重复元素时,快速排序的性能会下降。

  • 解决方案:在分区过程中,将所有等于基准的元素集中到一起,避免对它们进行递归排序。这通常被称为“三向切分”。

4. C代码示例
void quickSort(int arr[], int low, int high) { 
    if (low < high) { 
        int pivot = arr[low]; 
        int lt = low; 
        int gt = high; 
        int i = low + 1; 

        while (i <= gt) { 
            if (arr[i] < pivot) { 
                swap(&arr[i], &arr[lt + 1]);
                lt++;
                i++;
            } else if (arr[i] > pivot) { 
                swap(&arr[i], &arr[gt]);
                gt--;
            } else { 
                i++;
            }
        } 

        quickSort(arr, low, lt - 1); 
        quickSort(arr, gt + 1, high); 
    }
}
5. 代码解释

此版本将数组划分为三个部分:小于基准值、等于基准值和大于基准值。等于基准值的部分在递归调用中被排除,从而提高了具有许多重复项的数组的性能。

五、快速排序 - 非递归实现

1. 基本思想

递归版本的快速排序在处理大型数组时可能会导致栈溢出。非递归实现通过使用栈来模拟递归调用,从而避免栈溢出的问题。

2. 步骤
  1. 初始化栈(Initialize Stack):将初始的分区范围(low和high)压入栈。

  2. 循环处理(Loop Processing):从栈中弹出一个分区范围,进行分区操作。

  3. 压入子分区(Push Sub-partitions):将左右子分区的范围压入栈中,以便后续处理

3. C语言代码示例
#include <stdio.h>
#include <stdlib.h>

void swap(int *a, int *b) { 
    int temp = *a; 
    *a = *b; 
    *b = temp; 
} 

int partition(int arr[], int low, int high) { 
    int pivot = arr[high]; 
    int i = (low - 1); 

    for (int j = low; j <= high - 1; j++) { 
        if (arr[j] < pivot) { 
            i++; 
            swap(&arr[i], &arr[j]); 
        } 
    } 
    swap(&arr[i + 1], &arr[high]); 
    return (i + 1); 
}

void quickSortNonRecursive(int arr[], int low, int high) { 
    int *stack = (int *)malloc(sizeof(int) * (high - low + 1) * 2); 
    int top = -1; 

    stack[++top] = low; 
    stack[++top] = high; 

    while (top >= 0) { 
        high = stack[top--]; 
        low = stack[top--]; 

        int p = partition(arr, low, high); 

        if (p - 1 > low) { 
            stack[++top] = low; 
            stack[++top] = p - 1; 
        } 
        if (p + 1 < high) { 
            stack[++top] = p + 1; 
            stack[++top] = high; 
        } 
    } 
    free(stack); 
}

int main() { 
    int arr[] = {10, 7, 8, 9, 1, 5}; 
    int n = sizeof(arr) / sizeof(arr[0]); 
    quickSortNonRecursive(arr, 0, n - 1); 

    printf("Sorted array: \n"); 
    for (int i = 0; i < n; i++) 
        printf("%d ", arr[i]); 
    return 0; 
}
4. 代码解释
  • 使用动态分配的栈来避免固定大小的栈的限制。

  • 在算法结束时释放动态分配的内存以避免内存泄漏。

  • 栈存储要处理的分区的边界。

  • 该算法迭代地从栈中弹出分区边界,对分区进行分区,并将新分区推入栈中,直到所有分区都被处理完毕。

合适的基准和采用适当的优化策略,可以充分发挥其性能优势。 理解快速排序的不同实现方式有助于在实际应用中选择最合适的算法变体。 无论是经典递归实现还是非递归实现,快速排序都是每个程序员工具箱中不可或缺的一部分.

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

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

相关文章

前缀树算法篇:前缀信息的巧妙获取

前缀树算法篇&#xff1a;前缀信息的巧妙获取 那么前缀树算法是一个非常常用的算法&#xff0c;那么在介绍我们前缀树具体的原理以及实现上&#xff0c;我们先来说一下我们前缀树所应用的一个场景&#xff0c;那么在一个字符串的数据集合当中&#xff0c;那么我们查询我们某个字…

大数据系列 | 白话讲解大数据技术生态中Hadoop、Hive、Spark的关系介绍

大数据属于数据管理系统的范畴&#xff0c;数据管理系统无非就两个问题&#xff1a;数据怎么存、数据怎么算    现在的信息爆炸时代&#xff0c;一台服务器数据存不下&#xff0c;可以找10台服务器存储&#xff0c;10台存储不下&#xff0c;可以再找100台服务器存储。但是这1…

华为云函数计算FunctionGraph部署ollma+deepseek

1 概述 ollama和deepseek如果需要多实例&#xff0c;一种方式是部署在kubernetes集群中&#xff0c;一种是使用云厂商的云函数服务。云函数服务是按量付费&#xff0c;并且底层支持GPU&#xff0c;不需要维护kubernetes集群。本文介绍使用华为云函数计算FunctionGraph来部署ol…

尚硅谷爬虫note001

一、模板设置 file——setting——editor——code style——file and code template——python script # _*_ coding : utf-8 _*_ # Time : ${DATE} ${TIME} # Author : 20250206-里奥 # File : ${NAME} # Project : ${PROJECT_NAME} 二、数据类型 2-1. 数字 整型int 浮点型f…

35~37.ppt

目录 35.张秘书-《会计行业中长期人才发展规划》 题目​ 解析 36.颐和园公园&#xff08;25张PPT) 题目​ 解析 37.颐和园公园&#xff08;22张PPT) 题目 解析 35.张秘书-《会计行业中长期人才发展规划》 题目 解析 插入自定义的幻灯片&#xff1a;新建幻灯片→重用…

FPGA简介|结构、组成和应用

Field Programmable Gate Arrays&#xff08;FPGA&#xff0c;现场可编程逻辑门阵列&#xff09;&#xff0c;是在PAL、GAL、CPLD等可编程器件的基础上进一步发展的产物&#xff0c; 是作为专用集成电路&#xff08;ASIC&#xff09;领域中的一种半定制电路而出现的&#xff0c…

4. React 中的 CSS

用例中的干净的脚手架的创建可以参考另一篇文章&#xff1a;3.React 组件化开发React官方并没有给出在React中统一的样式风格&#xff1a; 由此&#xff0c;从普通的css&#xff0c;到css modules&#xff0c;再到css in js&#xff0c;有几十种不同的解决方案&#xff0c;上百…

django中间件,中间件给下面传值

1、新建middleware.py文件 # myapp/middleware.py import time from django.http import HttpRequest import json from django.http import JsonResponse import urllib.parse from django.core.cache import cache from comm.Db import Db class RequestTimeMiddleware:def …

【论文阅读】Revisiting the Assumption of Latent Separability for Backdoor Defenses

https://github.com/Unispac/Circumventing-Backdoor-Defenses 摘要和介绍 在各种后门毒化攻击中&#xff0c;来自目标类别的毒化样本和干净样本通常在潜在空间中形成两个分离的簇。 这种潜在的分离性非常普遍&#xff0c;甚至在防御研究中成为了一种默认假设&#xff0c;我…

Python基于Django的微博热搜、微博舆论可视化系统(V3.0)【附源码】

博主介绍&#xff1a;✌Java老徐、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&…

不小心删除服务[null]后,git bash出现错误

不小心删除服务[null]后&#xff0c;git bash出现错误&#xff0c;如何解决&#xff1f; 错误描述&#xff1a;打开 git bash、msys2都会出现错误「bash: /dev/null: No such device or address」 问题定位&#xff1a; 1.使用搜索引擎搜索「bash: /dev/null: No such device o…

【云安全】云原生- K8S kubeconfig 文件泄露

什么是 kubeconfig 文件&#xff1f; kubeconfig 文件是 Kubernetes 的配置文件&#xff0c;用于存储集群的访问凭证、API Server 的地址和认证信息&#xff0c;允许用户和 kubectl 等工具与 Kubernetes 集群进行交互。它通常包含多个集群的配置&#xff0c;支持通过上下文&am…

【工业场景】用YOLOv8实现火灾识别

火灾识别任务是工业领域急需关注的重点安全事项,其应用场景和背景意义主要体现在以下几个方面: 应用场景:工业场所:在工厂、仓库等工业场所中,火灾是造成重大财产损失和人员伤亡的主要原因之一。利用火灾识别技术可以及时发现火灾迹象,采取相应的应急措施,保障人员安全和…

(2025)深度分析DeepSeek-R1开源的6种蒸馏模型之间的逻辑处理和编写代码能力区别以及配置要求,并与ChatGPT进行对比(附本地部署教程)

(2025)通过Ollama光速部署本地DeepSeek-R1模型(支持Windows10/11)_deepseek猫娘咒语-CSDN博客文章浏览阅读1k次&#xff0c;点赞19次&#xff0c;收藏9次。通过Ollama光速部署本地DeepSeek-R1(支持Windows10/11)_deepseek猫娘咒语https://blog.csdn.net/m0_70478643/article/de…

【自然语言处理】TextRank 算法提取关键词、短语、句(Python源码实现)

文章目录 一、TextRank 算法提取关键词 [工具包]二、TextRank 算法提取关键短语[工具包]三、TextRank 算法提取关键句[工具包]四、TextRank 算法提取关键句&#xff08;Python源码实现&#xff09; 一、TextRank 算法提取关键词 [工具包] 见链接 【自然语言处理】TextRank 算法…

记一次Self XSS+CSRF组合利用

视频教程在我主页简介或专栏里 &#xff08;不懂都可以来问我 专栏找我哦&#xff09; 目录&#xff1a;  确认 XSS 漏洞 确认 CSRF 漏洞 这个漏洞是我在应用程序的订阅表单中发现的一个 XSS 漏洞&#xff0c;只能通过 POST 请求进行利用。通常情况下&#xff0c;基于 POST 的…

JDBC如何连接数据库

首先&#xff0c;我们要去下载JDBC的驱动程序 官网下载地址:https://downloads.mysql.com/archives/c-j/ 选择最新版本就可以 然后回到我们idea点击file - project Structure - Modules&#xff0c; 就行了 参考1&#xff1a;如何解决JDBC连接数据库出现问题且对进行数据库操…

AI语言模型的技术之争:DeepSeek与ChatGPT的架构与训练揭秘

云边有个稻草人-CSDN博客 目录 第一章&#xff1a;DeepSeek与ChatGPT的基础概述 1.1 DeepSeek简介 1.2 ChatGPT简介 第二章&#xff1a;模型架构对比 2.1 Transformer架构&#xff1a;核心相似性 2.2 模型规模与参数 第三章&#xff1a;训练方法与技术 3.1 预训练与微调…

网络安全威胁是什么

1.网络安全威胁的概念 网络安全威胁指网络中对存在缺陷的潜在利用&#xff0c;这些缺陷可能导致信息泄露、系统资源耗尽、非法访问、资源被盗、系统或数据被破坏等。 2.网络安全威胁的类型 物理威胁系统漏洞威胁身份鉴别威胁线缆连接威胁有害程序危险 &#xff08;1&#x…

驱动开发、移植(最后的说法有误,以后会修正)

一、任务明确&#xff1a;把创龙MX8的驱动 按照我们的要求 然后移植到 我们的板子 1.Linux系统启动卡制作&#xff0c; sd卡 先按照 《用户手册—3-2-Linux系统启动卡制作及系统固化》 把创龙的Linux系统刷进去。 2. 把TLIMX8-EVM的板子过一遍 把刚刚烧好系统的sd卡插入 创…