之所以把归并排序和快速排序放一起,是因为这两种方法其实很类似,两者都是讲数列细分到0或1个单位,但是快排在细分的过程中会将排序做好,而归并排序是先执行细分这个过程,然后把细分的数据有序合并,过程比快排稍微繁琐一点
3.快速排序
快速排序是通过一趟排序将待排数列分隔成独立的两部分,其中一部分记录的新序列比另一份小,则可分别对这两部分继续进行排序,最后整个数列就是有序的。
步骤:
1. 从待排数列中选好一个基准
2. 重新排序数列,把比基准小的摆在基准前面,大的排在后面
3. 对于基准左右两侧的数列继续进行上述两个步骤,直到序列中只有0或者1个数据,这时候数列就是有序的【可以用递归实现】
对于如何选取基准,有几种常见的办法:
1.使用固定位置的基准:一般选用数列第一个或者最后一个位置的数据作为基准
2.使用随机位置的基准:生成一个小于数列数据大小的随机数,取出随机数位置的数据作为基准。
3.三数取中的基准:选取数列第一个位置、中间位置,最后一个位置三个数据的中间值作为基准。
以三数取中为例
假设有数据{1,4,6,8,3,4,0,2,14},要按照从大到小进行排序
选取第一、中间、最后的三个值分别是1,3,14,此时我们以3为基准,将数据分为两部分,因为是从大到小,我们把大于基准的数据放左边,较小值放右边。
分别是 {4,6,8,4,14} {3} {1,0,2}
重复上述动作,结果是:
{14}{8} {4,6,4} {3} {2}{1}{0}
再重复,结果是
{14} {8} {6}{4}{4} {3} {2}{1}{0}
排序完成
{14,8,6,4,4,3,2,1,0}
以三数取中为例的C语言代码:
void swap(int *pa, int *pb)
{
int nTmp = *pa;;
*pa = *pb;
*pb = nTmp;
}
int getMidOfThree(int arr[], int nLow, int nHigh)
{
int nPivot = 0;
int nMid = nLow + ((nHigh - nLow) >> 1);
//通过判断进行将中值移动到第一个下标处,也方便处理
if(arr[nLow] > arr[nHigh])
{
swap(&arr[nLow], &arr[nHigh]);
}
if(arr[nMid] > arr[nHigh])
{
swap(&arr[nMid], &arr[nHigh]);
}
if(arr[nLow] < arr[nMid])
{
swap(&arr[nMid], &arr[nLow]);
}
//nLow下标所在的数据就是中值
return arr[nLow];
}
int quickSortPartition(int arr[], int nLow, int nHigh)
{
int nPivot = getMidOfThree(arr, nLow, nHigh);
int i = nLow;
int j = 0;
for(j=i+1; j<=nHigh; j++)
{
if(arr[j] >= nPivot)
{
i++;//由于第一个索引下保存的是中值,所以先不保存到这个位置
swap(&arr[i],&arr[j]);
}
}
swap(&arr[i],&arr[nLow]);//将最后一个交换过来的数据跟第一个索引交换位置,这样就形成了中值在中间的形式
return i;//此时返回中值所在的下标i
}
void quickSort(int anData[], int nLow, int nHigh)
{
int nIndex = 0;
if(nLow < nHigh)//满足该条件证明至少还有两个元素,继续递归。
{
nIndex = quickSortPartition(anData, nLow, nHigh);
quickSort(anData, nLow, nIndex-1);//Index-1 和 Index+1是因为Index索引处的值已经是单独的中值,不需要再交换位置了
quickSort(anData, nIndex+1, nHigh);
}
}
以上算法时间复杂度分析:
最优时间复杂度:O(nlogn); //这个想了好久不知道怎么算的,知道的老铁可以说一下
最差时间复杂度:O(n^2); //如果每次取出的数据都是最大值或者最小值作为基准,那么就跟选择排序一样了。
4.归并排序(Merge Sort): 归并的含义是将两个或者两个以上的有序表合并成一个新的有序表。
就是把待排序的序列分成若干个子序列,每个序列是有序的,然后把多个序列两两合并成一个整体。
步骤:
(1)把数列从中间切分成两个数列,以此类推直至子数列的大小为0或者1【因为如果是1个数据,我们就可以默认它是有序的了,从一个数据开始合并】
(2)将子序列两两有序合并,直到合成唯一的主序列,此时排序就完成了
假设有数据{1,4,6,8,3,4,0,2,14},要按照从大到小进行排序
那么先从中间将数据分开
{1,4,6,8,3} {4,0,2,14}
第二次拆分
{1,4,6} {8,3} {4,0} {2,14}
{1,4,6} {8,3} {4,0} {2,14}
{1, 4} {6}(显而易见,分到最后6是单独一组){8} {3} {4} {0} {2} {14}
{1} {4} {6} {8} {3} {4} {0} {2} {14}
两两从大到小合并
{4,1} {6} (这一组分多了一次,所以归并的时候也要多一次)
{6,4,1} {8,3} {4,0} {14,2}
{8,6,4,3,1} (14,4,2,0)
{14,8,6,4,4,3,2,1,0}
算法分析:
(1)稳定性:
归并排序是一种稳定的排序。
排序的稳定性:排序之后,相同值的数据在目标序列中的位置不变。
比如序列a[], 其中出现a[i] = a[j], i<j
此时排序完成后,在目的数组中, a[i]仍在a[j]的前面。
(2)存储结构
可用数组和链表实现
(3)时间复杂度
归并排序的时间复杂度为n(logn)
(4)空间复杂度
最优情况下:O(logn) 最差情况下:O(n)
(5)C语言代码
void mergeSortPartition(int arr[], int nLow, int nHigh, int anTemp[])
{
int i = nLow;//要注意数据的起始位置
int nMid = (nLow + nHigh)/2;
int j = nMid + 1;
int k = 0;
while(i <= nMid && j<= nHigh)
{
if(arr[i] <= arr[j])
anTemp[k++] = arr[j++];//从大到小排序,选大的
else
anTemp[k++] = arr[i++];
}
//处理以上循环结束之后还未拷贝的数据
while(i <= nMid)
{
anTemp[k++] = arr[i++];
}
while(j <= nHigh)
{
anTemp[k++] = arr[j++];
}
//将合并后小数组并入大数组
for(i=0; i<k; i++)
{
arr[nLow + i] = anTemp[i];
}
}
void mergeSort(int anData[], int nLow, int nHigh, int anTemp[])
{
int nMid = (nLow + nHigh)/2;
if(nLow < nHigh)//不满足该条件证明已经是0个或者1个元素,递归终止
{
mergeSort(anData, nLow, nMid, anTemp);//Index-1 和 Index+1是因为Index索引处的值已经是单独的中值,不需要再交换位置了
mergeSort(anData, nMid+1, nHigh, anTemp);
mergeSortPartition(anData, nLow, nHigh, anTemp);
}
}
int main()
{
int anData[] = {1, 4, 6, 8, 3, 0, 2, 144, 3, 5, 8, 88, 11};
int anTemp[13] = {0};
int i = 0;
for(i=0; i<sizeof(anData)/sizeof(int); i++)
{
printf("%d ", anData[i]);
}
printf("\n");
mergeSort(anData, 0, sizeof(anData)/sizeof(int)-1, anTemp);
for(i=0; i<sizeof(anData)/sizeof(int); i++)
{
printf("%d ", anData[i]);
}
printf("\n");
getchar();
return 0;
}