算法 - 快速排序
快排,,时间复杂度O(nlogn),重点逻辑在于选择一个基准值,然后将数组小于这个数的值去左边,大于的去右边,其核心思想还是分治法。
具体过程:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数
基准数,基准数是从当前序列随机选取的一个元素。我们可以通过以下方案来选择
以序列的第一个元素为基准数、以序列的最后一个元素为基准数、以序列的中间元素为基准数、通过随机下标的方式选取基准。
具体见代码,以及对应注释:
/// <summary>
/// 快速排序的核心思想是分治法
/// 并通过 基准数 把序列分为 左序列 和 右序列,然后分别对 左序列 和 右序列 递归地执行同样的基本步骤
/// </summary>
/// <param name="nums"></param>
/// <param name="left"></param>
/// <param name="right"></param>
public void QuickSort(int[] nums, int left, int right)
{
if (left >= right) return;
/**
* 基准数:基准数是从当前序列随机选取的一个元素。我们可以
* 以序列的第一个元素为基准数、
* 以序列的最后一个元素为基准数、
* 以序列的中间元素为基准数、
* 通过随机下标的方式选取基准
*/
// 一次排序,index左边都比对应的值小,右边比他大,index值就是比较的基准值
int index = PartSort_OneP(nums, left, right);
// 分治法,可以通过i当前步进的位置将数组再次分割!!
// 排序左边
QuickSort(nums, left, index - 1);
// 排序右边
QuickSort(nums, index + 1, right);
}
/// PartSort()函数是进行一次快排的算法
/// 对于快速排序的一次排序,有很多种算法,,,左右指针法,三数取中法,直接插入 挖坑法
/// 返回索引值
public int PartSort_Mid(int[] nums, int left, int right)
{
// 比中点元素作为比较值
int mid = left + (right - left) / 2;
// 和左交换,然后依然按照以left做比较值的算法
swap(nums, left, mid);
//return PartSort_OneP(nums, left, right);
return PartSort_LeftRight(nums, left, right);
}
/// <summary>
/// 单指针比较
/// </summary>
/// <param name="nums"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public int PartSort_OneP(int[] nums, int left, int right)
{
/**
* [3,2,1,5,6,0,4]
*
* 第一轮比如以3作为比较值,pivotIndex = 0
* 2和3比较,2小,那么将2放到0的位置,此时变为 [3,2,1,5,6,0,4],同时更新pivotIndex = 1
* 1和3比较,,,,,此时变为 [3,2,1,5,6,0,4],同时更新pivotIndex = 2
* 5和3比较,不变,pivotIndex = 2
* 6和3比较,不变,pivotIndex = 2
* 0和3比较,此时变为 [3,2,1,0,6,5,4],pivotIndex = 3
* 4和3比较,不变,此时变为 [3,2,1,0,6,5,4],pivotIndex = 3
* 最后将 pivotIndex = 3与left=0做交换,得到[0,2,1,3,6,5,4]
*/
int pivot = nums[left]; // 以第一个作为比较值
int pivotIndex = left;
for (int i = left + 1; i <= right; i++)
{
// 小的交换到pivotIndex位置上,注意pivotIndex是>=pivot的数值且是最靠近数组左边的
if (nums[i] <= pivot)
{
pivotIndex++; // 更新索引,注意这里不同算法,更新时机不一样!!!!
swap(nums, i, pivotIndex);
}
else
{
// 大于等于没有变化
}
}
// 现在left上的值还是pivot,pivotIndex是分割点,值是小于pivot的,
// 那么两方交换,就可以保证[left, pivotIndex - 1]是小于等于pivot的,[pivotIndex + 1, right]是大于pivot的
swap(nums, pivotIndex, left);
return pivotIndex;
}
/// <summary>
/// 左右指针法
/// </summary>
/// <param name="nums"></param>
/// <param name="left"></param>
/// <param name="right"></param>
/// <returns></returns>
public int PartSort_LeftRight(int[] nums, int left, int right)
{
int pivot = nums[left]; // 以第一个作为比较值
int pivotIndex = left;
int l = left;
int r = right;
while (l < r)
{
// 从右往左找第一个小于比较元素的位置
while (l < r && nums[r] >= pivot) r--;
// 从左往右找第一个大于比较元素的位置
while (l < r && nums[l] <= pivot) l++;
// 当前nums[l]大于比较值,nums[r]小于比较值,互换,这样 l 位置保证小于比较值
if (l != r)
{
swap(nums, l, r);
}
}
// 完毕后,将 left和l 互换,这样pivot就到了原l的位置,也就是分割点位置
swap(nums, l, left);
// 返回分割点
return l;
}