快速排序堆栈溢出
在使用VS写快排的时候,发现了以下错误:
而且堆栈溢出这种错误不能被捕捉,真是令人头大。  ̄へ ̄
挖坑法
看一下代码,使用的是挖坑法。
public void QuickSort_v1(ref int[] data,int Low, int High)
{
if (Low >= High)
{
return;
}
int pivot = data[Low]; //使用data[Low]作为枢纽元素
int i = Low;
int j = High;
while (i < j)
{
while (data[j] >= pivot && i < j)
{
j--;
}
data[i] = data[j]; //比枢纽小的向前移动
while (data[i] <= pivot && i < j)
{
i++;
}
data[j] = data[i]; //比枢纽大的向后移动
}
data[i] = pivot;
QuickSort_v1(ref data,Low, i - 1); //左区间快排
QuickSort_v1(ref data,i + 1, High); //右区间快排
}
这个方法拿来普通排序没有问题,但是数量级稍微上升就有溢出的风险,当然这个得看脸,不幸的我一次中了。
不难看出,以上代码十分头铁,枢纽总是选择第一个数。如果测试的数据分布不均匀,左右区间分布不均匀,一个区间递归次数过多就会堆栈溢出,例如我把十万降序数,十万重复数和十万升序数分别带入,毫无例外全部暴毙。(ಥ_ಥ)
当然解决办法还是有的:
- 改进枢纽选择:进行三数选中,即比较首位、末位和中间位,选取一个中间值作为枢纽。(无法解决重复数)
- 在三数选中的同时,对重复数进行聚集:(解决溢出问题)
public void QuickSort_v3(ref int[] data,int Low, int High)
{
if (Low >= High)
{
return;
}
//使用较为中间的数作为枢纽
DealArray(Low, High);//作用:比较首位末位和中间位,选出中间大小的值与首位交换
//到此可认为较为接近中间的值在第一位
int i, j;
int pivot; //使用data[Low]作为枢纽元素
int pLow, pHigh; //pLow与pHigh是记录枢纽两边 与枢纽值相同 的数量
pivot = data[Low];
i = Low;
pLow = 0;
j = High;
pHigh = 0;
while (i < j)
{
while (data[j] >= pivot && i < j)
{
if (data[j] == pivot)
{
//将与枢纽相同的值移动到一边
Swap(ref data,j, High - pHigh);//作用:交换两边的值
pHigh++;
}
j--;
}
data[i] = data[j]; //比枢纽小的向前移动
while (data[i] <= pivot && i < j)
{
if (data[i] == pivot)
{
Swap(ref data,i, Low + pLow);
pLow++;
}
i++;
}
data[j] = data[i]; //比枢纽大的向后移动
}
data[i] = pivot;
//把与枢轴相同的元素聚集起来
//将相同的值移动到中间
for (int m = 0; m < pHigh; m++)
{
Swap( ref data, High - m, i + 1 + m);
}
for (int n = 0; n < pLow; n++)
{
Swap( ref data , Low + n, i - 1 - n);
}
QuickSort_v3(ref data ,Low, i - 1 - pLow); //左区间快排
QuickSort_v3( ref data ,i + 1 + pHigh, High); //右区间快排
}
我的比较次数可以突破天际!
这种方法在挖坑的思想上,解决了堆栈溢出问题,但是由于能力不足,我写出来总是比较麻烦和不爽。
左右指针法
其实利用左右指针法的思想,能够比较好的解决这个问题
private void QuickSort_v4(ref int[] data,int low, int high)
{
int i = low, j = high;
int pivot = data[low + (high - low) / 2];
while (i <= j)
{
while (data[i] < pivot)
{
i++;
}
while (data[j] > pivot)
{
j--;
}
if (i <= j)
{
Swap(ref data ,i, j);
i++;
j--;
}//同时加减,能够使大多数情况使枢纽考中,减少递归次数
}
if (low < j)
QuickSort_v4(ref data,low, j);
if (i < high)
QuickSort_v4(ref data,i, high);
}
这种方法能够很好的平衡左右区间,就不会那么容易溢出。
其他
快排也可以和其他排序算法相结合去解决这类问题。
这里再标记一些其他大佬的文章:
https://blog.youkuaiyun.com/qq_36528114/article/details/78667034 ( 快速排序(三种算法实现和非递归实现))
https://blog.youkuaiyun.com/insistgogo/article/details/7785038 (三种快速排序以及快速排序的优化)
水平有限,请多包涵✧(≖ ◡ ≖✿