1.快速排序缺陷

  快速排序面对重复的元素时的处理方法是,把它放在了左部分数组或右部分数组,下次进行分区时,还需检测它。如果需要排序的数组含有大量重复元素,则这个问题会造成性能浪费。

  解决方法:新增一个相同区域,并把重复元素放进去,下次进行分区时,不对相同区域进行分区。

2. 3区快速排序(3-way quicksort)

  从例子入手:

  

 

  现有数组A[]如上图。

  令int lt=0; int i =1; int gt=5;

  首先从A[0](53)开始  

  A[lt]与A[i]进行对比,结果:A[0]<A[1], 交换A[i]与A[gt], gt减一:

  

  A[lt]与A[i]进行对比,结果:A[0]>A[1], 交换A[i]与A[lt], lt加一,i加一:

   

  A[lt]与A[i]进行对比,结果:A[1]>A[2],交换A[i]与A[lt], lt加一,i加一:

  

  A[lt]与A[i]进行对比,结果:A[2]=A[3],i加一:

  

  A[lt]与A[i]进行对比,结果:A[1]>A[2],交换A[i]与A[lt], lt加一,i加一:

  

  i>gt,第一次排序结束。

  此时,整个数组分为3个区:

  第一个区里的所有数字都比53小,它含有a[0]~a[2];

  第二个区里的所有数字都等于53,它含有a[3]~a[4];

  第三个的所有数字都比53大,它含有a[5];

  我们还需要对第一个区和第三个区进行3区快速排序(因为这两个区里的数字可能还是乱的,虽然在本例中顺序是对的)

  从第一个区的第一个数字20开始:

  令int lt=0; int i =1; int gt=2;

  

  A[lt]与A[i]进行对比,结果:A[2]=A[3],i加一:

  

  A[lt]与A[i]进行对比,结果:A[0]<A[2], 交换A[i]与A[gt], gt减一:(这里i=gt,所以等于没交换)

   

  i>gt,第二次排序结束。

  此时,整个区分为3个区:

  第一个区里的所有数字都比20小,它没有元素;

  第二个区里的所有数字都等于20,它含有a[0]~a[1];

  第三个的所有数字都比20大,它含有a[2];

  对第一个区和第三个区进行3区快速排序,但第一个区没元素,不用排;第三个区只有一个元素,不用排。

  

  对下一个区进行3区快速排序,但此区只有A[5]一个元素,不用排;

  没有下一个区了,排序结束。

  总结一下:

  对于一个数组A[],令lt=0;i=1,gt为数组的最后一个元素的序号(index)。

  1.从A[lt]开始,如果A[lt]>A[i],交换lt项元素和i项元素,lt++,i++;如果A[lt]=A[i], i++;如果A[lt]<A[i],交换gt项元素和i项元素,gt--。

  2.当i>gt时,数组已经分好3个区域了。

  3.对大于A[lt]的元素区域和小于A[lt]的元素区域分别进行3区快速排序,直到分区数组只有一个元素为止。

 

3.实现代码

.h:

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

    //生成数组
    void InitArray(int N);
    //更换数组里两个数字
    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
AThreeWayQuicksort::AThreeWayQuicksort()
{
     // 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 AThreeWayQuicksort::BeginPlay()
{
    Super::BeginPlay();
    //测试
    //生成数组
    InitArray(1000);
    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 AThreeWayQuicksort::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
}

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

void AThreeWayQuicksort::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 AThreeWayQuicksort::Sort()
{
    Sort(0, MyIntArray.Num() - 1);
}

void AThreeWayQuicksort::Sort(int lo, int hi)
{
    if (hi <= lo) return;
    //left是小于V和等于V的分界线
    int Left(lo);
    //Right是大于V和等于V的分界线
    int Right(hi);
    int V(MyIntArray[lo]);
    //i是等于V和未排序元素的分界线
    int i(lo);
    while (i <= Right)
    {
        //如果小于V,放在Left的左边(小于V的元素区间)
        if (MyIntArray[i] < V) ExChange(Left++, i++);
        //如果大于V,放在Right的右边(大于V的元素区间)
        else if (MyIntArray[i] > V) ExChange(i, Right--);
        //如果等于V,i++,相当于放在Left和i之间(等于V的元素区间)
        else i++;
    }
    //然后这两部分数组作为新的部分数组继续分下去,直到hi <= lo
    Sort(lo, Left - 1);
    Sort(Right + 1, hi);
}