数据结构-数据排序 总结

  • Post author:
  • Post category:其他


信息获取后通常需要进行处理,处理后的信息其目的是便于人们的应用。信息处理方法有多种,通常有数据的

排序,查找,插入,删除,归并

等操作。



一、选择排序


1.基本思想

:

每一趟从待排序的数 据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,直到全部待排序的数据元素排完。


2.排序过程

:

[示例]

初始关键字

[49 38 65 97 76 13 27 49]


第一趟排序后13

[38 65 97 76 49 27 49]


第二趟排序后13 27

[65 97 76 49 38 49]


第三趟排序后13 27 38

[97 76 49 65 49]


第四趟排序后13 27 38 49

[76 97 65 49]


第五趟排序后13 27 38 49 49

[97 65 76]


第六趟排序后13 27 38 49 49 65

[97 76]


第七趟排序后13 27 38 49 49 65 76

[97]


最后排序结果13 27 38 49 49 65 76 97

void SelectSort(int R[])//对R[1..N]进行直接选择排序
{
	int temp, k;
	for (int i = 1; i <= n - 1; i++)//做N- 1趟选择排序
	{
		k = i;
		for (int j = i + 1; j <= n; j++)if (R[k] < R[j]) k = j;
		//在当前无序区R[I-N]中选最小的元素R[K]	
		if (k != i) temp = R[i], R[i] = R[k], R[k] = Temp;
		//交换R[]和R[K]
	}
}



二、冒泡排序


1.基本思想


依次比较相邻的两个数,把大的放前面,小的放后面。即首先比较第1个数和第2个数,大数放前,小数放后。然后比较第2个数,第3个数…直到比较最后两个数。第一趟结束,最小的一定沉到最后。重复上过程,仍从第1个数开始到最后第2个数,然后由于在排序过程中总是大数往前,小数往后,相当气泡上升,所以叫冒泡排序。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

void mpsort(int a[])
{
	int temp;
	for (int i = 1; i <= n - 1; i++)
		for (int j = 1; j <= n - i; j++)
			if (a[j] < a[j + 1])
				temp = a[j], a[j] = a[j + 1], a[j + 1] = temp;
}


2.改进


上例中,可以发现,第二趟结束已经排好序。但是计算机此时并不知道已经排好序。所以,如果在进行一次比较,如果没有发生任何数据交换,则知道已经排好序,可以不继续了。

我们设置一个布尔变量bo来记录是否有进行交换。值为false表示本趟中进行了交换,true则没有。

代码如下:

void mpgjsort(int a[])
{
	int i = 1, temp;
	bool bo = true;
	do
	{
		bo = true;
		for (int j = 1; j <= n - i; j++)
			if (a[j] < a[j + 1])
			{
				temp = a[j], a[j] = a[j + 1], a[j + 1] = temp;
				bo = false;
			}
		i++;
	} while (!bo);
}



三、桶排序(哈希排序)


1.基本思想


桶排序的思想是若待排序的记录的关键字在一个明显有限范围内(整型)时,可设计有限个有序桶,每个桶装入一个值(当然也可以装入若干个值),顺序输出各桶的值,将得到有序的序列。


2.例题


输入n个0到100之间的不相同整数,由小到大排序输出。

#include<iostream>
#include<cstring>
int main()
{
	int b[101], k, i, n;
	memset(b, 0, sizeof(b));//初始化
	std::cin >> n;
	for (i = 1; i <= n; i++)
	{
		std::cin >> k;
		b[k]++;//将关键字等于k的值全部装入第k桶
	}
	for (i = 0; i <= 100; i++)
		while (b[i] > 0) 
		{ 
			std::cout << i << " ";  //输出排序结果
			b[i]--; 
		}
	std::cout << std::endl;
	return 0;
}



四、插入排序


1.基本思想


假设待排序的数据存放在数组R[1…n]中,增加一个哨兵结点x。

(1) R[1]自成1个有序区,无序区为R[2…n];

(2)从i=2起直至i=n为止,将R[i]放在恰 当的位置,使R[1…]数 据序列有序;

①x=R[i];

②将x与前i-1个数比较,j=i-1; while (x<a[i]){ j=j-1;}

③将R数组的元素从j位置开始向后移动: for k:=i downto j do a[k]:=a[k1];

④R[j]=x;

(3)生成包含n个数据的有序区。


2.例子


设n=8,数组R中8个元素是: 36,25,48,12,65,43,20,58,

