算法导论学习之快排+各种排序算法时间复杂度总结

快排是一种最常用的排序算法,因为其平均的时间复杂度是nlgn,并且其中的常数因子比较小。

一.快速排序

快排和合并排序一样都是基于分治的排序算法;快排的分治如下:

分解:对区间A[p,r]进行分解,返回q,使得A[p–q-1]都不大于A[q] A[q+1,r]都大于A[q];

求解:对上面得到的区间继续递归进行快排

合并:因为快排是原地排序,所以不需要特别的合并

从上可以看出最重要的就是分解函数,其按关键值将数组划分成3部分,其具体实现的过程见代码注释。

我们一般取数组的最后一个元素作为划分比较的关键值,如下面的代码

int Paratition(int *a,int p,int r)
{ ///在循环时a[p--i]表示的是不大于key的元素
  ///a[i+1--j]表示的是当前大于key的元素
  ///划分的过程其实就是将每一个元素通过比较放到这两个区间去(主要是i的增长)。
  ///当然最后还要将a[i+1]和a[r]交换,使得a[i+1]表示划分元素
    int key=a[r]; ///取最后一个元素作为比较的关键值
    int i=p-1;
    for(int j=p;j<r;j++)
        if(a[j]<=key)
        {
            i++;
            swq(a[j],a[i]);
        }
    swq(a[i+1],a[r]);
    return i+1;
}

但是我们也可以在区间a[p,r]中任取一个元素作为关键值,这样可以使得每次的划分更加的均匀,从而提高效率,对应的代码如下:

///随机划分函数
int RandParatition(int *a,int p,int r)
{
    int x=rand()%(r-p+1)+p;   ///产生一个[p,r]之间的随机数x
    swq(a[x],a[r]);  ///将a[x],a[r]交换,使得将a[x]作为划分的关键值
    return Paratition(a,p,r);
}

下面给出一份快排的完整代码:

#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstring>
using namespace std;

void swq(int &a,int &b)
{
    int t=a;
    a=b;
    b=t;
}
///划分函数
int Paratition(int *a,int p,int r)
{
    int key=a[r];
    int i=p-1;
    for(int j=p;j<r;j++)
        if(a[j]<=key)
        {
            i++;
            swq(a[j],a[i]);
        }
    swq(a[i+1],a[r]);
    return i+1;
}

///快排
void QiuckSort(int *a,int p,int r)
{
    if(p>=r)
        return;
    int q=Paratition(a,p,r);
    QiuckSort(a,p,q-1);
    QiuckSort(a,q+1,r);
}

///以上是一般的快排,还有一个随机快排
///随机快排的思想在于划分a[p,r]时我们不是每次都选a[r]作为关键值
///而是每次随机的在a[p,r]中取一个元素作为划分的关键值。

///随机划分函数
int RandParatition(int *a,int p,int r)
{
    int x=rand()%(r-p+1)+p;   ///产生一个[p,r]之间的随机数x
    swq(a[x],a[r]);  ///将a[x],a[r]交换,使得将a[x]作为划分的关键值
    return Paratition(a,p,r);
}

///随机快排函数
void RandQiuckSort(int *a,int p,int r)
{
    if(p>=r)
        return;
    int q=RandParatition(a,p,r);  ///随机快排调用的是随机划分函数
    RandParatition(a,p,q-1);
    RandParatition(a,q+1,r);
}

int main()
{
      int n=5,a[10];
      cout<<"请输入"<<n<<"个数:"<<endl;
      for(int i=1;i<=n;i++)
           cin>>a[i];
      ///RandQiuckSort(a,1,n);
      QiuckSort(a,1,n);
      cout<<"排序以后的数组:"<<endl;
      for(int i=1;i<=n;i++)
          cout<<a[i]<<" ";
      cout<<endl;
   return 0;
}

二.快排的时间复杂度:快排的时间复杂度分析是一件很麻烦的事情,我们知道如果每次的划分都是完全不平衡的即T(n)=T(n-1)+O(n),那么快排的时间复杂度是n^2;如果每次都是均等的划分即T(n)=T(n/2)+T(n/2)+O(n),那么时间复杂度就是nlgn。但是这两种划分在平时都是不常见的,所以不具有代表性;但是如果我们考察一些不平衡的划分,如每次都是9:1即T(n)=T(n/10)+T(n*9/10)+O(n),我们可以发现在这种情况下时间复杂度仍然是nlgn;并且即使不平衡性进一步增加(只要不是完全不平衡),达到99:1的程度,时间复杂度仍然会是nlgn(关于这一段算法导论上有大篇幅的证明)。所以我们可以说快排的平均时间复杂度是nlgn,这个复杂度是经常可以达到的。

三.常用排序算法的时间复杂度总结

以下简单的罗列出前面学习过的排序算法的时间复杂度,不给出严格的证明。

 排序算法          最好时间复杂度               最坏时间复杂度

 插入排序         O(n)(原数组有序)            O(n^2)(原数组逆序)

 合并排序         O(nlgn)                    O(nlgn)

 堆排序           O(nlgn)                    O(nlgn)

 快速排序         O(n^2)(每次都是完全不平衡划分) O(nlgn)(大多数情况)

从上面的表格看似乎合并排序和堆排序都要比快排好,但实际中因为快排O(nlgn)的时间复杂度是经常可以达到,并且快排中的常数因子比较小,所以在实际中快排一般是最快的排序算法,不愧他”快排”的称号。

