归并排序 and 快速排序
Posted 码农在途
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了归并排序 and 快速排序相关的知识,希望对你有一定的参考价值。
正文共: 3293字
预计阅读时间: 9分钟
归并排序 and 快速排序 ( C 语言实现 )
归并排序
含义 :将将一串混乱数字分成无数个以两个数字为集合的小块,此时只要对两个元素进行排序即可,再无数个有序小块合并成一个有序集合,排序的过程就完成了。 将一个大的集合分成无数的小的集合,符合了『 分治法 』中将一个大的问题分成小问题来解决的思想,而对两个元素进行排序,再将各个小的有序集合合并成一个大的有序集合这个过程就是『 治 』。
算法不仅仅是计算的方法,在探究算法的过程中反映的是我们对世界的认知方法,应当把算法看做事一种思维方式。
归并排序的起始点,先申请一个和需要排序的数据同等大小的空间来存储排列好的数据,通过 MSort 方法来切分数组 ,排序结束后释放临时数组所分配的内存。( 『 归并排序 』的实现方法有很多,这里使用的是《 数据结构与算法分析 —— C 语言描述 》中的代码 )
归并排序程序主入口
void MergeSort(int arry[], int n)
{
int * tempArry = (int *)malloc(n * sizeof(int));
if (tempArry != NULL)
{
MSort(arry, tempArry, 0, n - 1);
free(tempArry);
}
else
{
printf("内存空间不足 !");
}
}
通过递归方法,将数组分成 1 个 1 个的 小数组,再通过 Sort 将所有数组合并排序
void MSort(int arry[], int tempArry[], int left, int right)
{
if (left < right)
{
int center = left + (right - left) / 2;// 与直接求平均值无异,此写法只为避免 left + right 超出 int 范围
MSort(arry, tempArry, left, center);//将数组从中间分开,直到只剩下1个元素
MSort(arry, tempArry, center + 1, right);
Sort(arry, tempArry, left, center + 1, right);
}
}
设置左起始位 left 与右起始位 center 两两合并排序,当数组遍历完成则数组排序结束
void Sort(int arry[], int tempArry[], int left, int center, int rightEnd)
{
int i, leftEnd, numsArry, tempFlag;
leftEnd = center - 1;
tempFlag = left;
numsArry = rightEnd - left + 1;// 元素数量
while (left <= leftEnd&¢er <= rightEnd)
{
if (arry[left] <= arry[center])
tempArry[tempFlag++] = arry[left++];// 执行顺序 tempArry[tempFlag] = arry[left]; => tempFlag+=1;left+=1;
else
tempArry[tempFlag++] = arry[center++];
}
while (left <= leftEnd)//两个元素时直接放入数组
{
tempArry[tempFlag++] = arry[left++];
}
while (center <= rightEnd)
{
tempArry[tempFlag++] = arry[center++];
}
for (i = 0; i < numsArry; i++,rightEnd--)
arry[rightEnd] = tempArry[rightEnd];
}
快速排序
思想:在无序数组中找到分区点 ,左边都为小于等于分区点的值,右边都为大于等于分区点的值。将整个数组分成 N 个这样的数组,当最后分区小于三个元素时说明排序完成。
快速排序的算法好坏很大程度上取决于 『 分区点 』,本文代码采用的是 『 三数中值分割法 』,即取数组最左端最右端以及数组中间三个数的中间数为分区点,减少采用左右端点碰到极端顺序的出现的最坏情况( 当选取左右端点,碰到数据有序,从大到小或是从小到大的情况 ,算法时间复杂度就会变成最坏时间复杂度)。
将数组分区
void QuickSort(int arry[],int n)
{
QSort(arry, 0, n - 1);
}
void QSort(int arry[],int start,int end)
{
int i, j;
int pivot;//建立分区点
if (start + 3 <= end)
{
pivot = Median3(arry,start,end);// pos 等于中间值
i = start; j = end-1;
//while (i<j)//确保左边的数都小于等于中间值,右边的数都大于等于中间值
//{
// while (arry[++i] < pos);
// while (arry[--j] > pos);
// Swap(&arry[i], &arry[j]);//不满足则交换
//}
for (;;)
{
while (arry[++i] < pivot);
while (arry[--j] > pivot);
if (i < j)
Swap(&arry[i], &arry[j]);
else
break;
}
Swap(&arry[i], &arry[end-1]);// 右边界从第二个数
QSort(arry,start,i-1);
QSort(arry, i+1 ,end);
}
else//说明此时最多只有 3 个元素只要比较大小进行即可
{
insertSort(arry+start,end-start+1);
}
}
通过 『 三数中值法 』优化分区点
int Median3(int arry[],int left,int right)
{
int center = left + (right - left) / 2;
if(arry[left] > arry[right])
Swap(&arry[left], &arry[right]);
if (arry[left] > arry[center])
Swap(&arry[left], &arry[center]);
if (arry[center] > arry[right])
Swap(&arry[center], &arry[right]);
return arry[right - 1];
}
void Swap(int *a,int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void insertSort(int *nums, int n)
{
//int *temps = (int *)malloc(sizeof(int )*n);
/*
把数据分成两个部分一部分是有序的另一部分是无序的,
每次把无序数列中的第一个数插入到有序数列中按顺序排列的位置
*/
int j = 0, i = 0;
for (; i < n - 1; i++)
{
int value = *(nums + i + 1);
for (j = i; j >= 0; )
{
if (*(nums + j) > value)
{
*(nums + j + 1) = *(nums + j);
j--;
}
else
break;
}
*(nums + j + 1) = value;
}
}
比较分析
递归排序和归并排序都采用了递归,将数组分成了 1-2 个元素大小的数组 则递归次数为 log n,每次都对整个数组有一个排序的执行 大小为 N ,综合来看二者的平均时间复杂度都为 O(n * log n ),但从最坏情况来看,当快速排序的分区点每次都是最大或最小值时分区点就要继续选择,最后最小集合还有一个插入排序的过程,即为 O(n^ 2) ,而归并排序无论最好最坏执行的步骤一致,所以复杂度还是 O(n * log n )。通常我们会选择合适的分区点来优化快速排序遭遇最坏情况的可能性,例如 :三数中值分割法
从原地排序分类的角度来看 :归并排序在合并的时候可能会改变数据的原始顺序,所以并不是原地排序算法。而快速排序法没有开辟额的内存空间只是交换大小位置,可以做到不对数据做多余操作所以是一种原地排序算法。
综合考虑 :
归并排序并不是原地排序算法,且在排序过程中要分配额外的内存空间,所以在实际应用中较少使用。
快速排序法可以通过分区点的优化来降低算法的复杂度,且不用开辟多余的内存空间,在实际中应用的更为广泛。
如果文章阅读有问题欢迎探讨 ,了解更多 『 数据结构与算法 』方面的知识可以看这几篇文章
你的点赞、评价或转发都是对我最好的支持。感谢你的观看,希望和你一起成长。
以上是关于归并排序 and 快速排序的主要内容,如果未能解决你的问题,请参考以下文章