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); }