目录
快速排序
历史:
基本思想:
主框架:
下面解释实现单次排序的几种版本:
1.Hoare版本
2. 挖坑法
3. 前后指针法
快速排序的实现包括递归与非递归:
1. 递归实现:(即开头的基本框架)
2. 非递归:(迭代)
下面我们通过栈来演示非递归实现快速排序:
快速排序
历史:
快速排序是Hoare(霍尔)于1962年提出的一种二叉树结构的交换排序方法
基本思想:
任取待排序元素序列中的一个元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后在左右子序列重复该过程,直到所有元素都排序在相应位置上为止
主框架:
上述为快速排序递归实现的主框架,我们可以发现它和二叉树的前序遍历十分相似
下面解释实现单次排序的几种版本:
1.Hoare版本
单趟动图演示:
步骤:
选择基准值:通常选择序列的第一个元素或最后一个元素作为基准值。
设置两个int变量记录数组下标:一个记录序列的开始的下标(left),另一个记录序列的末尾的下标(right)。
开始分区:
- 先让right++,直到找到一个比基准值大的元素。
- 再让left--,直到找到一个比基准值小的元素。
- 交换这两个元素的位置,并继续right++。
- 重复上述步骤,直到left >= right。
- 注意:right先加,保证下一步骤与基准值交换的值小于基准值
交换基准值与right位置的值:此时,基准值左边的所有元素都比基准值小,基准值右边的所有元素都比基准值大。
代码实现:
int PartSort1(int* a, int left, int right) {
int key = left;
while (left < right) {
while(a[right] >= a[key]&&left < right) {
right--;
}
while(a[left] <= a[key]&&left < right) {
left++;
}
swap(&a[left], &a[right]);
}
swap(&a[key], &a[right]);
return left;
}
2. 挖坑法
单趟动图演示:
步骤:
选择基准元素:在待排序的序列中,选择一个元素作为基准元素(key)。这个元素可以是序列的第一个元素、最后一个元素或者任意一个元素,甚至是随机选取的一个元素。
挖坑:将基准元素从原位置移除,形成一个“坑”(hole)。
填坑:从序列的一端开始(可以是左端或右端),将遇到的第一个比基准元素小(或大)的元素填入坑中,并形成一个新的坑。这里有两种情况:
- 如果从右向左遍历,遇到比基准元素小的元素,则将其填入坑中,并继续从左向右遍历。这里代码实现选择该情况
- 如果从左向右遍历,遇到比基准元素大的元素,则将其填入坑中,并继续从右向左遍历。
- 此时,基准元素左边的所有元素都小于基准元素,右边的所有元素都大于或等于基准元素。
代码实现:
int PartSort2(int* a, int left, int right) {
int x = a[left];
int key = left;
while (left < right) {
while (a[right] >= x && left < right) {
right--;
}
a[key] = a[right];
key = right;
while (a[left] <= x && left < right) {
left++;
}
a[key] = a[left];
key = left;
}
a[key] = x;
return key;
}
3. 前后指针法
单趟动图演示:
基本步骤:
- 从待排序的数组中选择一个元素(key)作为基准。通常选择第一个或最后一个元素作为基准,但也可以随机选择。
- 设置两个下标,一个记录序列的开始的下标(pre),即pre=left,另一个记录pre下一个元素的下标的下标(cur),即cur=pre+1;
- 当(cur<=right)时,循环执行:cur找比key小的元素,如果a[cur]<=a[key],交换a[cur]和a[pre],否则pre++,每次循环cur++
代码实现:
int PartSort3(int* a, int left, int right) {
int key = left;
int pre = left;
int cur = pre + 1;
while (cur <= right) {
if (a[cur] <= a[key] && ++pre != cur) {
swap(&a[cur], &a[pre]);
}
cur++;
}
swap(&a[key], &a[pre]);
return pre;
}
快速排序的实现包括递归与非递归:
1. 递归实现:(即开头的基本框架)
void QuickSort(int* a, int left, int right) {
if (left >= right) {
return;
}
//int key = PartSort1(a, left, right);
//int key = PartSort2(a, left, right);
int key = PartSort3(a, left, right);
QuickSort(a, left, key - 1);
QuickSort(a, key + 1, right);
}
2. 非递归:(迭代)
在非递归版本中,我们使用栈(或队列)来保存待排序的子数组的起始和结束索引,而不是直接递归调用;栈相当于前序遍历,而队列相当于层序遍历
下面我们通过栈来演示非递归实现快速排序:
思路与步骤:
循环处理栈:当栈不为空时,执行以下步骤:
- 弹出栈顶的两个元素,它们分别表示当前子数组的起始和结束索引。
- 如果起始索引等于结束索引(即子数组只有一个元素),那么直接跳过,不需要排序。
- 调用
PartSort3
函数对当前子数组进行划分,并返回划分后的基准索引key
。- 弹出之前压入的起始和结束索引(因为已经处理过这个子数组了)。
- 根据
key
的位置,决定下一步的操作:
- 如果
key
等于起始索引left
,说明基准左边的元素都已经排好序,只需要对基准右边的元素进行排序,所以将key + 1
和right
压入栈中。- 如果
key
等于结束索引right
,说明基准右边的元素都已经排好序,只需要对基准左边的元素进行排序,所以将left
和key - 1
压入栈中。- 如果
key
既不等于left
也不等于right
,说明基准左右两边都有需要排序的元素,所以将left
到key-1
和key+1
到right
的子数组分别压入栈中- 2.完成:当栈为空时,表示所有子数组都已排序完成,此时整个数组也已排序完成。
代码实现:
typedef int StackNode;
typedef struct Stack {
StackNode* a;
int real;
int capacity;
}Stack;
void InitStack(Stack* p) {
assert(p);
p->a = NULL;
p->capacity = p->real = 0;
}
//扩容
void expansion(Stack*p) {
int newcapacity = p->capacity == 0 ? 4 : 2 * p->capacity;
StackNode* tmp = (StackNode*)realloc(p->a, sizeof(StackNode) * newcapacity);
if (tmp == NULL) {
perror("expansion::realloc::NULL");
exit(0);
}
p->a = tmp;
p->capacity = newcapacity;
}
//入栈
void Push(Stack*p,int x) {
assert(p);
if (p->capacity == p->real) {
expansion(p);
}
p->a[p->real++] = x;
}
//出栈
void Pop(Stack* p) {
assert(p);
p->real--;
}
void StackDestroy(Stack* p) {
assert(p);
free(p->a);
p->a = NULL;
p->capacity = p->real = 0;
}
//快排代码实现
void QuickSortNonR(int* a, int left, int right) {
if (left >= right) {
return;
}
Stack stack;
InitStack(&stack);
Push(&stack, left);
Push(&stack, right);
while (stack.real) {
left = stack.a[stack.real - 2];
right = stack.a[stack.real - 1];
if (left == right) {
Pop(&stack);
Pop(&stack);
continue;
}
int key = PartSort3(a, left, right);
Pop(&stack);
Pop(&stack);
if (key == left) {
Push(&stack, key + 1);
Push(&stack, right);
continue;
}
if (key == right) {
Push(&stack, left);
Push(&stack, key - 1);
continue;
}
Push(&stack, key + 1);
Push(&stack, right);
Push(&stack, left);
Push(&stack, key - 1);
}
StackDestroy(&stack);
}