快速排序是一种交换排序,时间复杂度为O(N*logN)
,且在各种场景具有良好的适应性,但是是一种不稳定
的排序方式,排序之后相同值数据的相对位置会变化。
单趟排序
找大小交换
霍尔版本的快速排序,也是最经典的版本
- 定义key为序列第一个值的下标
- 左右指针分别找到比key小或者大的位置,并交换
- 重复寻找、交换的过程直到左右指针相遇
- 将相遇处值与key交换,一定要让右指针先走,这样key与相遇处交换时,才能保证key大于相遇处
返回值为下一趟排序key的位置,begin和end是序列起止下标
int PartSort1(int* arr, int begin, int end)// Hoare
{
int key = begin;
int left = begin, right = end;
while (left < right)
{
while (arr[right] >= arr[key] && left < right)// 一定要右边先走,找比key小的
{
right--;
}
while (arr[left] <= arr[key] && left < right)// 左边找比key大的
{
left++;
}
Change(&arr[left], &arr[right]);// 分别找到之后交换
}
Change(&arr[left], &arr[key]);
key = left;
return key;
}
挖坑法
和霍尔版本差不多,但是在思路上进行了优化
- 定义key为序列第一个值
- 把key的位置拿出来形成一个hole
- 右指针先走,找到比key小的,把该位置的值拿出来填到hole处,该处成为新的hole
- 左指针找到比key大的,把该位置的值拿出来填到hole处,该处成为新的hole
- 不断重复直到左右指针相遇
- 相遇处用最开始的key填充
返回值为下一趟排序key的下标
int PartSort2(int* arr, int begin, int end)// 挖坑法
{
int key = arr[begin];
int hole = begin;
int left = begin, right = end;
while (left < right)
{
while (arr[right] >= key && left < right)
{
right--;
}
arr[hole] = arr[right];
hole = right;
while (arr[left] <= key && left < right)
{
left++;
}
arr[hole] = arr[left];
hole = left;
}
arr[left] = key;
return left;
}
前后指针法
利用两个指针,与以上排序方式有本质上的区别
- 定义key和prev为序列第一个值的下标,cur为第二个值的下标
- cur先走,遇到比key小的值的时候停下,prev++并且二者的值交换,cur++
- 直到cur越界,交换prev和key的值
返回值为下一趟排序key的位置
int PartSort3(int* arr, int begin, int end)// 前后指针法
{
int key = begin;
int prev = begin;
int cur = prev + 1;
while (cur <= end)
{
while (arr[cur] >= arr[key] && cur <= end)
{
cur++;
}
if (cur <= end)
{
prev++;
Change(&arr[cur], &arr[prev]);
cur++;
}
}
Change(&arr[prev], &arr[key]);
return prev;
}
递归实现
递归方式与二叉树前序遍历类似
void QuickSort(int* arr, int begin, int end)
{
if (begin >= end)// 只有一个数或者非法区间就不用再排序了
return;
int mid = Div(arr, begin, end); //优化方式:三数取中
Change(&arr[mid], &arr[begin]);
int key = begin;// 选取key
key = PartSort3(arr, begin, end);// 单趟排序,划分区间为[begin, key - 1], key, [key + 1, end]
QuickSort(arr, begin, key - 1);// 递归左区间
QuickSort(arr, key + 1, end);// 递归右区间
}
非递归实现
非递归需要利用数据结构中的栈来模拟这个递归子区间的过程(栈先进后出,模拟二叉树的前序遍历)
void QuickSortNonR(int* arr, int left, int right)
{
Stack s;
StackInit(&s);
int mid = Div(arr, left, right);
Change(&arr[mid], &arr[left]);
int key = left;
StackPush(&s, left);
StackPush(&s, right);
while (StackSize(&s))
{
if (left >= right)
break;
int right = StackTop(&s);
StackPop(&s);
int left = StackTop(&s);
StackPop(&s);
key = PartSort1(arr, left, right);// 单趟排序划分区间
if (left < key - 1)
{
StackPush(&s, left); // 区间[left, key - 1]
StackPush(&s, key - 1);
}
if (key + 1 < right)
{
StackPush(&s, key + 1); // 区间[key + 1, right]
StackPush(&s, right);
}
}
StackDestroy(&s);
}
用到的栈操作函数定义补全
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
Stack ST;
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = -1;//top指向栈顶元素
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->_capacity == ps->_top + 1)
{
int newcapacity = ps->_capacity == 0 ? sizeof(STDataType) : ps->_capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));
ps->_capacity = newcapacity;
if (tmp == NULL)
{
perror("malloc fail");
return;
}
ps->_a = tmp;
}
ps->_a[ps->_top + 1] = data;
ps->_top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_a);
ps->_top--;
ps->_capacity--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_a);
STDataType top = ps->_a[ps->_top];
return top;
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top + 1;
}
void StackDestroy(Stack* ps)
{
free(ps->_a);
ps->_a = NULL;
ps->_top = -1;
ps->_capacity = 0;
}
优化方式:三数取中
取begin
, (begin + end) / 2
, end
处的值比较,返回的mid就是中间大小的下标。
把mid处的数与begin对调,使得key为中间数,能避免极端情况出现(key为序列中的最值),提高程序效率。
int Div(int* arr, int begin, int end)
{
int mid = (begin + end) / 2;
if (arr[begin] >= arr[mid])
{
if (arr[mid] >= arr[end])
{
return mid;
}
else if(arr[begin] >= arr[end])
{
return end;
}
else
{
return begin;
}
}
else//arr[begin] < arr[mid]
{
if (arr[mid] <= arr[end])
{
return mid;
}
else if (arr[begin] >= arr[end])
{
return begin;
}
else
{
return end;
}
}
}