考研数据结构:第八章 排序

这篇具有很好参考价值的文章主要介绍了考研数据结构:第八章 排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


一、排序的基本概念

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

二、插入排序

2.1插入排序

2.1.1算法思想

插入排序的思想很简单,就是不断的把一个个带排序的记录,按关键字的大小插入到前面已经排好序的子序列中。直到全部序列插入完成。

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
比如我们现在要对下面的序列进行排序,
刚开始我们从1号位开始
我们会认为当前处理的这个元素之前都是有序的
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
现在把当前元素38和前面已经排好序的元素依次进行对比,比当前处理元素38更大的元素,我们都需要将它依次后移。

目前只有49这个元素,我们把49后移,38插入到49前面
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来要处理的是65,65比49更大,不用移动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是97,97>69,不用移动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是76,76<97,97右移
76>65,76放下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是13,
13<97,97右移
13<76,76右移
13<65,65右移
13<49,49右移
13<38,38右移
再往下没有数可以比较了,13放下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是27,
27<97,97右移
27<76,76右移
27<65,65右移
27<49,49右移
27<38,38右移
27>13,27放下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
最后要处理的是49,这里我们49给了一个下划线,区分前面的49

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
处理方式和前面一样,把前面排好序的,小于49的右移,然后49放下去
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

注意,我们刚才是把比49更大的元素右移,和它相等的是没有右移的,这样保证了排序的稳定性。

2.1.2算法实现

代码如下:

//直接插入排序
void InsertSort(int A[],int n){
    int i,j,tmp;
    for(i=1;i<n;i++){//将各元素插入已经排好序的序列中
	   if(A[i]<A[i-1]){//若A[i]关键字小于前驱
			tmp=A[i];//tmp暂存A[i]
			for(j=i-1;j>=0&&A[j]>tmp;j--){//检查前面已经排好序的元素
					A[j+1]=A[j];
			}
			A[j+1]=tmp;
			//这里用j+1是因为上面第一次找到A[j]<=tmp时跳出了循环
			//也就是说A[j]的位置是不需要动的,而A[j+1]当时是赋给了A[j+2]
			//你需要的是把tmp赋给A[j+1]
		}
	}
}

还有一种带哨兵位的排序方式,大家可以自己看一下,我个人更偏向前一种,逻辑更清楚

带哨兵位就是把0号位空出来,然后用0号位作为哨兵暂存当前元素,和上面方法的tmp一样
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

2.1.3算法效率分析

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
对于空间复杂度,我们也就是定义了两个变量i,j用于循环。然后就是tmp(如果不用哨兵)。这些变量所需要的空间都是常数级的,所以空间复杂度O(1)

然后是时间复杂度,在进行插入排序时,我们都是从第二个元素开始,然后依次往后处理的。如果有n个元素,一共要往后进行n-1次遍历。

每次遍历时,每个元素需要进行一个往前的对比。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

2.1.4算法优化——折半插入排序

前面在处理一个元素时,都是用的顺序查找的方式依次往前找,找到该插入的位置。

但是细心的同学会发现当前要处理的元素前面的都是有序的
而有序,我们就可以用折半查找来优化了,没必要顺序查找一个个对比

比如我们现在在A[0]保存当前要处理的元素
接下来在当前要处理的元素55前面的区域设low和high
low=1,high=i-1
mid=(low+high)/2

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
对比mid和当前元素
50<55,所以55应该插在50右边
low=mid+1
mid=(low+high)/2
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

对比mid和当前元素
70>55,55应该插在70左边
high=mid-1
low=(low+high)/2

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
对比mid和当前元素
60>55,55应该插在60左边
high=mid-1
low=(low+high)/2
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里,low>high,所以折半查找停止
不难发现low所指元素及右边全部比i所指元素大,全部右移
然后把A[0]放到low的位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

后面的以此类推。

2.2希尔排序

2.2.1算法思想

希尔排序就是一个叫希尔的人发明的,其实就是基于上面介绍的插入排序的优化

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
其实就是把排序表里的元素再一个个拆分成子表

怎么拆分?就是你在每一趟的处理过程中,设置一个增量d

把相距为d的各个元素看成一个特殊的子表,把各个子表中的元素进行直接插入排序。

假设我们第一趟排序设d1=4,也就是d1=n/2=8/2=4
所有相距为d的元素都看成属于1个子表,也就是说对于49相距d=4的是76
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
而对于38,和它相距d=4的元素是13
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
后面以此类推,65和27是一个子表,97和49是一个子表
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
那在第一趟的处理中,我们会把相距距离=4的元素看成同一个子表中的元素,接下来就是对各个子表分别进行直接插入排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里就完成了第一趟子表的排序,所以第一趟结束后数组元素如下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来进行第二趟的处理,第二趟我们所需增量d的值,我们取d2=d1/2=4/2=2
第二趟我们把相距距离为2的元素划分为同一个子表。

