一、栈的实现
1. Stack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <time.h>
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int capacity;
int top;
}ST;
void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
2. Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void STInit(ST* ps) {
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void STDestroy(ST* ps) {
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void STPush(ST* ps, STDataType x) {
assert(ps);
// 空间不够,扩容
if (ps->top == ps->capacity) {
int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
if (tmp == NULL) {
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void STPop(ST* ps) {
assert(ps);
assert(!(STEmpty(ps)));
ps->top--;
}
STDataType STTop(ST* ps) {
assert(ps);
assert(!(STEmpty(ps)));
return ps->a[ps->top - 1];
}
int STSize(ST* ps) {
assert(ps);
return ps->top;
}
bool STEmpty(ST* ps) {
assert(ps);
return ps->top == 0;
}
二、辅助函数的实现
1. Swap 交换
void Swap(int* a, int* b) {
int tmp = *b;
*b = *a;
*a = tmp;
}
2. MedianOfThree 三数取中
int MedianOfThree(int* a, int left, int right) {
printf("\n调用三数取中\n");
int mid = (left + right) / 2;
// 确保a[mid]在中间
if (a[left] > a[mid])
Swap(&a[left], &a[mid]);
if (a[left] > a[right])
Swap(&a[left], &a[right]);
if (a[mid] > a[right])
Swap(&a[mid], &a[right]);
Swap(&a[mid], &a[left]);
printf("三数取中完成\n");
return left;
}
3. InsertSort 插入排序
// 插入排序(用于处理小规模数组)
void InsertSort(int* a, int left, int right) {
printf("\n调用插入排序\n");
for (int i = left + 1; i <= right; i++) {
int key = a[i];
int j = i - 1;
while (j >= left && a[j] > key) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = key;
}
printf("插入排序完成\n");
}
4. CreateRandomArr 创建随机数组,FreeArr 释放数组资源
int* CreateRandomArr(int length, int min, int max) {
int* a = malloc(length * sizeof(int));
if (a == NULL) {
perror("malloc fail");
exit(EXIT_FAILURE);
}
srand(time(NULL));
for (int i = 0; i < length; i++) {
a[i] = rand() % (max - min + 1) + min;
}
return a;
}
void FreeArr(int* a) {
free(a);
}
三、非递归版快速排序实现(用栈实现)
void QuickSortNonR(int* a, int left, int right) {
ST stack;
STInit(&stack);
STPush(&stack, left);
STPush(&stack, right);
while (!STEmpty(&stack)) {
right = STTop(&stack);
STPop(&stack);
left = STTop(&stack);
STPop(&stack);
if (right - left <= 10) {
InsertSort(a, left, right);
continue;
}
int keyi = MedianOfThree(a, left, right);
int prev = left;
int cur = left + 1;
while (cur <= right) {
if (a[cur] < a[keyi] && ++prev != cur) {
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[keyi], &a[prev]);
keyi = prev;
// 如果我们先将左半部分入栈,后将右半部分入栈,
// 那么在出栈时,就会先处理右半部分,后处理左半部分,
// 与递归调用的顺序不一致,这可能会导致程序逻辑上的混乱,增加调试的难度。
// 将右半部分入栈
if (keyi + 1 < right) {
STPush(&stack, keyi + 1);
STPush(&stack, right);
}
// 将左半部分入栈
if (left < keyi - 1) {
STPush(&stack, left);
STPush(&stack, keyi - 1);
}
}
STDestroy(&stack);
}
首先,让我们回顾一下快速排序的基本步骤:
1. 选择一个 key 元素。
2. 将数组分成两部分:一部分包含所有小于 key 元素的值,另一部分包含所有大于或等于 key 元素的值。
3. 重复对这两部分数组执行步骤1和2,直到每个子数组只有一个元素。
在递归实现中,我们会使用函数自身来处理子数组。但在非递归实现中,我们将使用一个栈来模拟这个过程。
void QuickSortNonR(int* a, int left, int right) {
ST stack;
STInit(&stack);
STPush(&stack, left);
STPush(&stack, right);
while (!STEmpty(&stack)) {
// ...
}
STDestroy(&stack);
}
首先,我们创建了一个栈 stack,并使用 STInit 函数初始化它。然后,我们将数组的左右边界 left 和 right 压入栈中。接下来,我们进入一个循环,只要栈不为空,我们就从栈中弹出左右边界,并对这部分数组进行划分。
right = STTop(&stack);
STPop(&stack);
left = STTop(&stack);
STPop(&stack);
这一步我们从栈中弹出右边界和左边界。因为我们首先压入的是左边界,按照栈的“后进先出”,所以先弹出右边界,然后是左边界。
if (right - left <= 10) {
InsertSort(a, left, right);
continue;
}
如果子数组的大小小于或等于10,我们使用插入排序来排序这个子数组。这是因为对于小数组,插入排序通常比快速排序更高效。排序完成后,我们继续循环的下一轮。
int keyi = MedianOfThree(a, left, right);
我们使用 MedianOfThree 函数来选择一个枢纽元素,并得到它的索引 keyi ,这个 key 元素将用于划分数组。
int prev = left;
int cur = left + 1;
while (cur <= right) {
if (a[cur] < a[keyi] && ++prev != cur) {
Swap(&a[prev], &a[cur]);
}
++cur;
}
Swap(&a[keyi], &a[prev]);
keyi = prev;
这一段代码是快速排序的核心划分过程。我们使用 key 元素 a[keyi] 来划分数组。我们维护两个指针 prev 和 cur ,prev 指向最后一个小于 key 元素的值,cur 遍历数组。如果如果 a[cur] 小于 key 元素,我们就交换 a[prev] 和 a[cur] ,并移动 prev 指针。最后,我们将 key 元素 a[keyi] 交换到它最终的位置 a[prev] ,并更新 keyi 为 prev 。
if (keyi + 1 < right) {
STPush(&stack, keyi + 1);
STPush(&stack, right);
}
if (left < keyi - 1) {
STPush(&stack, left);
STPush(&stack, keyi - 1);
}
最后,我们将右子数组的边界和左子数组的边界压入栈中,以便在下一轮循环中处理。注意,我们首先压入右子数组的边界,然后是左子数组的边界。同上,因为栈是后进先出的数据结构,我们希望先处理左子数组,然后再处理右子数组。
STDestroy(&stack);
在循环结束后,我们调用 STDestroy 函数来销毁栈,释放它占用的内存。
四、 main函数
int main() {
const int arrSize = 30;
int* a = CreateRandomArr(arrSize, 1, 10000);
printf("原始数组:");
for (int i = 0; i < arrSize; i++) {
printf("%d ", a[i]);
}
printf("\n");
QuickSortNonR(a, 0, arrSize - 1);
printf("\n排序后:");
for (int i = 0; i < arrSize; i++) {
printf("%d ", a[i]);
}
printf("\n");
FreeArr(a);
return 0;
}