1、算法设计原理分析
快速排序是对冒泡排序的一种改进,快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据比另一部分的所有数据要小,再按这种方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,使整个数据变成有序序列。
基本步骤为:1)先从数列中取出一个数作为基准数;2)分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。3)再对左右区间重复第二步,直到各区间只有一个数。
非递归实现方法,需要把递归实现中函数调用部分换为迭代中调用函数,这里就可以用队列,或者栈来保存每次递归中的开始位置和结束位置,从而,可以在每次快排结束后,从队列,或者栈中跳出一组开始、结束位置下标,再进行快排,直到队列或者栈为空时停止。我采用了栈来保存开始、结束的位置,每次都先进行左边的快排,所以在对位置进栈操作时,先把右边的开始、结束标记位置推入栈,再把左边的推入栈
2、程序设计
递归实现:
#include<stdio.h>
#include<stdlib.h>
void QuickSort_GetKey(double *p,int len);
void QuickSort_Recursion(double *p,int len);
int main()
{
int i;
double arr[]={-34,4,0,6000.354,5.43,-343.23};
int n=sizeof(arr)/sizeof(double);
printf("排序前的数组为:\n");
for(i=0;i<n;i++)
printf("%lf ",arr[i]);
QuickSort_Recursion(arr,n);
printf("\n快速排序后的数组为:\n");
for(i=0;i<n;i++)
printf("%lf ",arr[i]);
return 0;
}
void QuickSort_Recursion(double *p,int len) //定义递归方式的快速排序算法函数
{
int i=0,j=len-1;
QuickSort_GetKey(p,len-1);
if(len<=1)
return;
double key=p[j]; //选取一个关键字(key)作为枢轴
while(i<j)
{
while(i<j&&p[i]<key)
{
i++;
}
p[j]=p[i];
while(i<j&&p[j]>key)
{
j--;
}
p[i]=p[j];
}
p[i]=key;
QuickSort_Recursion(p,j); //对前一子数组进行快速排序
QuickSort_Recursion(p+j+1,len-j-1); //对后一子数组进行快速排序
}
void QuickSort_GetKey(double *p,int len) //定义找枢纽的函数,取前中末三数的中位数作为枢纽放末尾
{
int tem;
if(p[0]>p[len/2])
{
tem=p[0];
p[0]=p[len/2];
p[len/2]=tem;
}
if(p[0]>p[len])
{
tem=p[0];
p[0]=p[len];
p[len]=tem;
}
if(p[len/2]<p[len])
{
tem=p[len/2];
p[len/2]=p[len];
p[len]=tem;
}
}
非递归方式实现:
#include<stdio.h>
#include<stdlib.h>
void quickSort(int *arr, int size);
int QuickSort_Getposition(int *arr, int begin, int end);
typedef struct Stack //定义结构体栈
{
int *data;
int size;
}stack;
void InitStack(stack *s) //初始化栈
{
int *data = (int*)malloc(20 * sizeof(int)); //申请开辟一个长度为20的数组空间
if (data == NULL) //若申请不到,则报错退出
{
return;
}
s->data = data;
s->size = 0;
}
void PushStack(stack *s,int d) //入栈函数
{
if (s->size > 20) //若栈满了就返回
{
return;
}
else
s->data[s->size++] = d;
}
void PopStack(stack *s) //出栈函数
{
if (s->size == 0) //若栈里没有数据就返回
{
return;
}
else
s->size--;
}
int TopStack(stack *s) //显示栈尾的内容
{
return s->data[s->size-1];
}
int EmptyStack(stack *s) //判断栈是否为空的函数
{
return(s->size==0);
}
void Print(int *arr, int size) //打印函数
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int MiddleNumber(int *arr, int begin, int end)
{
int mid = begin + ((end - begin) /2);
if (arr[begin] > arr[mid])
{
if (arr[begin] > arr[end])
{
if (arr[mid] > arr[end])
{
return mid;
}
else
return end;
}
else
return begin;
}
else
{
if (arr[mid] > arr[end])
{
if (arr[begin] > arr[end])
{
return begin;
}
else
{
return end;
}
}
else
return mid;
}
}
int main()
{
int arr[] = {0,5,3,2,9,8,7,6,4,1};
printf("排序前: "); //打印
Print(arr,sizeof(arr)/sizeof(int));
quickSort(arr,sizeof(arr)/sizeof(int));
printf("排序后: ");
Print(arr, sizeof(arr)/sizeof(int));
return 0;
}
void quickSort(int *arr, int size) //定义快速排序非递归实现函数
{
stack s; //用于存储起点和终点标记的栈
int position;
int left = 0;
int right = 0;
InitStack(&s);
PushStack(&s, 0);
PushStack(&s, size);
while (!EmptyStack(&s))
{
right = TopStack(&s);
PopStack(&s);
left=TopStack(&s);
PopStack(&s);
position = QuickSort_Getposition(arr, left, right-1);
//先快排基准左侧,则先将后侧的下标入栈
if ((right-left)>position+1)
{
PushStack(&s,position+1);
PushStack(&s,right-left);
}
if (position>0)
{
PushStack(&s,0);
PushStack(&s,position);
}
}
}
int QuickSort_Getposition(int *arr, int begin, int end) //定义找基准值的函数(挖坑法)
{
int index = MiddleNumber(arr, begin, end);
if (index != end)
{
int tem=arr[index];
arr[index]=arr[end];
arr[end]=tem;
}
//第一个坑
int key = arr[end];
int k = end;
while (begin != end)
{
/*begin从左边开始找比关键字大的元素将其入坑
,begin所在位置变为坑*/
while (arr[begin] <= key&&begin < end)
{
begin++;
}
if (begin != end)
{
arr[end] = arr[begin];
end--;
}
/*end从右开始找比关键字小的元素将其入begin坑*/
while (arr[end] >= key&&begin < end)
{
end--;
}
if (begin != end)
{
arr[begin] = arr[end];
begin++;
}
}
if (begin != k)
{
arr[begin] = key;
}
return begin;
}
3、运行结果
4、算法分析(复杂度)
快速排序的时间主要耗费在划分操作上,对长度为n的区间进行划分,共需n-1次关键字的比较,时间复杂度为O(n)。对n个元素进行快速排序的过程构成一棵递归树,在这样的递归树中,每一层最多对n个元素进行划分,所花的时间为O(n)。当初始排序数据随机分布,使每次分成的两个子区间中的元素个数大致相等时,递归树高度为log2n,快速排序呈现最好情况,即最好情况下的时间复杂度为O(nlog2n)。快速排序算法的平均时间复杂度也是O(nlog2n)。
非递归方式实现算法的时间复杂度是一样的,因为非递归的实现过程和递归是一样的,不同之处在于,非递归减少了栈的开销,每迭代调用一个函数之后,就把该函数对栈的开销去除掉,对下一次函数调用开辟新的开销。
5、解题收获
快速排序算法时间复杂度最坏情况为O(n2),最好和平均时间复杂度都为O(nlog2n),时间复杂度的好坏与原数组的排序方式和基准值有关,所以快排是一种不稳定的算法,为了使快速排序更加稳定,我们可以设计一些找关键字的方法,比如我在写代码时,就定义了一个取前中末三数的中位数作为关键字,这是可以避免每次找到的关键字为最大或者最小,从而提高快排的稳定性,当然也可以通过取随即值为关键字等方法。