深入探究快速排序 && 随机快速排序
本篇博客将展示快速排序中两种partition的写法,同时讲解随机快速排序,并将它们进行对比
快速排序
1、第一种partition
这种partition的写法主要是确定一个基准值(一般最右侧的值),然后两个指针从两头往中间移动,,当两个指针相遇后,将基准值与指针交换,这样使得小于基准值的值在基准值的左侧,大于基准值的值在基准值的右侧。
int partition(vector<int> &q, int l, int r)
{
int x = q[r];
int i = l;
int j = r - 1;
while (i < j)
{
while (i < r && q[i] <= x)
i++;
while (j > l && q[j] >= x)
j--;
if (i < j)
swap(q[i], q[j]);
else
j--;
}
if ((i != j) || (i == j && q[i] > q[r]))
swap(q[i], q[r]);
return i;
}
2、第二种partition
这种partition是基于交换的,定义两个变量i和j分别指向当前小于基准值的数组元素,j用于遍历数组。
int partition(vector<int> &nums, int low, int high) {
int i = 0, j = 0;
for (i = low, j = low; j < high; ++j) {
if (nums[j] <= nums[high]) {
swap(nums[i++], nums[j]);
}
}
swap(nums[i], nums[j]);
return i;
}
3、快速排序主体
void quick(vector<int> &q, int l, int r)
{
if (l >= r)
return;
int part = partition(q, l, r);
quick(q,l, part - 1);
quick(q, part + 1, r);
}
随机快速排序
上面版本的快排在选取基准值的时候,都选取了最右边的元素。但是当序列为有序时,会发现划分出来的两个子序列一个里面没有元素,而另一个则只比原来少一个元素。这种情况就会成为快速排序最坏情况。为了避免这种情况,引入一个随机化量来破坏这种有序状态。
在随机化的快排里面,选取a[left…right]中的随机一个元素作为基准值,然后再进行划分,就可以得到一个平衡的划分。
下面是随机快速排序的伪代码以及c++代码,这里代码选择第一种partition的写法。
randomPartition (A, l, r)
index = random(l, r)
exchange A[index] with A[r]
return partition(A, l, r)
randomQuick (A, l, r)
if (l<r)
q = randomPartition (A, l, r)
randomQuick (A, l, q-1)
randomQuick (A, q+1, r)
int partition(vector<int> &q, int l, int r)
{
int x = q[r];
int i = l;
int j = r - 1;
while (i < j)
{
while (i < r && q[i] <= x)
i++;
while (j > l && q[j] >= x)
j--;
if (i < j)
swap(q[i], q[j]);
else
j--;
}
if ((i != j) || (i == j && q[i] > q[r]))
swap(q[i], q[r]);
return i;
}
int randomPartition(vector<int> &q, int l, int r)
{
srand((unsigned int)time(NULL));
int index = rand() % (r - l) + l;
swap(q[index], q[r]);
return partition(q, l, r);
}
void quick(vector<int> &q, int l, int r)
{
if (l >= r)
return;
int part = randomPartition(q, l, r);
cout << "After Change: ";
for (int i = 0; i < q.size(); i++)
{
cout << q[i] << " ";
}
cout << "\n" << "\n";
quick(q,l, part - 1);
quick(q, part + 1, r);
}
结果比较
1、无序数组
随机化快排因为要生成随机数,所以有一些性能损失,所以数据规模较小,数据分布均匀时普通快排还是比随机化快排要快些的,不过随着数据规模的上升,随机化快排的性能优势就展现出来了。
10w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
普通快排 | 14ms | 15ms | 14ms | 14.3ms |
随机快排 | 26ms | 30ms | 23ms | 26.3ms |
100w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
普通快排 | 104ms | 110ms | 99ms | 104.3ms |
随机快排 | 130ms | 120ms | 123ms | 124.3ms |
1000w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
普通快排 | 1390ms | 1444ms | 1373ms | 1402.3ms |
随机快排 | 1395ms | 1386ms | 1309ms | 1363.3ms |
2、有序数组
在有序数组的情况下,随机快速排序的效果就能很好地体现出来了。
10w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
普通快排 | 溢出 | 溢出 | 溢出 | 溢出 |
随机快排 | 16ms | 9ms | 12ms | 12.3ms |
1w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
普通快排 | 104ms | 123ms | 163ms | 130ms |
随机快排 | 2ms | 1ms | 1ms | 1.3ms |
1000w:
算法 | 第一次 | 第二次 | 第三次 | 平均值 |
---|---|---|---|---|
随机快排 | 740ms | 792ms | 812ms | 781.3ms |