时间: 2024-10-03 14:02:19

算法导论学习之快排+各种排序算法时间复杂度总结的相关文章

【算法导论学习-014】计数排序(CountingSortTest)

参考:<算法导论>P194页 8.2节 Counting sort 1.Counting sort的条件 待排序数全部分布在0~k之间,且k是已知数:或者分布在min~max之间,等价于分布在0~max-min之间,max和min是已知数. 2.java 实现 /** * 创建时间:2014年8月17日 下午3:22:14 项目名称:Test * * @author Cao Yanfeng * @since JDK 1.6.0_21 类说明: 计数排序法,复杂度O(n), 条件:所有数分布在0

【算法导论学习-016】两个已排过序的等长数组的中位数(median of two sorted arrays)

问题来源 <算法导论>P223 9.3-8: Let X[1..n] and Y[1..n] be two arrays, each containing nnumbers already in sorted order. Give an O(lgn)-time algorithm to find themedian of all 2n elements in arrays X and Y. 翻译过来即:求两个等长(n个元素)的已排序数组A和B的中位数 方案1:对两个数组进行归并直到统计到第n

算法导论 学习资源

学习的过程会遇到些问题,发现了一些比较好的资源,每章都会看下别人写的总结,自己太懒了,先记录下别人写的吧,呵呵. 1  Tanky Woo的,每次差不多都看他的 <算法导论>学习总结 - 1.前言 <算法导论>学习总结 - 2.第一章 && 第二章 && 第三章 <算法导论>学习总结 - 3.第四章 && 第五章 <算法导论>学习总结 - 4.第六章(1) 堆排序 <算法导论>学习总结 - 5.第六

【算法导论学习-015】基数排序(Radix sort)

1.<算法导论>P197页 8.3节Radix sort 2.java实现 这里仅仅对[算法导论学习-014]计数排序 的参数进行了修改,同时仅仅修改了一行代码. /** * 创建时间:2014年8月17日 下午4:05:48 * 项目名称:Test * @author Cao Yanfeng * @since JDK 1.6.0_21 * 类说明: 利用计数排序实现基数排序 * 条件:待排序的所有数位数相同,注意,即便不相同,也可以认为是最多那个位数,如下面的例子可以认为都是3位数 */ p

算法导论 第8章 线性时间排序

合并排序和堆排序的时间复杂度为O(nlgn),插入排序和冒泡排序的时间复杂度为O(n^2),快速排序的时间复杂度在平均情况下是O(nlgn),这些排序算法都是通过对元素进行相互比较从而确定顺序的,因此都叫比较排序. 比较排序可以看做是决策树(一个满二叉树),因为每一次比较都是一个分支.n个元素的序列,其排序的结果有 n! 种可能(n个元素的全排),所以这个决策树有 n! 个叶子结点,假设树的高度为h,则有:n! <= 2^h,所以h >= lg(n!) = Ω(nlgn).一次比较排序就是从决

普林斯顿公开课 算法3-3:三路快排

很多时候排序是为了对数据进行归类,比如对城市进行排序,对员工的职业进行排序.这种排序的特点就是重复的值特别多. 如果使用普通的快排对这些数据进行排序,会造成N^2复杂度,但是归并排序和三路快排就没有这样的问题. 三路快排 三路快排的基本思想就是,在对数据进行分区的时候分成左中右三个部分,中间都是相同的值,左侧小于中间,右侧大于中间. 性能 三路快排的复杂度比普通快排小,主要取决于数据中重复数据的数量.重复数据越多,三路快排的复杂度就越接近于N. 代码 public class Quick3 {

算法导论学习---红黑树具体解释之插入(C语言实现)

前面我们学习二叉搜索树的时候发如今一些情况下其高度不是非常均匀,甚至有时候会退化成一条长链,所以我们引用一些"平衡"的二叉搜索树.红黑树就是一种"平衡"的二叉搜索树,它通过在每一个结点附加颜色位和路径上的一些约束条件能够保证在最坏的情况下基本动态集合操作的时间复杂度为O(nlgn).以下会总结红黑树的性质,然后分析红黑树的插入操作,并给出一份完整代码. 先给出红黑树的结点定义: #define RED 1 #define BLACK 0 ///红黑树结点定义,与普通

【算法导论学习-015】数组中选择第i小元素(Selection in expected linear time)

1.算法思想 问题描述:从数组array中找出第i小的元素(要求array中没有重复元素的情况),这是个经典的"线性时间选择(Selection in expected linear time)"问题. 思路:算法导论215页9.2 Selection in expect linear time 2.java实现 思路:算法导论216页伪代码 /*期望为线性时间的选择算法,输入要求,array中没有重复的元素*/ public static int randomizedSelect(i

算法导论学习---红黑树详解之插入(C语言实现)

前面我们学习二叉搜索树的时候发现在一些情况下其高度不是很均匀,甚至有时候会退化成一条长链,所以我们引用一些"平衡"的二叉搜索树.红黑树就是一种"平衡"的二叉搜索树,它通过在每个结点附加颜色位和路径上的一些约束条件可以保证在最坏的情况下基本动态集合操作的时间复杂度为O(nlgn).下面会总结红黑树的性质,然后分析红黑树的插入操作,并给出一份完整代码. 先给出红黑树的结点定义: #define RED 1 #define BLACK 0 ///红黑树结点定义,与普通的二