执行插入排序程序后,其数据变动情况:

第0步:

[36]

25 48 12 65 43 20 58

第1步:

[25 36]

48 12 65 43 20 58

第2步:

[25 36 48]

12 65 43 20 58

第3步:

[12 25 36 48]

65 43 20 58

第4步:

[12 25 36 48 65]

43 20 58

第5步:

[12 25 36 43 48 65]

20 58

第6步:

[12 20 25 36 43 48 65]

58

第7步:

[12 20 25 36 43 48 58 65]


该算法的时间复杂性为O(n2)。适用于原先数据已经排列好,插入一个新数据的情况。

void insertsort(int r[]) // 对r[1..n]按递增序进行插入排序,x是监视哨
{
	for (int i = 2; i <= n; i++)//依次插入r[2]..r[n]
	{
		int x = r[i], j = i - 1;
		while (x < r[j])//查找r[i]的插入位置
		{
			r[j + 1] = r[j];//将大于r[i]的元素后移
			j--;
		}
		r[j + 1] = x;//插入r[i] 
	}
}



五、快速排序


1.基本思想


快速排序是对冒泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。假设待排序的序列为{a[L],a[L+ 1],a[l+2],… ,a[R]}, 首先任意选取一个记录(通常可选中间一个记作为枢轴或支点),然后重新排列其余记录,将所有关键字小于它的记录都放在左子序列中,所有关键字大于它的记录都放在右子序列中。由此可以将该“支点”记录所在的位置mid作分界线,将序列分割成两个子序列和。这个过程称作一趟快速排序(或一次划分) 。


一趟快速排序的具体做法


附设两个指针i和j,它们的初值分别为I和R,设枢轴记录取mid,则首先从j所指位置起向前搜索找到第一个关键字小于的mid的记录,然后从i所指位置起向后搜索,找到第一个关键字大于mid的记录,将它们互相交换,重复这两步直至i>j为止。

快速排序的时间的复杂性是O(nlog2n),速度快,但它是不稳定的排序方法。就平均时间而言,快速排序是目前被认为是最好的一种内部排序方法。

由以上讨论可知,从时间上看,快速排序的平均性能优于前面讨论过的各种排序方法,但快速排序需一个栈空间来实现递归。若每一趟排序都将记录序列均匀地分割成长度相接近的两个子序列,则栈的最大深度为log(n+1)。

void qsort(int l, int r, int a[])
{
	int i, j, mid, p;
	i = l, j = r, mid = a[(l + r) / 2];//将当前序列在中间位置的数定义为分隔数
	do {
		while (a[i] < mid)i++; //在左半部分寻找比中间数大的数
		while (a[j] > mid)j--; //在右半部分寻找比中间数小的数
		if (i <= j)
		{		//若找到一组与排序目标不一致的数对则交换它们
			p = a[i], a[i] = a[j], a[j] = p;
			i++, j--; //继续找
		}
	} while (i <= j);//注意这里不能有等号
	if (l < j)qsort(1, j, a);//若未到两个数的边界,则递归搜索左右区间
	if (i < r)qsort(i, r, a);
}



六、归并排序


1.基本思想


将两个或两个以上有序的数列(或有序表),合并成一个仍然有序的数列(有序表),这种操作称为归并操作。这样的方法经常用于多个有序的数据文件归并成一个有序的数据文件。若将两个有序表合并成一个有序表则称为二路归并,同理,有三路归并、四路归并等。二路归并比较简单,所以我们只讨论二路归并。例如有两个有序表:(7,10,13,15)和(4,8,19,20),归并后得到的有序表为: (4,7,8,10,13,15,19,20)。


2.基本过程


比较A[i]和A[j]的大小,若A[i]<=A[j], 则将第一个有序表中的元素A[i]复制到R[k]中,并令i和k分别加1,即使之分别指问后一单元,否则将第二个有序表中的元素A[j]复制到R[k]中,并令j和k分别加1;如此循环下去,直到其中的一个有序表取完,然后再将另一个有序表中剩余的元素复制到R中从下标k到下标t的单元。

//两个有序表A[s,m]和A[m+1,t]合并成一个有序表R[s,t]
//s是第一个有序表起点位置,m+1是第二个有序表的起点
void merge(int s, int m, int t, int A[], int R[])
{	
	int i = s, j = m + 1;//i和j分别指向二个有序表的头部
	int k = s;
	while (i <= m && j <= t)
		if (A[i] <= A[j])
			R[k] = A[i], i++, k++;
		else 
			R[k] = A[j], j++, k++;
	while (i <= m) R[k] = A[i], i++, k++; //复制第一路剩余
	while (j <= t) R[k] = A[j], j++, k++; //复制第二路剩余
}

