首页 > 技术文章 > 排序算法—快速排序

mcomco 2018-12-18 15:46 原文

1.排序问题

  现有一个含有N个数字的数组S,如何通过程序把这个数组变成有序的数组?

  例如:

  排序前:S:5,3,7,5,9,4,1,100,50

  排序后:S:1,3,4,5,5,7,9,50,100

2.快速排序(Quicksort)

  简单介绍:  

  快速排序内含一道重要的工序:分区(Partition)。这里将先介绍分区,然后介绍排序方法。

分区:

  

  对于一个数组,将它的第一个元素设为V,令i=2,j为数组的最后一个元素的序号(index)。

  1.先从i开始比较,如果i项元素比V小,则i++;否则停止比较,此时的i项元素大于等于V。

  2.然后从j开始比较,如果j项元素比V大,则j--;否则停止比较,此时的i项元素小于等于V。

  3.如果i<j,说明i项元素和j项元素之间还有元素,将i项元素和j项元素互换,然后i++,j--,然后重复1,2,3步骤,直到i和j重合(即i=j)。

  4.将J项元素和V互换。这样就得到了一个分好区的数组:V的左端的元素都小于等于V;V的右端都大于等于V。

排序:

  1.将整个数组进行分区。

  2.对分区得到的两个部分数组分别进行分区,不停的分区下去,直到分区数组只有一个元素为止。

  3.排序完成。

3.快速排序的优缺点

优点:

不需要额外空间(归并排序需要一个额外数组),速度比归并排序快。

缺点:

1.如果数组是递增的有序数组,对它用快速排序需要N2/2次操作。

2.No Stable:如果数组已经按一种排序方式排成有序了,然后再按另一种排序方式用快速排序对此数组排序,则会打乱第一种排序。(例如手机通讯录先按地区排了一次序,再按名字排一次序。)PS:归并排序是Stable的。

 

4.快速排序具体代码实现:

.h:

UCLASS()
class ALGORITHM_API AQuicksort : public AActor
{
    GENERATED_BODY()
    
public:    
    // Sets default values for this actor's properties
    AQuicksort();
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    //生成数组
    void InitArray(int N);
    //把此部分数组分为两半,中间分界线为v,左半部分的数字都比v小,右半部分的数字都比v大
    int Partition(int lo, int hi);
    //更换数组里两个数字
    void ExChange(int i, int j);//开始排序
    //开始排序
    void Sort();
    void Sort( int lo, int hi);

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:    
    

private:

    TArray<int> MyIntArray;
};

.cpp:

// Sets default values
AQuicksort::AQuicksort()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void AQuicksort::BeginPlay()
{
    Super::BeginPlay();
    //测试
    //生成数组
    InitArray(10000);
    UKismetSystemLibrary::PrintString(this, "Before Sort: ");
    for (int i = 0; i < MyIntArray.Num(); i++)
    {
        UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + " : " + FString::FromInt(MyIntArray[i]));
    }
    //开始排序
    Sort();
    UKismetSystemLibrary::PrintString(this, "After Sort: ");
    for (int i = 0; i < MyIntArray.Num(); i++)
    {
        UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + " : " + FString::FromInt(MyIntArray[i]));
    }
    
}

// Called every frame
void AQuicksort::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

void AQuicksort::InitArray(int N)
{
    FRandomStream Stream;
    Stream.GenerateNewSeed();
    for (int i = 0; i < N; i++)
    {
        MyIntArray.Add(Stream.RandRange(0, 100));
    }
}

int AQuicksort::Partition(int lo, int hi)
{
    int i(lo);
    //因为后面会--j,而我们需要从hi项开始比较,所以这里j=hi+1
    int j(hi + 1);
    //只要中途不break,循环一直进行下去
    while (true)
    {
        //lo项作为比较用的元素,从lo+1开始比较,直到找到大于等于lo项的元素,才停止(除非lo项是此部分数组中最大的,此时i=hi)
        //停止时,i项元素比lo项大
        //注意:MyIntArray[++i] < MyIntArray[lo]中,先进行++i,再进行比较
        while (MyIntArray[++i] < MyIntArray[lo])
        {
            if (i == hi) break;
        }
        //从hi项开始比较,直到找到小于等于lo项的元素,才停止(除非lo项是此部分数组中最小的,此时i=lo)
        //注意:MyIntArray[lo]<MyIntArray[--j]中,先进行--j,再进行比较
        while (MyIntArray[lo]<MyIntArray[--j])
        {
            if (j == lo) break;
        }

        //如果i >= j,说明i,j重合了,不需要继续循环。此时,i项左边的元素都比lo项小;右边的元素都比lo项大
        if (i >= j) break;
        //如果i和j之间还有元素,说明还没比较完,继续比较
        ExChange(i, j);
    }
    //lo项是分界线元素,把它放到分界线处
    ExChange(lo, j);
    //返回分界线元素的Index
    return j;
}

void AQuicksort::ExChange(int i, int j)
{
    //序号i,j应该在数组范围内
    if (i > MyIntArray.Num() - 1 || j > MyIntArray.Num() - 1) return;
    //互换
    int Tempint = MyIntArray[i];
    MyIntArray[i] = MyIntArray[j];
    MyIntArray[j] = Tempint;
}

void AQuicksort::Sort()
{
    Sort(0, MyIntArray.Num() - 1);
}

void AQuicksort::Sort(int lo, int hi)
{
    if (hi <= lo) return;
    //把此部分数组分为两部分
    int j = Partition(lo, hi);
    //然后这两部分数组作为新的部分数组继续分下去,直到hi <= lo
    Sort(lo, j - 1);
    Sort(j + 1, hi);
}

 

推荐阅读