归并排序 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 left, int right)
{
int i, j, pivot;
if (left + 3 <= right)
{
i = left; j = right - 1; pivot = median(arry, left, right);
printf("%d ",pivot);
while (true)
{
while (arry[++i] < pivot);
while (arry[--j] > pivot);
if (i < j)
Swap(arry + i, arry + j);
else
break;
}
for (int i = 0; i < 9; i++)
printf("%d ", arry[i]);
printf("\n");
Swap(arry+i,arry+right-1);//将分界点放到中间
quickSort(arry, left, i - 1);
quickSort(arry, i + 1,right);
}
else
{
insertSort(arry+left,right-left+1);
}
}
通过 『 三数中值法 』优化分区点
int median(int* arry,int left,int right)
{
int temp = left+(right-left)/2;
if (arry[left]>arry[right])
Swap(arry+left,arry+right);
if (arry[left]>arry[temp])
Swap(arry+left,arry+temp);
if (arry[temp] > arry[right])
Swap(arry+temp,arry+right);
Swap(arry+temp,arry+right-1);//将中间值放到右边第二个数,确保右边扫描过的数都大于 中间值
return arry[right - 1];
}
采用插入排序而非冒泡排序等,在极端情况下的复杂度有所优化
void Swap(int *num1,int *num2)
{
int temp = *num1;
*num1 = *num2;
*num2 = temp;
}
void insertSort(int *arry,int length)
{
int i=0, j=0,temp=0;
for (; i < length - 1; i++)
{
temp = arry[i + 1];//temp 为 arry[i] 的下一个数
for (j = i; j >= 0;) //按从小到大的顺序排序,先从最大的开始比较,将新加入元素放入合适位置
{
if (temp < arry[j])
{
Swap(arry+j,arry+j+1);// 将较大值放到末尾
j--;
}
else
break;
}
}
}
比较分析
递归排序和归并排序都采用了递归,将数组分成了 1-2 个元素大小的数组 则递归次数为 log n,每次都对整个数组有一个排序的执行 大小为 N ,综合来看二者的平均时间复杂度都为 O(n log n ),但从最坏情况来看,当快速排序的分区点每次都是最大或最小值时分区点就要继续选择,最后最小集合还有一个插入排序的过程,即为 O(n^ 2) ,而归并排序无论最好最坏执行的步骤一致,所以复杂度还是 O(n log n )。==通常我们会选择合适的分区点来优化快速排序遭遇最坏情况的可能性,例如 :三数中值分割法==
从原地排序分类的角度来看 :归并排序在合并的时候可能会改变数据的原始顺序,所以并不是原地排序算法。而快速排序法没有开辟额的内存空间只是交换大小位置,可以做到不对数据做多余操作所以是一种原地排序算法。
综合考虑 :
归并排序并不是原地排序算法,且在排序过程中要分配额外的内存空间,所以在实际应用中较少使用。
快速排序法可以通过分区点的优化来降低算法的复杂度,且不用开辟多余的内存空间,在实际中应用的更为广泛。