快速排序,时间复杂度为O(NlogN),属于排序中相对快的那一列,以下是快排的模拟实现:
法一:左右指针交换法
void swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}//交换函数
int getmid(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 mid;
}
else
{
return left;
}
}
}
else //left大于right
{
if (a[mid] < a[right])
{
return right;
}
else
{
if (a[mid] < a[left])
{
return mid;
}
else
{
return left;
}
}
}
}//三数取中
int PartSort1(int* a, int left, int right)
{
int mid = getmid(a, left, right);
int keyi = left;
while (left < right)
{
while (left < right && a[right]>=a[keyi])
{
right--;
}
while (left < right && a[left] <=a[keyi])
{
left++;
}
swap(&a[left], &a[right]);
}
swap(&a[keyi], &a[left]);
return left;
}
void sort(int* arr, int left, int right)
{
if (left < right)
{
int mid = PartSort1(arr, left, right);
sort(arr, left, mid - 1);
sort(arr, mid + 1, right);
}
}
本思路是以left下标所指的元素为分界线,left指针向右移动找比分界元素大的,right指针向左移动找比分解元素小的,然后两者交换。通过这样循环,使得左侧的元素都是比分界元素小的,右侧元素都是比分界元素大的,且此时分界元素已经放在了排好序后的正确位置上。再对分界元素左侧、右侧调用同样的方法,最后就能实现排序。三数取中是一种优化方式,使得left下标所指向的元素更加贴近当前left到righ范围内的中间值。最后之所以用left指向位置和keyi指向位置进行交换,是因为left此时所指元素一定小于等于keyi所指向的元素。下面会证明为什么left此时所指元素一定小于等于keyi所指向的元素:
当循环停止以后,循环前的最后一次移动分两种情况,一种是right指针左移动碰到left,一种是left指针右移动碰到right。对于第一种情况,此时left指针所指的位置是前面left和right所指元素交换后的,所以此时left所指元素一定是小于keyi的。对于第二种情况,此时right指针已经结束移动,所指向的位置也是小于keyi的元素。所以left此时所指元素一定小于等于keyi所指向的元素。当然,如果先移动left指针再移动right指针,就不能符合上述结论。
法二:挖坑法
// 挖坑法
int PartSort2(int* a, int left, int right)
{
int mid = getmid(a, left, right);
swap(&a[left], &a[mid]);
//三数取中
int key = a[left];
int hole = left;
while (left < right)
{
while (left < right && a[right] >= key)
{
right--;
}
a[hole] = a[right];
hole = right;
while (left < right && a[left] <= key)
{
left++;
}
a[hole] = a[left];
hole = left;
}
a[hole] = key;
return hole;
}
void sort(int* arr, int left, int right)
{
if (left < right)
{
int mid = PartSort2(arr, left, right);
sort(arr, left, mid - 1);
sort(arr, mid + 1, right);
}
}
该方法的思路是先把left所指的位置视为坑,保存最左的元素到可以中 ,此后利用right指针和left指针移动找元素填坑和创建新坑。此时整个数组中一直保持着有一个下标指向坑,最后再将最开始保存的key放入坑中,形成完整的数组。left和right指针的移动规则仍然是left找比key大的,right找比key小的。
法三:前后指针法
int PartSort3(int* a, int left, int right)
{
int prev = left;
int cur = left+1;
while (cur <= right)
{
while(cur <= right&&a[cur] <= a[left])
{
prev++;
swap(&a[cur], &a[prev]);
cur++;
}
if (cur > right)
{
break;
}
while (cur <= right && a[cur] > a[left])
{
cur++;
}
if (cur > right)
{
break;
}
}
swap(&a[prev], &a[left]);
return prev;
}
void sort(int* arr, int left, int right)
{
if (left < right)
{
int mid = PartSort2(arr, left, right);
sort(arr, left, mid - 1);
sort(arr, mid + 1, right);
}
}
思路是让prev的下一位元素是比a[left]大的元素,cur所指的元素是比a[left]小的元素,然后交换,使得小于a[left]的元素往左移动,比a[left]大的元素不断往右移动。