Partition算法以及其应用详解下(Golang实现)

接前文,除了广泛使用在快速排序中。Partition算法还可以很容易的实现在无序序列中使用O(n)的时间复杂度查找kth(第k大(小)的数)。

同样根据二分的思想,每完成一次Partition我们可以轻松的知道该位置前面有几个比自己小的数,后面有几个比自己大的数(或逆序相反)。所以也能知道自己是第几大或者小的数。

查找kth(大/小)

func partition(left int, right int, arr []int) (int) {
    i := left
    j := right
    pivot := arr[left]
    for i != j {
        for i < j && arr[j] >= pivot {
            j--
        }

        for i < j && arr[i] <= pivot {
            i++
        }

        if i < j{
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    arr[left], arr[i] = arr[i], arr[left]
    return i
}

func findKthSmallestNumber(arr[]int, k int) (int) {
    left := 0
    right := len(arr)-1
    targetNumber := 0
    for left <= right {
        pos := partition(left, right, arr)
        if pos == k-1 {
            targetNumber = arr[pos]
            break
        } else if pos > k-1 {
            right = pos - 1
        } else {
            left = pos + 1
        }
    }
    return targetNumber
}

func main() {
    list := []int{4, 3, 1, 4, 5, 6, 3}
    fmt.Println(findKthSmallestNumber(list, 6))
}

逻辑是每次partition回传的位置,都是分割好的位置。

那么我们假设默认pivot设置的数总是操作数组(假如是按照正序排列,即比pivot大的数放右边,比pivot小的数放左边)的第一个数。分割完毕后,我们拿着回传的pivot位置同要寻找的k小的数做比较。

如果 pivot = k-1 那么说明这就是我们要找的那个位置,直接返回即可。pivot返回的是索引位置,比如我们要找第二小的数,索引位置就应该是1。

如果 pivot > k-1 那么说明我们要找的数在pivot的左边,这时我们需要将right置为pos索引-1

如果 pivot < k-1 那么说明我们要找的数载pivot的右边,这时我们需要将left的值置为pos索引+1

另外特别要注意的一点就是边界的问题。由于上面我使用的例子中right就是传递的索引的终点为之,所以left是有可能等于right的情况的,这时要让程序进入循环正确退出。如果你使用的是len数量版本的partition算法,就不需要这样做。

Dutch national flag problem:

荷兰国旗问题,同样是经典的Partition算法的问题。通过一次扫描来进行归类,依然是他核心思想。解决这个问题我们除了要同时使用头部指针,尾部指针以外。还需要使用一个当前位置的指针来扫描。

func threeWayPartition(list []int, target int) {
    var smallestPos, scanPos int
    biggestPos := len(list)-1

    for scanPos <= biggestPos {
        if list[scanPos] < target {
            list[scanPos], list[smallestPos] = list[smallestPos], list[scanPos]
            scanPos++
            smallestPos++
        } else if list[scanPos] > target {
            list[scanPos], list[biggestPos] = list[biggestPos], list[scanPos]
            biggestPos--
        } else {
            scanPos ++
        }
    }
}

首先扫描的当前位置只能小于和等于指向最大指针的位置。因为我们总是使用scanPos位置上的数来进行判断的,如果这里是小于而不是小于等于的话就意味着指向最大值的指针位置所在的值miss了。

说明这个之后,其他的就可以分为三种情况。

如果scanPos > target的话 就会跟后面指向大值的指针交换,然后大值指针往后退一

如果scanPos < target的话 就会跟前面指向小值的指针交换,然后当前指针位置和指向最小值的指针同进一

如果scanPos = target的话 当前指针继续往前,指向小值的指针会原地不动。

用心感受一下,其实还是蛮简单的。。

 

原文地址:https://www.cnblogs.com/piperck/p/6656985.html