对于1号元素49,和它相距为2的是3号元素27,再往下是76,然后是65

对于2号元素13,和它相距为2的是4号元素49,再往下是38,然后是97
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
对两个子表进行插入排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里完成第二趟的处理,表里的元素如下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

再往下第三趟的处理,我们让增量d继续缩小,d3=d2/2=2/2=1
d=1时,所有元素都会被划分为同一个子表,进行一次插入排序就可以结束了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

到这里,经过3趟排序就可以得到一个递增有序的序列

我们把刚才的几个过程进行一个罗列,如下图

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

上例中我们用的是d=4,2,1,也就是每次缩小一半,这个是希尔本人推荐的增量序列。

当然,你也可以用别的序列,考研中对希尔排序的考察,通常会出现其他各种各样的增量序列

比如我们规定第一趟d1=3,第二趟d2=1
这种情况下,第一趟的排序中,我们把相距距离为3的元素看成从属同一个子表的元素
然后分别对每个子表插入排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

接下来是第二趟d2=1,也就是对整体进行一次插入排序,得到最终序列
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

这也就是考试中关于希尔排序最常考的一种,它会给你一个元素序列,然后给你每一趟的增量d,让你求每一趟排序之后得到的序列是什么样

2.2.2代码实现

//希尔排序
void ShellSort(int A[],int n){
 	int d,i,j;//d为每一趟的增量
 	//A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置
 	for(d=n/2;d>=1;d=d/2){
		for(i=d+1;i<=n;i++){
			if(A[i]<A[i-d]){
				A[0]=A[i];
				for(j=i-d;j>0&&A[0]<A[j];j-=d){
					A[j+d]=A[j];//后移
				}
				A[j+d]=A[0];
			}
		}
	}
}

下面我们来举例说明上面的代码
一开始我们让d=n/2=8/2=4
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来进行第一趟的处理,也就是i=d+1=5,
指向这个位置的原因是我们在处理第一个子表时,按照直接插入排序的规则,我们只需要从第二个元素开始,就是看这个元素是否需要插到它前面就行了。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所以刚开始我们会让i指向第一个子表中的第2个元素的位置。按照直接插入排序的规则,我们需要对比当前元素和前面的元素大小关系,76>49,A[i]<A[i-d]条件不满足,不需要变动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

