数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)

news/2025/3/19 13:54:09/

目录

算法概述

图示

伪代码

选主元

子集划分

小规模数据的处理

算法实现


算法概述

图示

快速排序和归并排序有一些相似,都是用到了分而治之的思想:

伪代码

 

通过初步的认识,我们能够知道快速排序算法最好的情况应该是:

每次都正好中分,即每次选主元都为元素的中位数的位置。

最好情况的时间复杂度为T(N) = O(NlogN)

选主元

假设我们把第一个元素设为主元,看以下的一种特殊情况:

 选了第一个元素为主元之后,扫描所有元素所用时间复杂度为O(N),然后还有N-1个元素要进行递归,时间复杂度记为T(N-1),所以最终它的时间复杂度为:

\begin{matrix} T(N) &= &O(N)+T(N-1) \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \\ &= &O(N)+O(N-1)+T(N-2) \\ &= &O(N)+O(N-1)+...+O(1) \\ &= &O(N^2) \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \: \end{matrix}

既然这种方法不行,那如果我们随机取pivot呢?

很显然,rand()函数的时间效率是很低的,当然也不考虑。

所以我们就想,

取头、中、尾的中位数,例如取三个数的中位数:8、12、3的中位数就是8;或者五个数选取中位数等等。

下面就是三个数中选取中位数的算法:

ElementType Median3( ElementType A[], int Left, int Right )
{ int Center = (Left+Right) / 2;if ( A[Left] > A[Center] )Swap( &A[Left], &A[Center] );if ( A[Left] > A[Right] )Swap( &A[Left], &A[Right] );if ( A[Center] > A[Right] )Swap( &A[Center], &A[Right] );/* 此时A[Left] <= A[Center] <= A[Right] */Swap( &A[Center], &A[Right-1] ); /* 将基准Pivot藏到右边*//* 只需要考虑A[Left+1] … A[Right-2] */return  A[Right-1];  /* 返回基准Pivot */
}

 选好基准之后,我们就进入子集的划分。

子集划分

注意:如果有元素正好等于pivot时,则停下来进行交换。

小规模数据的处理

快速排序有一个比较明显的问题,那就是用递归,而递归需要频繁地调用栈;在小规模数据的处理上不占优势。

也就是说,对小规模的数据(例如N不到100)可能还不如插入排序快。

解决方案

  • 当递归的数据规模充分小时,则停止递归,直接调用简单排序(例如插入排序)
  • 在程序中定义一个Cutoff的阈值

算法实现

ElementType Median3( ElementType A[], int Left, int Right )
{ int Center = (Left+Right) / 2;if ( A[Left] > A[Center] )Swap( &A[Left], &A[Center] );if ( A[Left] > A[Right] )Swap( &A[Left], &A[Right] );if ( A[Center] > A[Right] )Swap( &A[Center], &A[Right] );/* 此时A[Left] <= A[Center] <= A[Right] */Swap( &A[Center], &A[Right-1] ); /* 将基准Pivot藏到右边*//* 只需要考虑A[Left+1] … A[Right-2] */return  A[Right-1];  /* 返回基准Pivot */
}void Qsort( ElementType A[], int Left, int Right )
{ /* 核心递归函数 */ int Pivot, Cutoff, Low, High;if ( Cutoff <= Right-Left ) { /* 如果序列元素充分多,进入快排 */Pivot = Median3( A, Left, Right ); /* 选基准 */ Low = Left; High = Right-1;while (1) { /*将序列中比基准小的移到基准左边,大的移到右边*/while ( A[++Low] < Pivot ) ;while ( A[--High] > Pivot ) ;if ( Low < High ) Swap( &A[Low], &A[High] );else break;}Swap( &A[Low], &A[Right-1] );   /* 将基准换到正确的位置 */ Qsort( A, Left, Low-1 );    /* 递归解决左边 */ Qsort( A, Low+1, Right );   /* 递归解决右边 */  }else InsertionSort( A+Left, Right-Left+1 ); /* 元素太少,用简单排序 */ 
}void QuickSort( ElementType A[], int N )
{ /* 统一接口 */Qsort( A, 0, N-1 );
}

end


学习自:MOOC数据结构——陈越、何钦铭


http://www.ppmy.cn/news/981020.html

相关文章

Vben Admin学习笔记

Modal 弹窗 modal弹窗一般作为单文件组件被引用&#xff0c;下面是两段示例代码&#xff1a; 弹窗文件 Modal.vue // Modal.vue <template><BasicModal v-bind"$attrs" title"Modal Title" :helpMessage"[提示1, 提示2]">Modal I…

自然语言处理从入门到应用——LangChain:模型(Models)-[聊天模型(Chat Models):基础知识]

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 聊天模型是语言模型的一种变体。虽然聊天模型在内部使用语言模型&#xff0c;但它们公开的接口略有不同。它们不是提供一个“输入文本&#xff0c;输出文本”的API&#xff0c;而是提供一个以“聊天消息”作为输入和输…

LLM 基础-transformers 库快速入门

一,Transformers 术语 1.1,token、tokenization 和 tokenizer1.2,input IDs1.3,attention mask1.4,bos_token、eop_token、pad_token、eos_token1.5,decoder models1.6,架构与参数二,Transformers 功能 API 概述三,快速上手 3.1,transformer 模型类别3.2,Pipeline&l…

强化学习价值函数方法笔记

在强化学习中&#xff0c;价值函数&#xff08;Value Function&#xff09;是一个核心概念&#xff0c;它用于衡量在不同状态或状态-动作对下&#xff0c;一个智能体&#xff08;agent&#xff09;可以获得的预期累积奖励。价值函数对于智能体做出决策和学习行为策略非常重要。…

8-js高级-7(理解js的事件循环)

一、前言 JS是单线程语言&#xff0c;但是又可以做到异步处理高并发请求&#xff0c;这时就用到了JavaScript的事件循环机制 理解事件循环&#xff0c;可以帮助我们准确分析和运用各种异步形式&#xff0c;减少代码的不确定性&#xff0c;在一些执行效率优化上也能有明确的思路…

Oralce数据库 手工重新创建控制文件

控制文件对于Oralce数据库的作用&#xff0c;就好像微软操作系统中注册表的作用一样。控制文件是一个比较小的二进制文件&#xff0c;记录着数据库的结构信息。如果数据库控制文件发生孙华的话&#xff0c;则Oracle将无法正常启动。通常情况下&#xff0c;在创建数据库时会自动…

没有编程基础,学习风变编程有困难吗?

许多初学者担心学习编程语言太难了。但是&#xff0c;大多数人可以通过时间、决心和正确的资源掌握编码。 许多因素决定了学习者找到编码的难度。一些语言优先考虑简单的命令&#xff0c;而另一些语言则使用密集的语法。有些语言比其他语言有更多的学习资源。在选择第一种编程…

UE4/5C++多线程插件制作(十、接口类和代理容器类的制作)

目录 封装 准备 MTPThreadInterface制作 作用域锁 然后我们开始重载操作符 operator<<: