数据结构之归并排序、基数排序

                   

          小目录

            1.归并排序

            2.基数排序


         1.归并排序

         归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治思想的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

 归并操作的过程如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列的表尾

  过程如图(坑爹的csdn,又自动给图片加上了水印去不掉阿,如果有侵权请及时联系我删除):

                                 数据结构之归并排序、基数排序_第1张图片

具体的实现代码:

/*
* @description:对左右两部分进行合并
*/
void Merge(ElemType *SR,ElemType *TR,int i,int m,int n) {
	int j,k;
	/*
	由于前后两部分都是有序的,故可以采用前后中一个元素比较的方法
	设置两个指针,k主要是起到了控制下标的作用,只有在两部分都是
	有序的情况下才可以用这种办法
	*/
	for(j = m + 1,k = i; i <= m && j <= n ; k++) {
		if(SR[i].key < SR[j].key)
			TR[k] = SR[i++];
		else
			TR[k] = SR[j++];
	}

	//将前半部分剩余的移入TR
	if(i <= m)
		while(i <= m)
			TR[k++] = SR[i++];

	//将后半部分剩余的移入TR,注意这两种情况不会同时出现
	if(j <= n)
		while(j <= n)
			TR[k++] = SR[j++];

}


/*
* @description:具体的归并排序实现
*/
void MSort(ElemType *SR,ElemType *TR1,int s,int t) {
	int m;
	ElemType TR2[MAXSIZE + 1];

	if(s == t)
		TR1[s] = SR[s];
	else {
		//找到中间值
		m = (s + t) / 2;

		MSort(SR,TR2,s,m);	//对左半部分进行归并排序
		MSort(SR,TR2,m + 1,t);	//对右半部分进行归并排序
		Merge(TR2,TR1,s,m,t);	//将左右两部分进行归并
	}

}




/*
* @description:归并排序
*/
void MergeSort(SqList *L) {
	MSort((*L).r,(*L).r,1,(*L).length);
}

        性能分析:该算法是最坏和平均情况下的时间复杂度都为O(nlogn),辅助存储为O(n)



     2.基数排序

     基数排序是一种比较奇葩的排序算法,前面的排序算法都是通过两个元素间比较和移动来实现排序,可是技术排序却偏偏不是这样的。如果不是这样,那也能排序吗?我一想也觉得奇怪,后来才发现这真不是我等平凡人能想出来的。

     基数排序是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,然后在收集,以达到排序的作用。什么是部分资讯呢?比如我们要排序扑克牌,我们是按照花色来还是按照面值来排序呢?当然两者都可以。比如我们比较12和02是我们总是先比较1和0再比较2和2,基数排序中就有两种排序思想:最高位优先/最低位优先。下面我们的代码实现是基于数组实现的:

/*
* @description:基数排序
* @more:并没有采用递归的思想
*/
void RadixSort(int *a, int n) {
	int i,m,exp;
	int b[MAX];

	m = a[0];
	exp = 1;

	//找到最大值
	for (i = 1; i < n; i++) {
		if (a[i] > m) {
			m = a[i];
		}
	}
	/*
	为什么要找到最大值呢?因为可能会出现 1 和 999这样的两个数字,必须以
	最大的数字为基准来进行基数的收集和分配结束条件
	*/
	while (m / exp > 0) {
		//初始化
		int bucket[BASE] = { 0 };
		//统计出当前相同字码的个数,比如21和31则现在有两个个位数为1的数
		for (i = 0; i < n; i++) {
			bucket[(a[i] / exp) % BASE]++;
		}
		
		/*
		统计出每个字码的数组边界,比如个位数为0有1,个位数为1的有两个
		则个位数为1的在数组中的存储边界为3即1-2
		*/
		for (i = 1; i < BASE; i++) {
			bucket[i] += bucket[i - 1];
		}
		
		//分配,注意这里从后面扫描仅仅是为了保证排序的稳定性
		for (i = n - 1; i >= 0; i--) {
			b[--bucket[(a[i] / exp) % BASE]] = a[i];
		}

		//收集
		for (i = 0; i < n; i++) {
			a[i] = b[i];
		}

		//进行下一位数的收集和分配
		exp *= BASE;

	}
}
      排序过程即不断收集和分配的过程: 【521 310 72 373 15 546 385 856 187 147】序列为例,具体细节如下图所示:

              数据结构之归并排序、基数排序_第2张图片             数据结构之归并排序、基数排序_第3张图片         数据结构之归并排序、基数排序_第4张图片


       更详细的可以看看这个(其实他的代码风格很不好):链接

     性能分析:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,时间复杂度O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。辅助存储空间为O(radix).

      

   总结:基本排序算法的博客已经写完了,上一张图来总结下吧

数据结构之归并排序、基数排序_第5张图片



在稳定性上,快速排序/堆排序/希尔排序都是不稳定排序,其他的都是稳定排序。

后面的博客会介绍常见的五种算法思想,敬请期待。


你可能感兴趣的:(Data,Structure,Algorithm)