COMP0005-Notes(4): Quick Sort

7 篇文章 0 订阅

Main Idea

  1. Randomly shuffle the input array once
  2. Choose a pivot from the array and insert it into the “correct” position - where all elements to the left are smaller and all elements to the right are greater. This partitions the array into two sub-arrays.
  3. Repeat the process on the two sub-arrays.

The way to find the position for the pivot is quite unique, and ensures O(N) operations:

  1. In each iteration, we set two pointers i and j, where i travels from the head onto the tail in the array and j travels from the tail onto the head.
  2. Each time we check if a[i] is smaller than our pivot, and if not, that means a[i] needs to be in the latter half of the array instead of the former half, so we halt the increment of i;
  3. At the same time in the same loop, we decrement j to see whether a[j] is larger than our pivot, if so then carry on decrementing; otherwise this a[j] is in the wrong half of the array;
  4. As we 've been decrementing j after we finish incrementing i, it is possible that they have already passed each other, in which case no swapping is needed as they have trespassed each other’s territory;
  5. If i and j are still safe, this means that we have found a pair of values that are in the wrong position of the list, so we need to swap them.
  6. carry on the loop.

This is a smart way of dividing the workload as it uses two pointers in opposite directions.

There are recursive & non-recursive implementations for this algorithm.

  • To implement a recursive algorithm, you simply need to recursively call the partitioning function on the individual partitions, until the partition contains one element only or is simply empty. Both in-place and not-in-place solutions are possible, the difference is only that an in-place algorithm returns the head and tail index of the partitions, while a not-in-place returns a new copy of the partition, which only needs to be concatenated onto other partitions.
  • To implement a non-recursive version, you need to have a stack that holds the partitions. The initial state of the stack should contain the original input array, and loop iterates by constantly popping the top-most element (array) out of the stack, and (if the popped array contains only one number or is empty then skip, otherwise) feed it to the partition function, while the result of the function - two smaller partitions are pushed into the stack. This process iterates until the stack becomes empty.

Analysis

Complexity

In the average scenario, since we’re dividing an array into two in every iteration, ideally we have log(N) layers of recursion in total (where we can always divide in the middle or so), and in each layer, we overall visit each element exactly once (in the partition that contains it and in that partition only). Hence in each layer we make exactly N visits. Therefore the overall complexity is O(Nlog(N)).

From the discussion above, you can also see the situation which makes the algorithm degrading rapidly - that is exactly when the input array is already sorted. In this case, we are unable to divide the array into two parts of equal size. In fact, since the input array is already sorted, that means every element is already in its correct position, so each partition is only going to give us a partition that contains the element (or empty depending on implementation), and a partition of the sorted list that is exactly one element short. In this case, the algorithm will make exactly N partition calls in total, in order to completely exhaust the array. This produces an N-layer recursive call, and still in each layer we make N visits, thus the overall complexity becomes O(N * N) = O(N^2).

Benefits

  • In-place… No need for a “copy” array, so spatially efficient;
  • Fastest in most cases… Mostly temporally efficient as well.

Drawbacks

  1. Not stableNon-consecutive swaps are made instantly, so original order not maintained
  2. Not robust to duplicate keys, as pointers halt immediately when the strict inequality is not satisfied;
  3. Degrades rapidly in some rarest circumstances

Possible fix

  1. None, as it is how Quick Sort works!
  2. MUST: during partitioning, stop scan on items equal to pivot
    SHOULD: during partitioning, pull all items equal to pivot in-place (Licia Capra, COMP0005, UCL, 2022)
  3. That’s the reason for the random shuffle at the beginning of the algorithm - provides a statistical insurance (but still…).
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值