快速排序是冒泡排序的优化版,效率更高
假设对以下数据进行快速排序
接下来介绍实现快速排序的方法(用升序来举例)
挖坑法
1.1 单趟排
先将第一个数据设为key,然后经过单趟的排序,使key的左边都小于key,key的右边都大于key
上面动图的实现代码
void PrintArr(int* arr, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
void QuickSort(int* arr, int n)
{
int left = 0, right = n - 1;
int key = arr[left];
int pivot = left;
while (left < right)
{
//右边找小,放到左边
while (left < right && arr[right] >= key)
{
right--;
}
arr[pivot] = arr[right];//填坑
pivot = right;//right位置的数据被移走,形成新的坑
//左边找大,放到右边
while (left < right && arr[left] <= key)
{
left++;
}
arr[pivot] = arr[left];//填坑
pivot = left;//left位置的数据被移走,形成新的坑
}
//注意!要注意判断是先找小还是先找大
//此处排升序,所以必须要先在右边找小,然后左边找大
//因为第一次的坑在左边,所以要右边找小的放在左边
arr[pivot] = key;
}
void TestQuickSort()
{
int arr[] = { 6,1,2,7,9,3,4,10,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
QuickSort(arr, sz);
PrintArr(arr, sz);
}
int main()
{
TestQuickSort();
return 0;
}
1.2 完整版排序
经过单趟排之后,我们可以想到分治的思路,用递归,让key的左子区间和右子区间都有序
#include <stdio.h>
void PrintArr(int* arr, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
void QuickSort(int* arr, int begin,int end)
{
if (begin >= end)
{
return;
}
int left = begin, right = end;//一组待排序的数据,left是这组数据的第一个数据,right是最后一个数据
int key = arr[left];//key取的是每组数据的第一个数
int pivot = left;
while (left < right)
{
//右边找小,放到左边
while (left < right && arr[right] >= key)
{
right--;
}
arr[pivot] = arr[right];//填坑
pivot = right;//right位置的数据被移走,形成新的坑
//左边找大,放到右边
while (left < right && arr[left] <= key)
{
left++;
}
arr[pivot] = arr[left];//填坑
pivot = left;//left位置的数据被移走,形成新的坑
}
//注意!要注意判断是先找小还是先找大
//此处排升序,所以必须要先在右边找小,然后左边找大
//因为第一次的坑在左边,所以要右边找小的放在左边
arr[pivot] = key;
QuickSort(arr, begin, pivot - 1);//让左子区间有序
QuickSort(arr, pivot + 1, end);//让右子区间有序
}
void TestQuickSort()
{
int arr[] = { 6,1,2,7,9,3,4,10,8 };
int sz = sizeof(arr) / sizeof(arr[0]);
QuickSort(arr, 0, sz - 1);
PrintArr(arr, sz);
}
int main()
{
TestQuickSort();
return 0;
}
1.3 三数取中优化
如果数组的第一个是最大的数或最小的数,就会出现最坏情况,为了避免最坏情况发生,有三数取中的办法进行优化
思路:比较left,right和mid的大小,取中间的数当key,并且将其与第一个交换位置
int GetMidIndex(int* arr, int left, int right)
{
int mid = (left + right) / 2;
int arr2[3] = { arr[left],arr[right],arr[mid] };
QuickSort(arr2, 0, 2);
if (arr[left] == arr2[1])
{
return left;
}
else if (arr[right] == arr2[1])
{
return right;
}
else
{
return mid;
}
}
void Swap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void QuickSort(int* arr, int begin,int end)
{
if (begin >= end)
{
return;
}
int left = begin, right = end;
int index = GetMidIndex(arr, left, right);
Swap(&arr[left], &arr[index]);
int key = arr[left];
int pivot = left;
while (left < right)
{
//右边找小,放到左边
while (left < right && arr[right] >= key)
{
right--;
}
arr[pivot] = arr[right];//填坑
pivot = right;//right位置的数据被移走,形成新的坑
//左边找大,放到右边
while (left < right && arr[left] <= key)
{
left++;
}
arr[pivot] = arr[left];//填坑
pivot = left;//left位置的数据被移走,形成新的坑
}
//注意!要注意判断是先找小还是先找大
//此处排升序,所以必须要先在右边找小,然后左边找大
//因为第一次的坑在左边,所以要右边找小的放在左边
arr[pivot] = key;
QuickSort(arr, begin, pivot - 1);
QuickSort(arr, pivot + 1, end);
}
1.4 序列长度达到一定大小时,使用插入排序
当快排达到一定深度后,划分的区间很小时,再使用快排的效率不高。当待排序列的长度达到一定数值后,可以使用插入排序。由《数据结构与算法分析》(Mark Allen Weiness所著)可知,当待排序列长度为5~20之间,此时使用插入排序能避免一些有害的退化情形。
假设当有一百万个数据,用快速排序,可以看到最后几层的排序就调用了很多次QuickSort函数。
例如当深度很深,排序十个数据时,要调用几十万次函数,效率较低。
所以可以使用插入排序
void QuickSort(int* arr, int begin,int end)
{
if (begin >= end)
{
return;
}
int left = begin, right = end;
int index = GetMidIndex(arr, left, right);
Swap(&arr[left], &arr[index]);
int key = arr[left];
int pivot = left;
while (left < right)
{
//右边找小,放到左边
while (left < right && arr[right] >= key)
{
right--;
}
arr[pivot] = arr[right];//填坑
pivot = right;//right位置的数据被移走,形成新的坑
//左边找大,放到右边
while (left < right && arr[left] <= key)
{
left++;
}
arr[pivot] = arr[left];//填坑
pivot = left;//left位置的数据被移走,形成新的坑
}
//注意!要注意判断是先找小还是先找大
//此处排升序,所以必须要先在右边找小,然后左边找大
//因为第一次的坑在左边,所以要右边找小的放在左边
arr[pivot] = key;
if (pivot - 1 - begin > 10)
{
QuickSort(arr, begin, pivot - 1);
}
else
{
InsertSort(arr, begin, pivot - 1);
}
if (end - pivot - 1 > 10)
{
QuickSort(arr, pivot + 1, end);
}
else
{
InsertSort(arr, pivot + 1, end);
}
}
文章动图借用自csdn博主“双鱼211”
非递归写法
使用栈来模拟递归调用过程,避免了递归造成的函数栈溢出和效率问题。
程序首先将整个数组的左右端点入栈。接下来,程序进入循环,每次从栈顶弹出一个元素来进行操作。如果右端点小于等于左端点,则说明该子数组不需要进行排序,直接进入下一轮循环;否则,程序选取最右侧的元素作为基准元素,并将数组中小于基准元素的元素放到左侧,大于基准元素的元素放到右侧。然后,程序将排序后的左右两个子数组的左右端点入栈。当栈为空时,整个数组就完成了排序。
typedef int ElemType;
typedef struct Stack
{
int top;//栈顶
ElemType* a;//后续使用动态开辟空间
int capacity;//容量
}ST;
void StackInit(ST* ps)//初始化
{
assert(ps);
ps->top = 0;
ps->a = NULL;
ps->capacity = 0;
}
void StackPush(ST* ps, ElemType x)//增
{
assert(ps);
//检查空间,如果满了就二倍增容
//如果是第一次增容则先开辟四个空间
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : (2 * ps->capacity);
ElemType* tmp = (ElemType*)realloc(ps->a, sizeof(ST) * newcapacity);
//如果ps->a为NULL,则此时realloc的作用与malloc一样
if (tmp == NULL)
{
printf("realloc fail!\n");
exit(0);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
bool StackEmpty(ST* ps)//判断是否为空
{
assert(ps);
if (ps->top == 0)//为空
{
return true;
}
else//不为空
{
return false;
}
}
void StackPop(ST* ps)//删
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
printf("栈顶数据删除成功\n");
}
ElemType StackSize(ST* ps)//判断栈的长度
{
assert(ps);
return ps->top;
}
ElemType StackTop(ST* ps)//查看栈顶
{
assert(ps);
//找栈顶的话得保证指向的空间不为空
assert(!StackEmpty(ps));
//此时的top-1就是栈顶数据
return ps->a[ps->top - 1];
}
void StackDestory(ST* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
}
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
int PartSort(int* arr, int begin, int end)
{
int left = begin, right = end;
int key = arr[left];
int pivot = left;
while (left < right)
{
//右边找小,放到左边
while (left < right && arr[right] >= key)
{
right--;
}
arr[pivot] = arr[right];//填坑
pivot = right;//right位置的数据被移走,形成新的坑
//左边找大,放到右边
while (left < right && arr[left] <= key)
{
left++;
}
arr[pivot] = arr[left];//填坑
pivot = left;//left位置的数据被移走,形成新的坑
}
arr[pivot] = key;
return pivot;
}
void QuickSortNonS(int* arr, int n)
{
int left = 0, right = 0;
ST st;
StackInit(&st);
StackPush(&st, n - 1);
StackPush(&st, 0);
while (!StackEmpty(&st))
{
left = StackTop(&st);
StackPop(&st);
right = StackTop(&st);
StackPop(&st);
int IndexMid = PartSort(arr, left, right);
if (left < IndexMid - 1)//左部分入栈
{
StackPush(&st, IndexMid-1);
StackPush(&st, left);
}
if (right > IndexMid + 1)//右部分入栈
{
StackPush(&st, right);
StackPush(&st, IndexMid + 1);
}
}
StackDestory(&st);
}
void PrintArr(int* arr, int n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
}
void TestQuickSort()
{
int arr[] = { 6,1,2,7,9,3,4,10,8,3456,15,4,-348 };
int sz = sizeof(arr) / sizeof(arr[0]);
QuickSortNonS(arr, sz);
PrintArr(arr, sz);
}
int main()
{
TestQuickSort();
return 0;
}