在这里插入图片描述

归并排序(Mergesort)就是利用归并操作把一个无序表排列成一个有序表的过程。二路归并排序的过程是首先把待排序区间( 即无序表)中的每一个元素都看作为一个有序表,则n个元素构成n个有序表,接着两两归并( 即第一个表同第二个表归并,第三个表同第四个表归并,…),得到[n/2]个长度为2的有序表( 最后一个表的长度可能小于2),称此为一趟归并,然后再两两有序表归并,得到[n/2]/2]个长度为4的有序表(最后一个表的长度可能小于4),如此进行下去,直到归并第[log2n]趟后得到一个长度为n的有序表为止。归并排序算法我们用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。对左右子区间的排序与原问题-样,所以我们可以调用同样的子程序,只是区间大小不一样。

#include<iostream>
int a[10001], r[10001], n, i;//a是待排序数组,r是临时数组
void mergesort(int s, int t)//对[s,t]区间的无序数据进行归并排序
{
	int m, i, j, k;
	if (s == t)return;//若区间只有一个数据就不用排了
	m = (s + t) / 2;//取区间的中点
	mergesort(s, m);//以中点二分,对左边了区间进行排序
	mergesort(m + 1, t);//以中点二分,对右边了区间进行排序
	i = s, j = m + 1, k = s;//以下是一次归并 (合并)操作
	while (i <= m && j <= t) //二个 子序列从小大到合并,直到有一列结束
	{
		if (a[i] <= a[j])
			r[k] = a[i], i++, k++;
		else
			r[k] = a[j], j++, k++;
		while (i <= m)//把左边子序列剩余的无系接入进来
			r[k] = a[i], i++, k++;
		while (j <= t)//把右边子序列剩余的元素接入进来
			r[k] = a[j], j++, k++;
		for (i = s; i <= t; i++)//把合并后的有序数据重新放回a数组
			a[i] = r[i];
	}
}
int main()
{
	std::cin >> n;
	for (i = 1; i <= n; i++)//读入n个待排序数据
		std::cin >> a[i];
	mergesort(1, n);//对[1,n]区间的无序数据进行归并排序
	for (i = 1; i <= n; i++)//输出n个有序的数据
		std::cout << a[i] << " ";
	std::cout << std::endl;
	return 0;
}



各种排序算法的比较


1.稳定性比较


插入排序、冒泡排序、二叉树排序、二路归并排序及其他线形排序是稳定的。

选择排序、希尔排序、快速排序、堆排序是不稳定的。


2.时间复杂性比较


插入排序、冒泡排序、选择排序的时间复杂性为O(n2);

快速排序、堆排序、归并排序的时间复杂性为O(nlog2n);桶排序的时间复杂性为O(n);

若从最好情况考虑,则直接插入排序和冒泡排序的时间复杂度最好,为O(n),其它算法的最好情况同平均情况相同;

若从最坏情况考虑,则快速排序的时间复杂度为O(n2),直接插入排序和冒泡排序虽然平均情况相同,但系数大约增加一倍,所以运行速度将降低一半,最坏情况对直接选择排序、堆排序和归并排序影响不大。

由此可知,在最好情况下,直接插入排序和冒泡排序最快;在平均情况下,快速排序最快;在最坏情况下,堆排序和归并排序最快。


3.辅助空间的比较


桶排序、二路归并排序的辅助空间为O(n);

快速排序的辅助空间为O(log2n),最坏情况为O(n);

其它排序的辅助空间为O(1);


4.其它比较


插入、冒泡排序的速度较慢,但参加排序的序列局部或整体有序时,这种排序能达到较快的速度。反而在这种情况下,快速排序反而慢了。

当n较小时,对稳定性不作要求时宜用选择排序,对稳定性有要求时宜用插入或冒泡排序。

若待排序的记录的关键字在一个明显有限范围内时,且空间允许是用桶排序。

当n较大时,关键字元素比较随机,对稳定性没要求宜用快速排序。

当n较大时,关键字元素可能出现本身是有序的,对稳定性没有要求时宜用堆排序

快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳的。

在这里插入图片描述



版权声明:本文为qq_46371399原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。