排序算法(二)快速排序和归并排序

 

之所以把归并排序和快速排序放一起,是因为这两种方法其实很类似,两者都是讲数列细分到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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值