接下来是第二轮的for循环,我们让i++,也就是i=6
由于当前指向元素13比前面的元素38更小,也就是A[i]<A[i-d]满足,
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
那么我们把当前指向的元素放到A[0]的位置,然后更深层的for循环(插入排序),我们要检查当前元素前面的元素是否比当前元素更大,如果更大,那我们需要把j所指元素后移(这里的后移是你后移到子表的下一个位置

比如这里的38后移到子表的下一个位置,也就是A[j+d]=A[j],即后移到6号位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

接下来根据最里层for循环(插入排序)的规则,我们要检查这个子表中当前还有没有其他元素需要处理。此时d=4,j-d=2-4=-2,这里-2<=0就说明前面已经没有需要再比较的元素了,那么最里层的for循环(插入排序)跳出。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

然后就是把我们刚才保留在A[0]的数值放到j+d的位置,也就是把13放到2号位
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里就完成了对第二个子表的插入排序。

下面i++,进行第三个子表的插入排序,这个子表原理也是类似的,大家直接看动图
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
再让i++,发现i已经超过8这个最大值了,那么第一趟处理结束
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
再往下,让d变为原先一半,也就是d2=d1/2=2,然后处理流程和第一趟基本一样,这里不再赘述。

2.2.3算法性能分析

显然对于空间复杂度,我们只需要常数级的空间

而对于时间复杂度,由于你d设置不同,你最后得到的趟数和每趟排序的元素对比都不同。

所以对于希尔排序时间复杂度,没法用数学求证
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
我们只能大致求出希尔排序的时间复杂度范围
如果你第一趟就用d=1,那么希尔排序会退化为插入排序,也就是时间复杂度为O(n2

而如果数据元素数量不多,在某个范围内,希尔排序效率可达到O(n1.3

而对于算法稳定性,希尔排序是不稳定的!
举个例子:
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

另外,由于希尔排序只能基于顺序表,因为你希尔排序要用增量d来快速找到从属与一个子表的各个元素。那么你必须要有随机访问的特性才能实现,所以必须是顺序表

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
ps:希尔排序代码考察频率不高,大多是给你一些元素和d,让你求每趟之后的元素序列。但你要学会算法思想,万一考到也可以现场写。

三、交换排序

3.1冒泡排序

3.1.1算法思想

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
从前往后(或者从后往前)两两比较相邻元素的值,如果两个元素为逆序,进行交换。比较完整个序列,称为一趟冒泡排序

举例说明,现要求对下面的序列进行排序,排序后元素递增

第一趟排序:(这里采用从后往前的方式,你用从前往后也可以)
我们会先对比最后两个元素,27<49,不需要交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
往前检查,13<27,不需要交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
往前检查,76>13,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

往前检查,97>13,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

往前检查,65>13,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

往前检查,38>13,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
往前检查,49>13,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
每一趟排序,我们都可以把当前需要排的序列中的最小值“冒”到最前面

第二趟处理也是一样的,大家自己看动画:
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
需要注意的是,在第二趟排序到1号位时,已经不需要和0号位比较了,因为第一趟已经确定0号是最小的,你比较到1号位置就可以了。

第二趟排序之后,我们就确定了最小的两个元素,把它们放到最左边
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第三趟排序,如下动图:
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
同理,第三趟时已经确定前面两个是最小了,对比到2号位置即可
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
后面以此类推,不再赘述。

需要注意的是,如果你的某一趟排序没有发生任何交换,那么就已经说明整体有序了,下面不用再浪费时间对比了,结束算法
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

3.1.2代码实现

//交换
void swap(int &a,int *b)
{
   int tmp =a;
   a=b;
   b=tmp;
}

//冒泡排序
void BubbleSort(int A[],int n){//这里是进行递增排序
    for(int i=0;i<n-1;i++){
		bool flag=false;//表示本趟冒泡排序是否发生交换的标志
		for(int j=n-1;j>i;j--){//一趟冒泡过程
			if(A[j-1]>A[j]){//出现逆序
				swap(A[j-1],A[j]);//交换两元素位置
				flag=true;
			}
		}
		if(flag==false){
			return;//本趟遍历后没有发生交换,说明表已经有序
		}
	}
}

注意:我们这里是左边元素大于右边元素才交换,如果相等是不交换的,
这样就保证了算法的稳定性
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

3.1.3算法性能分析

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
试问:冒泡排序是否可以用于链表?
答案是可以,比如想让下面这个链表递增有序

我们用L指向链头元素
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
我们可以用i指向元素和它后面一个元素进行对比,如果当前i指向元素更大,就进行交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
这样就完成了一趟排序,

后面的几趟排序也是一样的,不再赘述。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

3.2快速排序

3.2.1算法思想:

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
对于一个给定的待排序表,我们可以任选一个元素作为基准(或者枢轴),

比如在第一趟排序中,我们可以选49这个元素作为基准
在接下来经过一趟排序后,我们可以找到49这个元素的最终位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
显然,49这个元素在整个表中不是最大也不是最小的,我们需要把这个表划分为左右两个部分,左半部分所有元素<49,右半所有元素>=49。

这样的处理称为一次划分。

下面来看一下划分过程怎么做:
我们用low和high分别指向我们要处理的序列的头和尾两个位置,让49作为基准
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
我们用low和high把整个表的元素扫描一遍,在整个扫描过程中,我们要保证:
high所指的指针右边都是大于等于当前的基准元素49的
low所指的指针左边都是小于当前基准元素49的
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前low指向元素为空,先让high往左走,high所指元素49>=49,所以下标7的元素不需要移动

high–,high所指元素27<49,所有比49更小的元素都应该放到low指针所指位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

当前high指向元素为空,先让low往右走
此时low所指27<49不需要移动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
low++,38<49不需要移动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
low++,65>49,把65放到high所指位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前low指向元素为空,先让high往左走
high当前所指为65>49,不需要移动
high–,13<49,13放low位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前high指向元素为空,先让low往右走
low指向元素13<49,不需要移动
low++,97>49,97放到high位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

当前low指向元素为空,先让high往左走
high所指元素97,97>49不需要移动
high–,76>49不需要移动
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
high–,high和low到达同一位置,第一趟排序结束,把49放到low(high)所指位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
注意,此时既然已经确定了49的位置,那么我们下面排序就不需要再管49了,进行左右子表的排序,排序方法同上

先看左子表:
我们选择第一个27作为基准元素
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前low指向元素为空,先让high往左走
high所指元素13<27,把13换到low所指位置

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前high指向元素为空,先让low往右走
low所指元素13<27,不需要移动
low++,38>27,38放到high所指位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
当前low指向元素为空,先让high往左走
high所指元素38>27,不需要移动
high–,low和high指向同一位置,确定27所在位置,放27到low(high)的位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
而此时,下标0-2的子表又被我们划分为0-1和1-2两个部分
注意:这里两个部分仅仅只有1个元素了,所以也确定了13和38的最终位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是下标4-7子表处理,大家自己看动画,不再赘述:
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

3.2.2代码实现

快速排序算法代码实现是所有排序算法考察频率最高的,务必掌握

//用第一个元素将待排序序列划分为左右两个部分
int Partition(int A[],int low ,int high){
    int pivot=A[low];//用第一个元素作为枢轴
    while(low<high){
		while(low<high&&A[high]>=pivot){
			high--;
		}
		A[low]=A[high];//遇到A[high]<pivot的情况
		while(low<high&&A[low]<=pivot){
			low++;
		}
		A[high]=A[low];//遇到A[low]>pivot的情况
	}
	A[low]=pivot;
	return low;
}

//快速排序
void QuickSort(int A[],int low,int high){
   if(low<high){//递归跳出的条件
		int pivotpos=Partition(A,low,high);//划分
		QuickSort(A,low,pivotpos-1);//划分左子表
		QuickSort(A,pivotpos+1,high);//划分右子表
	}
}

3.2.3算法性能分析

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

四、选择排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

4.1简单选择排序

4.1.1算法思想

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
刚开始所有元素都是无序的,第一趟的处理我们会从左往右扫描,然后找到关键字值最小的一个元素。

显然,这里13是最小的,那我们把最小元素13和最前面一个位置进行交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第一趟排序结束,接下来,我们就不需要管0号位置了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第二趟,从头到尾扫描各个元素,然后把关键字最小的元素放在最前面(当前子序列最前面是1号位置)
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第二趟排序结束,前面两个位置就不用管了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
从剩余元素中找到最小的元素,放到最前面
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第三趟结束,前3个位置不用管了,从剩余元素中找最小的
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

这里需要注意,剩余元素中有2个最小的元素49,我们选中第一个扫描到的49
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

然后后面以此类推,大家自己看动画,不再赘述
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

对于最后一个元素,它就一个了,肯定就是它自己啊,不用额外考虑了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

4.1.2代码实现

//交换函数
void swap(int &a,int &b){
	int temp=a;
	a=b;
	b=temp;
}
//简单选择排序
void SelectSort(int A[],int n){
	for(int i=0;i<n-1;i++){//一共进行n-1趟
		int min=i;//记录最小元素位置
		for(int j=i+1;j<n;j++){//遍历选择最小元素
			if(A[j]<A[min]){//更新最小元素位置
				min=j;
			}
		}
		if(min!=i){
			swap(A[i],A[min]);
		}
	}
}

4.1.3算法性能分析

空间复杂度不必多说,也就定义了几个变量,所以空间复杂度O(1)

而对于时间复杂度,无论给出的序列是“全部有序”,“全部逆序”,还是“乱序”
你都需要n-1趟的处理
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
总共需要比较(n-1)+(n-2)+…+1=n(n-1)/2次
所以时间复杂度为O(n2

然后是算法的稳定性
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

4.2堆排序

4.2.1什么是堆?

堆排序是考研中又难又常考的
这种算法的实现基于一种叫做堆的数据结构,我们首先要探讨的就是什么是堆?

堆这种数据结构又分为“大根堆”、“小根堆”
如果直接看定义其实很难理解,我们还是具体举例子说明
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

比如说大根堆:L(i)>=L(2i)且L(i)>=L(2i+1)
假设我们现在i取1,那么这里87>78和32,
如果所有i都满足这个性质,则为大根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

比如说小根堆:L(i)<=L(2i)且L(i)<=L(2i+1)
假设我们现在i取1,那么这里9<17和65,
如果所有i都满足这个性质,则为小根堆

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里估计大家还是有点懵,那我们来复习一下以前讲过的二叉树的顺序存储

对于一个完全二叉树,如果按照层序一个节点一个节点的存到一个数组中,就是完全二叉树的顺序存储
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

并且这些结点在数组中的存储位置可以反映结点之间的逻辑关系
对于一个结点i,如果要找它的左孩子就是2i,如果要找右孩子就是2i+1

如果一个结点i,i>⌊n/2⌋,则说明i是叶子结点;i<=⌊n/2⌋,则说明i是非叶子结点
所有分支结点会存放在数组靠前的位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所有叶子结点存放在比较靠后的位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

知道这个之后,我们再来看刚才介绍的大(小)根堆

所谓的“堆”,从内存上看像一个连续存放的数组
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
但其实从逻辑视角看,堆是一棵顺序存储的完全二叉树
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
编号为1的结点就是这棵完全二叉树的根节点
然后数组下标i的结点,左孩子为2i,右孩子为2i+1
(1<=i<=n/2)
大根堆简化来说就是在这棵完全二叉树中,如果所有子树的根节点>=左孩子,根节点>=右孩子,那么这棵顺序存储的完全二叉树就是一个大根堆

同理可得小根堆:
小根堆简化来说就是在这棵完全二叉树中,如果所有子树的根节点<=左孩子,根节点<=右孩子,那么这棵顺序存储的完全二叉树就是一个小根堆

到这里大家就可以明白什么是堆啦!,下面回到我们主线剧情:怎么进行堆排序?

4.2.2如何进行堆排序?

我们之前介绍过,堆排序大类是属于选择排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
选择排序的基本思想就是我们会在待排序的这些元素中选取关键字最大(最小)的元素加入有序子序列。

对于大根堆来说,你选关键字最大的元素其实就非常方便,因为最大元素肯定是在根节点(也就是堆顶元素)啊

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
而如果从数组的视角看,肯定是第一个元素的值最大
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所以如果我们能把一个原始的数组整理成堆这种形式,那么我们想进行堆排序就很简单了

接下来要探讨的问题是:对于一个给定的初始序列,我们如何把它建立成大根堆,也就是根>=左、右

举个例子:现有如下初始序列和对应二叉树
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

既然要保证所有子树的根结点>=左、右
那么在这棵树中,我们应该检查所有的分支结点,因为所有的分支结点其实都是它所属的这个子树的根结点。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

因此,接下来要做的就是检查所有上面画了红框的分支结点
对于任何一个结点来说,如果它不满足根结点>=左、右,那我们就需要进行调整
ps:对于顺序存储的完全二叉树,分支结点(非终端结点)下标i<=⌊n/2⌋

该例中共8个结点,也就是n=8,那么我们只需要检查i<=⌊n/2⌋=4的结点即可

接下来我们会从后往前,也就是从i=4一直处理到i=1
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

第一个被处理的是4号结点,该结点是所有分支结点中编号最大的一个
我们现在来检查以4号结点为根的子树,看它是否满足大根堆的要求
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
我们知道i的左孩子是2i,右孩子2i+1
那么4的左孩子是8号结点,没有右孩子(一共就8个结点)

但是这里9<32,也就是根<左孩子
所有不满足大根堆,进行调整
调整方式:把当前结点和更大的孩子进行互换
由于这里只有一个左孩子,那么就和左孩子互换位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
那么4号位置的子树就满足大根堆了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来处理3号结点
78>65,根>左孩子
78<87,根<右孩子
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

那么进行调整,根和更大的孩子互换,也就78和右孩子互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
这样,3号子树也满足了大根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来处理2号结点,17<32,17<45
根和更大的孩子交换,也就是17和45互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
最后是1号结点,显然53<45,53<87,应该把53和87互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
但是这里大家会发现,53换下去之后,53的位置又不满足大根堆了

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
出现这种情况处理方法和前面一样,你就继续把小的往下换就是了

53左右孩子更大的是78,那你把53和78换一下位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所以,如果你在换位置过程中发现破坏了下一级的堆,那就让小的继续往下换,直到没法换(或者已经满足了)则调整结束
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到此为止,就让一个初始序列变成大根堆了

下面是建立大根堆的代码

//建立大根堆
void BuildMaxHeap(int A[],int len){
	for(int i=len/2;i>0;i--){
		HeadAdjust(A,i,len);
	}
}

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int k,int len){
	A[0]=A[k];//A[0]暂存子树的根结点
	for(int i=2*k,i<=len;i*=2){//沿key较大的子结点向下筛选
		if(i<len&&A[i]<A[i+1]){//比较左孩子和右孩子哪个更大,这里额外判断i<len是防止没有右孩子
			i++;//如果右孩子更大,i+1;左孩子更大则不会执行这一步
		}
		if(A[0]>=A[i]){//根节点值比最大的孩子还要大,那这棵子树就是满足大根堆的,不需要额外处理
			break;//筛选结束
		}
		else{
			A[k]=A[i];//将A[i]调整到双亲节点上
			k=i;//修改k值,以便继续向下筛选
		}
	}
	A[k]=A[0];//被筛选结点的值放到最终位置
}

现在已经知道了如何用代码建立大根堆,那么有了大根堆之后如何基于大根堆进行排序呢?

基于选择排序的思想,我们每一趟会把堆顶元素(最大的元素)加入到有序子序列中

具体做法:我们把堆顶元素和待排序序列中最后一个元素进行交换

举个例子:下面的大根堆中,堆顶元素就是87,和待排序序列中最后一个元素9进行交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
现在,就已经把最大的元素换到了数组的末尾,那么87这个位置就不需要再改了。

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
那么我们下面只要考虑上面的元素(除了87)就可以
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
在我们把9放到堆顶时,就已经不再是大根堆了,那我们要把9往下换
也就是调用我们前面讲的HeadAdjust函数

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int k,int len)
//传整个数组,k为当前要调整的结点编号,len是当前这个堆里还有多少元素

接下来就是让9不断向下换的过程,显然这里78更大,9和78换一下
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
这里继续向下换,9的左右孩子更大的是65,9和65换一下

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里,9已经到底部了,没法往下换了,那么调整结束

此时,上面的部分又恢复成了大根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到处位置就完成了第一趟的处理

第一趟的处理,我们把最大的元素放到最后,然后把剩余的元素恢复成一个大根堆。

接下来是第二趟的处理:
原理和之前类似,我们把堆顶和堆底的元素,也就是待排序序列中最后一个元素进行一个交换。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
然后和第一趟一样,对剩余元素进行调整(78不再考虑),变成大根堆
53现在在堆顶,不符合大根堆特性,我们把它往下换
53的左右孩子45和65,65更大,65和53换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里,53的左孩子9小于53,不用换了
这里78已结排好,不在考虑范围内

这样就把剩余的元素再次排成了一个大根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来是第三趟的处理:
把堆顶元素和堆底元素进行互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
换完之后要对剩余元素进行调整,变回大根堆
9的孩子45,53,显然53更大,让9和53互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里9已经没法往下换了,调整结束
65和78已结排好,不需要考虑

接下来是第四趟
堆顶和堆底元素互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
然后对剩余元素进行调整,变回大根堆
17两孩子45、9,显然45更大,45和17互换

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
17一个左孩子32,32>17,32和17互换
53已结排好了,不需要考虑
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

到这里17已结到底部了,调整结束
87已结排好,不需要考虑

再往下的处理大家自己看动画,不再赘述:
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
ps:你根据大根堆,得到递增序列;如果是小根堆,则是递减序列

下面是基于大根堆进行排序的代码

//建立大根堆
void BuildMaxHeap(int A[],int len)

//将以k为根的子树调整为大根堆
void HeadAdjust(int A[],int K,int len)

//堆排序的完整逻辑
void HeapSort(int A[],int len){
	BuildMaxHeap(A,len);//初始建堆
	for(int i=len;i>1;i--){//n-1趟的交换和建堆过程
		swap(A[i],A[1]);//堆顶元素和堆底元素交换
		HeadAdjust(A,1,i-1)//把剩余的待排序元素整理成堆
	}
}

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

4.2.3算法效率分析

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到目前为止,我们知道了整个堆排序总共需要分为两个大步骤,第一步是要建立一个初始的堆,你有了堆之后才能进行排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
而建堆时,我们需要调用下坠调整函数HeadAdjust
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
我们在排序时也同样要用到下坠调整函数HeadAdjust
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所以,要分析算法性能,必须分析下坠调整函数

举个例子,假设现在要调整9这个元素,我们要把它往下换
根据代码逻辑,我们第一步应该是先对比9这个元素的左右孩子,看它们谁更大
所以这里涉及了一次关键字对比
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

而确定哪个孩子更大之后,要把根阶段和这个大孩子再做一次对比
这里又涉及一次关键字对比

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

所以,如果一个结点有左右孩子,那么它往下换一层,总共需要对比关键字两次
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
接下来,9这个元素还要继续往下换,此时总共有8个元素,9这个元素编号是4,它的

左孩子编号是8,由于此时左孩子的编号和元素总数相等,不满足i<len,这里就不用比左孩子右孩子谁大了,就一个左孩子
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
9左孩子45,45>9,交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
所以,如果一个结点只有左孩子,那么它往下换一层,只需要对比关键字一次

综上
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
而我们之前说过,对于一个完全二叉树来说,如果它有n个结点,那么它的树高h应该是h=⌊log2n ⌋+1
如果忘记了这个性质,可以看笔者的树这个数据结构讲解

另外,对于完全二叉树,第i层应该是要有2i-1个结点
而我们建立一个初始堆时,最下面一层的结点是不需要调整的
我们只需要调整上面的h-1层就可以了

对于一个完全二叉树:
第一层:1个结点,向下换调整最多需要比较2*(h-1)
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
第二层:2个结点,总共需要2 * 2 * (h-2)次调整
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
后面的以此类推

一直到h-1层,一共2h-2个结点,需要调整2h-2 * 2
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

对整个式子累和得到下图的公式
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
想偷懒的话,这个推导就不看了,你记住建堆时间复杂度是O(n)就行

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

4.3堆的插入删除

上小节我们学习了什么是堆?如何进行堆排序?

该小节我们将补充如何在堆中进行插入和删除操作

4.3.1在堆中插入新元素

假设我们现在是小根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
假设我们现在要插入新元素13

我们先把新插入元素放到表尾位置,逻辑上看就是放在堆底
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

原先是小根堆,你现在插了新元素就不再是小根堆了(根<=左,右)

我们要做的就是把新插入的元素和父节点比较,如果新元素比父节点小,就往上换

要找父节点也很简单,当前节点位置i,那⌊i/2⌋就是父节点
当前13所在位置是9号,⌊9/2⌋=4,那就是和4号位的32进行交换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到了这层还不能结束,还要继续往上试探,看新插入的是否比父节点要小
这里13位置4号,父节点⌊4/2⌋=2,也就是2号位置的17
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

13<17,13和17互换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
然后继续往上试探,13位置2号,父节点是⌊2/2⌋=1,也就是1号位置的9
13>9,不用换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里就符合小根堆的要求了,整个过程对比3次

再举个例子,我们现在要插46,同样的,先放在堆底
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
这里46是10号位置,父节点是⌊10/2⌋=5,也就是5号位置的45
46>45,不用换
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里符合小根堆,整个过程发生一次对比

4.3.2在堆中删除元素

现有如下的小根堆
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
现要求把13删掉
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
删掉之后如何处理?
我们会用堆底的元素来代替被删元素
这里就是把46放到13以前的位置
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

然后为了让整体满足小根堆,我们让刚换上来的堆底元素不断往下换,直到没法换

这里也就是让46和它的两个孩子进行比较,让更小的上来

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
ps:关于对比关键字次数还是较常考的,这里注意一下
ps:关键字对比次数:先让左右孩子对比一次,然后选小的再和根对比一次。

再举个例子,现有如下小根堆,要求删除65
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

同样的,用堆底元素替换它,然后尝试往下换

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
换上去之后发现左右孩子都比46大,那就不用换了
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
ps:关键字对比次数:先让左右孩子对比一次,然后选小的再和根对比一次。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

五、归并排序

5.1算法思想

什么是归并排序?比如你现在有两个或者多个已经有序的序列,要求合并成一个
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

我们设置三个指针i,j,k,每次对比i和j所指元素,哪个更小就放到新数组里面

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
到这里需要注意,如果i和j指向的元素一样,这里就可以根据你自己写的代码决定让i所指还是j所指的元素过来了。

那这里,如果元素大小相同,我们让i所指的优先进大数组
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
关于二路归并
其实就是我们刚才上面介绍的过程,把两个有序序列合并成一个
对比i和j所指元素谁更大(更小),然后放到k位置

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

5.2手算模拟

如果给你一个初始序列,让你进行二路归并(归并排序),那我们会一开始把这个初始序列看成一个个单独的元素。
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

第一趟归并排序,我们把相邻的两个部分进行二路归并
比如01,23,45,6
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
ps:如果元素不管,比如6号位置没有别的和它归并了,你就直接把它进行下一轮

第二趟归并排序,我们基于第一趟的结果,再次进行二路归并,比如现在01已经有序,23已经有序。我们把01这个组合和23这个组进行二路归并
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

第三趟归并排序,我们在第二趟的基础上进行,把1-3组和4-6组进行二路归并
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

5.3代码实现

int *B=(int *)malloc(n*sizeof(int));//辅助数组B

//A[low...mid]和A[mid+1...high]各自有序,将两个部分归并
void Merge(int A[],int low,int mid,int high){
	int i,j,k;
	for(k=low;k<=high;k++){
		B[k]=A[k];//A中所有元素复制到B中
	}
	for(i=low,j=mid+1,k=i;i<=mid&&j<=high;k++){
		if(B[i]<=B[j]){
			A[k]=B[i++];//较小值复制到A中
		}
		else{
			A[k]=B[j++];
		}
	}
	while(i<=mid){
		A[k++]=B[i++];
	}
	while(j<=high){
		A[k++]=B[j++];
	}
}

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序

5.4算法效率分析

考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序
考研数据结构:第八章 排序,数据结构专栏,数据结构,插入排序,冒泡排序,堆排序,选择排序,归并排序文章来源地址https://www.toymoban.com/news/detail-680261.html

到了这里,关于考研数据结构:第八章 排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第八章结构型模式—装饰者模式

    结构型模式描述如何将类或对象按某种布局组成更大的结构,有以下两种: 类结构型模式 :采用继承机制来组织接口和类。 对象结构型模式 :釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足 “合成复用原则”,所以对象结构型模式比类结

    2024年02月05日
    浏览(49)
  • 【数据结构专栏】动态扩容顺序栈详解

      💌 博客内容:顺序栈的原理详解 😀 作  者:陈大大陈 🚀 个人简介:一个正在努力学技术的准前段,专注基础和实战分享 ,欢迎私信! 💖 欢迎大家:这里是CSDN,我总结知识和写笔记的地方,喜欢的话请三连,有问题请私信 😘 😘 😘 目录 顺序栈的定义 结构体定义

    2023年04月09日
    浏览(26)
  • 王道考研数据结构——链表

    找到头节点就相当于找到了整个链表 Linklist Lnode*是一个东西 大部分使用的带头结点,比较方便!带头结点只维护指针域,不维护数据域 找前驱节点+插入节点(可以单独封装成一个函数)  如果不带头节点的话,那么插入和删除头节点的话都需要特殊处理,即重新修改头指针的

    2024年02月16日
    浏览(36)
  • 数据结构【考研笔记】

    1、基本概念 1)数据 数据是 信息的载体 ,是描述客观事物属性的数、字符及所有 能输入到计算机中并被计算机程序识别和处理的符号 的集合。数据是计算机程序加工的原料。 2)数据元素、数据项 数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。一个数据

    2024年02月12日
    浏览(34)
  • 考研真题数据结构

    【2018年山西大学真题】试编写一个算法,使之能在数组L【1~n】中找到最小元素。 (1)给出算法的基本思想。 (2)根据设计思想,给出描述算法。 (3)分析所给算法的时间复杂度。 (1)算法基本思想: 1. 假设数组中的第一个元素为当前的最小元素,将其保存在一个变

    2024年02月04日
    浏览(31)
  • 24考研数据结构-——绪论2

    1.4.1 渐近时间复杂度 一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数f(n),算法的时间量度记作T(n)=O(n),它表示随问题规模n的增大而增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的 渐近时间复杂度 ,简称时间复杂度。 大O表示“同阶”,

    2024年02月16日
    浏览(30)
  • 考研数据结构--栈和队列

    内容 栈 :栈的抽象数据类型定义、栈的存储表示及基本操作实现、栈的应用 栈的定义(特点) 栈是一种后进先出(LIFO)的线性表,只能在一端进行插入和删除操作,这一端称为栈顶,另一端称为栈底。 打个比方: 有一个胡同很窄只能通过一辆车,而且是死胡同,只能从胡

    2023年04月19日
    浏览(28)
  • 24考研数据结构-图的存储结构邻接矩阵

    【1】顶点的结点结构 ——————— | data | firstarc | ——————— data数据域:储存顶点vi firstarc链域:指向链表中第一个结点 【2】弧的结点结构 —————————— | adjvex | info | nextarc | —————————— adjvex邻接点域:与顶点vi邻接的点在图中的位置 info数据域

    2024年02月14日
    浏览(43)
  • 考研数据结构:第七章 查找

    ps:查找表可以是线性结构、树状结构、图状结构等等 评价一个查找算法的优劣:主要看 算法的平均查找长度ASL 举个例子,我们现在有如下二叉排序树 如果你要查的是50,那么从根节点出发只需要对比一次就可以了,所以第一项是1 * 1 如果你要查的是第二层的26或者

    2024年02月13日
    浏览(40)
  • 王道考研数据结构--2.单链表

    1.前言 2.难点 2.1c和c++的引用转换 2.2引入头结点的好处 2.3头插法和尾插法 3.代码段 3.1C语言自定义bool操作 3.2单链表结构体定义 3.3创建新节点 3.4头插法和尾插法 3.5查找 3.6按位序插入 3.7后插和前插 3.8删除 3.9求表长 3.10遍历输出单链表 4.完整代码 日期:2023.6.21 书籍:2024年数据

    2024年02月09日
    浏览(79)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包