快速排序对单趟排序的处理
当谈到快速排序算法时,我们通常会提到单趟排序的实现方法。在本文中,我们将介绍三种常见的单趟排序实现方法:Hoare写法、挖坑法和快慢指针法。这些方法在快速排序中起到关键作用,帮助我们高效地分割序列并找到基准元素的正确位置
三数取中定基准值
三数取中是快速排序中的一种优化技巧,用于选择基准元素。在快速排序中,选择一个合适的基准元素对排序的效率具有重要影响。而三数取中的作用就是确保选取的基准元素尽可能接近序列的中间值,以提高排序的效率。
使用三数取中的方法,我们从待排序序列中选择三个元素:首元素、中间元素和尾元素。然后取这三个元素的中间值作为基准元素。这样做的目的是尽量选择一个接近序列中间值的元素作为基准,以提高分割的效果。
当我们用三数取中确定了基准值后,需要将选出的基准值放到待排序数据的开始或者末尾,这样方便我们进行排序,同时如果选取的基准值放在开始,则我们的排序要从末尾开始排序,放在末尾就从数据开始进行排序
三数取中的作用主要体现在以下几个方面:
- 避免最坏情况的发生:快速排序的效率受到基准元素的选择影响。如果每次都选择序列的最小或最大值作为基准,那么快速排序将退化为最坏情况,时间复杂度将达到 O ( N 2 ) O(N^2) O(N2)。通过使用三数取中的方法选择基准元素,可以避免最坏情况的发生,提高排序的效率。
- 增加基准元素的准确性:选取合适的基准元素可以尽可能接近序列的中间值,使得分割的效果更好。通过三数取中的方法选择基准元素,可以增加基准元素的准确性,使得分割更加均衡,减少了递归的深度,提高了排序的效率。
- 提高算法的稳定性:在某些情况下,序列中可能存在大量的重复元素。如果每次选择的基准元素都是相同的,会导致分割不均衡,进而影响排序的效率。通过使用三数取中的方法,可以增加基准元素的随机性,减少了重复元素对排序的影响,提高了算法的稳定性。
综上所述,三数取中的作用是确保选取的基准元素尽可能接近序列的中间值,以提高快速排序的效率、避免最坏情况的发生、增加基准元素的准确性和提高算法的稳定性。
int GetMidNumi(int* a,int left,int right)
{
int mid = (left + right) / 2;
if (a[left] < a[right])
{
if (a[mid] > a[right])
return right;
else if (a[mid] < a[left])
return left;
else
return mid;
}
else
{
if (a[mid] > a[left])
return left;
else if (a[right] > a[mid])
return right;
else
return mid;
}
}
单趟排序的三种常用方法
Hoare写法
Hoare写法是快速排序最早提出的实现方法之一,由英国计算机科学家Tony Hoare于1960年提出。它的思想是选择序列的第一个元素作为基准元素,然后通过两个指针i和j分别从序列的左右两端开始向中间移动。
- 选取基准元素
a[keyi]
,即序列的第一个元素 - 通过移动指针i和j,使得
a[i] <= a[keyi]
,a[j] >= a[keyi]
,同时i < j
- 当i和j相遇时,将基准元素
a[keyi]
与a[j]
交换位置 - 分割序列为两部分,左边的元素都小于等于基准元素,右边的元素都大于等于基准元素
- 使用递归对左右两部分进行快速排序
int Separate1(int* a, int left, int right)
{
int keyi = GetMidNumi(a, left, right);
Swap(&a[keyi], &a[left]);
while (left < right)
{
while(a[right] >= a[keyi]&& left < right)
{
right--;
}
while (a[left] <= a[keyi] && left < right)
{
left++;
}
//if (left == right) break;
Swap(&a[left], &a[right]);
}
Swap(&a[keyi], &a[left]);
return left;
}
挖坑法
挖坑法也是一种常用的单趟排序实现方法。它的思想是选择序列的第一个元素作为基准元素,通过“挖坑填数”的方式将序列分割成两部分。
- 选取基准元素
a[keyi] == key
,即序列的第一个元素 - 通过移动指针i和j,找到
a[j] < key
的元素,将a[j]
填入a[i]
的位置,此时形成一个坑 - 再通过移动指针i和j,找到
a[i] > key
的元素,将a[i]
填入上一个坑的位置,继续形成一个新的坑 - 重复上述步骤,直到 i 和 j 相遇
- 将基准元素
key
填入最后一个坑的位置 - 分割序列为两部分,左边的元素都小于等于基准元素,右边的元素都大于等于基准元素
- 递归地对左右两部分进行快速排序
int Separate2(int* a, int left, int right)
{
int keyi = GetMidNumi(a, left, right);
int key = a[keyi];
Swap(&a[keyi], &a[left]);
int hole = left;
while (left < right)
{
while (a[right] >= key && left < right)
{
right--;
}
a[hole] = a[right];
hole = right;
while (a[left] <= key && left < right)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
快慢指针法
快慢指针法是一种更为简洁的单趟排序实现方法,它通过快慢两个指针的移动来分割大于和小于基准值的部分,将大于基准值
的部分置于快慢指针之间
- 选取基准元素
a[left]
,即序列的第一个元素 - 使用快慢两个指针,分别为
slow
和fast
,初始时slow
指向基准元素a[slow]
,fast
指向基准元素下一位 - 快指针
fast
向后移动,如果a[fast]
小于基准元素,则两指针都继续向后移动;如果一旦出现a[fast]
大于等于基准元素,则只有fast++
,a[fast]
再次小于a[left]
时,将小于a[left]
的元素和a[slow]
处大于等于a[left]
的元素交换 - 重复上述步骤,直到
fast
遍历完整个序列 - 最后将基准元素
a[slow]
与a[left]
交换位置 - 分割序列为两部分,左边的元素都小于等于基准元素,右边的元素都大于等于基准元素
- 使用递归对左右两部分进行快速排序
int Separate3(int* a, int left, int right)
{
int keyi = GetMidNumi(a, left, right);
Swap(&a[keyi], &a[left]);
int slow = left;
int fast = left + 1;
while (fast <= right)
{
if (fast - 1 == slow && a[fast] < a[left])
{
slow++;
fast++;
}
else if(fast - 1 != slow && a[fast] < a[left])
{
slow++;
Swap(&a[fast], &a[slow]);
}
else
{
fast++;
}
}
Swap(&a[left], &a[slow]);
return slow;
}
这三种单趟排序的实现方法都能有效地将序列进行分割,并找到基准元素的正确位置。它们的本质思想是相同的,只是具体的实现细节有所不同
总结:
快速排序是一种高效的排序算法,通过选择基准元素和单趟排序的操作,将序列不断分割成两部分并递归排序,最终实现整个序列的排序。在单趟排序中,Hoare写法、挖坑法和快慢指针法是常用的实现方法,它们都能有效地找到基准元素的正确位置。这些方法的选择可以根据实际情况和个人喜好进行。无论使用哪种方法,快速排序都具有快速、高效的特点,是排序算法中常用的一种
希望本文对你理解快速排序的单趟排序实现方法有所帮助!