1. Simple Quick Sort

Quicksort is a simple, easy-to-understand sorting algorithm, let's take a look at an entry-level quicksort:

 function QuickSort(nums){
    if(nums.length <= 1){
        return nums
    }
    let pivot = nums[random(0, nums.length - 1)]
    let left  = []
    let right = []
    let mid   = []
    for (let i = 0; i < nums.length; i++) {
        if(nums[i] < pivot){
            left.push(nums[i])
        }else if(nums[i] == pivot){
            mid.push(nums[i])
        }else{
            right.push(nums[i])
        }
    }
    return QuickSort(left).concat(mid, QuickSort(right))
}

The advantage of this way of writing is that the logic is very clear and easy to understand; the disadvantage is that each recursion generates a new array, and finally merges the arrays, and the space complexity is slightly higher

2. In-situ sorting Partition algorithm

The Partition algorithm is an in-situ segmentation and sorting algorithm. It selects a reference value, swaps each value smaller than the reference value to the left, and swaps each value larger than the reference value to the right, and finally the reference value is centered. Algorithm for ground division, no extra space is used except for constant-level variables

2.1 Public functions

Let's first define a few commonly used public functions

 //数组交换
function swap(nums, i ,j){
    tmp = nums[j]
    nums[j] = nums[i]
    nums[i] = tmp
}
//范围内随机数
function random(minNum,maxNum){
    return parseInt(Math.random()*(maxNum-minNum+1)+minNum,10)
}

2.2 One traversal from left to right - Partition algorithm

 function Partition(nums, l = 0, r = nums.length - 1) {
    //随机选取一个作为基值值
    let random_i   = random(l, r)
    let pivot      = nums[random_i]
    swap(nums, l, random_i)
    let pos        = l
    for (let i = l + 1; i <= r; i++) {
        if(nums[i] <= pivot){
            pos++
            if(i != pos){
                swap(nums, i, pos)
            }
        }
    }
    swap(nums, l, pos)
    return pos
}

pos is the dividing line, the right side of pos (including itself) is less than or equal to the reference value, and the right side is greater than the reference value

2.3 Double Pointer-Partition Algorithm

 function Partition(nums, l = 0, r = nums.length - 1)
{
    let pivot = nums[l]
    while(l < r)
    {
        while(l < r && nums[r] >= pivot){
            r--
        }
        nums[l] = nums[r]
        while(l < r && nums[l] <= pivot){
            l++
        }
        nums[r] = nums[l]
    }
    nums[begin] = pivot;
    return begin;
}

The algorithm of double pointers is not easy to understand. The subtlety is that the exchange function is not used. The reference value is temporarily stored through pivot, and then the reference value and the relationship between the left and right endpoints are used to complete the segmentation. If you are interested, you can hit a breakpoint and run it.

3. Quicksort using Partition algorithm

 function QuickSort(nums, l = 0, r = nums.length - 1){
    if(r - l <= 0){
        return nums
    }
    let pos = Partition(nums, l, r)
    QuickSort(nums, l, pos - 1)
    QuickSort(nums, pos + 1, r)
    return nums
}

Using the quick sort of the Partition algorithm, no new array is created, the original array is swapped, and the sorting is completed

4. Three-Partition Algorithm

The Three-Partition algorithm is an extension of the Partition algorithm. It divides the unordered array into three parts, which are smaller than the reference value, equal to the reference value, and larger than the reference value. It is very suitable for array segmentation and sorting with a lot of repeated data.

4.1 Three-Partition Algorithm to Determine Left and Right Boundaries

 function ThreePartition(nums, l = 0, r = nums.length - 1) {
    //随机选取一个作为基准值
    let mid        = random(l, r)
    let pivot      = nums[mid]    
    let p = l//这里的p就是左边界,p(含p)左边都是小于基准值的
    for (let i = l; i <= r; i++) {
        while(i <= r && nums[i] > pivot){
            swap(nums, i, r)//这里的r就是右边界,r右边都是大于基准值的
            r--            
        }
        if(nums[i] < pivot){
            swap(nums, i, p)
            p++           
        }
    }    
    return [p,r]
}

I think this algorithm is easier to understand than 4.2确定左中边界的Three-Partition算法 . When encountering each larger than the reference value, it will be swapped to the right, and at the same time, the right border r-- ; if it is smaller than the reference value, it will be Swap to the left, while the left border p++ is more in line with common thinking.

4.2 Three-Partition Algorithm for Determining the Left Middle Boundary

 function ThreePartition(nums, l = 0, r = nums.length - 1) {
    //随机选取一个作为基准值
    let mid        = random(l, r)
    let pivot      = nums[mid]
    
    let p0 = p1 = l//p0 0的最右边界   //p1中间值的最右边界
    for (let i = l; i <= r; i++) {
        if(nums[i] < pivot){
            swap(nums, i, p0)
            //因为首先是连续的左值+连续的基准值+连续的右值
            //如果p1 > p0则会把一个基准值交换到了i,这不是我们期望的
            //这时候我们需要把i和基准值的右边界p1交换
            if(p0 < p1){
                swap(nums, i, p1)
            }
            p0++
            p1++
        }else if(nums[i] == pivot){
            swap(nums, i, p1)
            p1++
        }
    }
    return [p0,p1-1]
}

This algorithm is based on determining the left-middle boundary. Each time, the left boundary is exchanged to the left boundary, and the reference value is exchanged to the right boundary of the reference value. However, when the right boundary of the reference value p1 grows too fast, If it exceeds p0 , then it is necessary to i and the right boundary of the reference value p1

5 Quicksort using the Three-Partition algorithm

 function QuickSort(nums, l = 0, r = nums.length - 1){
    if(r - l <= 0){
        return nums
    }
    let pos = ThreePartition(nums, l, r)
    QuickSort(nums, l, pos - 1)
    QuickSort(nums, pos + 1, r)
    return nums
}

If there are no duplicate values in the unordered array, it is completely equivalent to the Partition algorithm. If there are duplicate values in the array, the more repetitions, the more concentrated the distribution, and the higher the efficiency of this algorithm.

6. Use the Partition algorithm to solve the problem TopK

 function findKthNumber(nums, k){
    let l = 0;
    let r = nums.length - 1;
    while (l <= r){
        let pos = Partition(nums, l ,r)
        let r_len = r - pos
        let l_len = pos
        if(k == r_len + 1){
            return nums[pos]
        }else if(k <= r_len){
            l = pos + 1
        }else{
            r = pos - 1
            k -= (r_len + 1)
        }
    }
    return 0
 }

Efficiency is still very efficient

image.png

7. Solve the problem using the Three-Partition algorithm TopK

 function findKthNumber(nums, k){
    let l = 0;
    let r = nums.length - 1;
    while (l <= r){
        let pos = Partition(nums, l ,r)
        let r_len = r - pos
        let l_len = pos
        if(k == r_len + 1){
            return nums[pos]
        }else if(k <= r_len){
            l = pos + 1
        }else{
            r = pos - 1
            k -= (r_len + 1)
        }
    }
    return 0
 }

The efficiency of the Three-Partition algorithm is also very efficient. The more convergent and concentrated the distribution, the better the effect.

image.png


tfzh
231 声望17 粉丝

code what u love & love what u code