内部排序算法总结

概要

  • 考点:算法分析、代码掌握
  • 八大排序如下:
内部排序
插入排序
交换排序
选择排序
归并排序
不基于比较的排序
基数排序
直接插入排序
折半插入排序
希尔排序
冒泡排序
快速排序
简单选择排序
堆排序

一、排序的定义

  • 稳定性:是指相同key的的元素排序后次序是否改变。(注意:通过举例说明)
  • 基本流程:分为比较移动,通过比较两个关键字的大小,确定元素的先后关系,然后通过移动元素达到有序。

二、插入排序

1、直接插入排序

算法流程 :

  1. 查找出L(i)L[1...i-1]中的插入位置K
  2. L[K...i-1]中的所有元素依此后移一个位置
  3. L(i)复制到L(k)

不带哨兵

  • 从第二个元素开始与有序元素进行比较
  • 设:表长为n,以排好序的表长为m
  • 待插入元素最坏比较次数为m,最好情况为比有序表中最后一个元素还要大,比较次数为1
  • 确定比较时每次都要判断是否越界j>=1
int Insert_Sort(shu a){
	int i,j,temp;
	for(i=2;i<=a.len;i++){//第一个元素自身默认为排好序的元素,从第二个元素开始
		if(a.num[i]<a.num[i-1]){
			temp=a.num[i];
			for(j=i-1;j>=1 && a.num[j]>temp;j--){
				a.num[j+1]=a.num[j];
			}
			a.num[j+1]=temp;
		}
	}
	my_print(a);
	return 1;
}

带哨兵

  • 将带插入元素存入A[0]
  • 最坏情况带插入元素与A[0]比较,不小于自身,所以插入A[0]之后
  • 好处是不需要每次判断是否越界
  • 最大比较次数为n+1(因为要与A[0]比较),最好为1
带哨兵的直接插入排序
int Insert_Sort_flag(shu a){
	int i,j;
	for(i=2;i<=a.len;i++)                 //从第二个元素开始遍历到最后一个元素
		if(a.num[i]<a.num[i-1]){			//如果当前元素小于前驱元素
			a.num[0]=a.num[i];				//赋值到哨兵
			for(j=i-1;a.num[0]<a.num[j];--j)//所有比哨兵大的元素右移
				a.num[j+1]=a.num[j];
			a.num[j+1]=a.num[0];			//最后j指向比哨兵小的第一个元素
	}
	my_print(a);
	return 1;
}

性能分析

  • 空间效率:仅使用了常数个辅助单元,因而空间复杂度: O ( n ) O(n) O(n)
  • 时间效率:
    • 在排序过程中,向有序子表中逐个地插入了元素的操作进行了 n − 1 n-1 n1趟,每趟操作都分为比较关键字和移动元素,而比较次数和移动次数取决于待排序的初始状态
    • 最好情况下,表中有序,只要比较 1 1 1次不要移动,时间复杂度为: O ( n ) O(n) O(n)
    • 最坏情况下,表中逆序,总的比较次数达到最大,为 ∑ i = 2 n i \sum_{i=2}^ni i=2ni ,总的移动次数也达到最大,为 ∑ i = 2 n ( i + 1 ) \sum_{i=2}^n(i+1) i=2n(i+1)
    • 平均情况下,表中元素是随机的,取平均值作为时间复杂度,总的比较次数与总的移动次数均约为 n 2 4 \frac{n^2}{4} 4n2,因此平均时间复杂度为 O ( n 2 ) O(n^2) O(n2)
    • 稳定性:因为是先比较后移动,所以从判断条件来看,算法是一个稳定的算法
    • 适用性:可以顺序可以链式,为链式时,从后往前查找指定元素的位置

2、折半插入排序

算法流程

  1. 折半查找待插入位置k
  2. 移动k+1之后的元素
//折半插入排序
int Insert_Sort_half(shu a){
	int i,j,low,high,mid;
	for(i=2;i<=a.len;i++){		//从第二个元素开始遍历到最后一个元素
		a.num[0]=a.num[i];		//暂时存入哨兵
		low=1;high=i-1;			//折半查找的范围
		while(low<=high){		//折半查找
			mid=(low+high)/2;
			if(a.num[mid]>a.num[0])
				high=mid-1;
			else
				low=mid+1;
		}						//*high最后指向的是比flag小的第一个元素
		for(j=i-1;j>=high+1;--j)
			a.num[j+1]=a.num[j];
		a.num[high+1]=a.num[0];
	}
	my_print(a);
	return 0;
}

性能分析

  • 减少了比较元素的次数,约为 O ( n ∗ log ⁡ 2 n ) O(n*\log{2}{n}) O(nlog2n),该比较次数与待排序表的初始状态无关,仅与表中元素个数n有关。
  • 而元素的移动次数并未改变,依然依赖于待排序表的初始状态。
  • 因此时间复杂度仍然未 O ( n 2 ) O(n^2) O(n2)
  • 对于数据量不大的表,性能发挥很好
  • 仍然时一个稳定的算法

3、希尔排序

算法流程

  1. 先取一个小于 n n n的步长 d 1 d_{1} d1,把表中的全部记录分成 d 1 d_1 d1组,所有距离为 d 1 d_1 d1倍数的记录放在一组,在组内进行插入排序。
  2. 然后取第二个步长 d 2 < d 1 d_2<d_1 d2<d1,重复上述过程。
  3. 直到 d t = 1 d_t=1 dt=1。即所有记录都在一组中,再进行直接插入排序,由于此时已经具有良好的局部有序性,所以很快会有很好的结果。
//希尔排序
int Sheel_Sort(shu a){
	int d,i,j;
	for(d=4;d>=1;d=d/2){			//增量递减
		for(i=d+1;i<=a.len;i++){	//对子表插入排序
			if(a.num[i]<a.num[i-d]){//小于子表中的前驱元素
				a.num[0]=a.num[i];	//存入a[0]
				for(j=i-d;j>0 && a.num[0]<a.num[j];j-=d){//子表元素后移
					a.num[j+d]=a.num[j];
				}
				a.num[j+d]=a.num[0];
			}
		}							//对所有子表排序完成
	}
	my_print(a);
	return 0;
}

假设序列如下:

  • d 1 = 5 d_1=5 d1=5
13
27
38
48
49
55
65
76
97
000
001
002
003
004
005
006
007
008
009
04
  1. 首先i=5;A[ i i i]=13;A[ i − d 1 i-d_1 id1]=49;A[0]存入13;
  2. 遍历子表[A[0],A[1],A[5]],找到A[5]的插入位置
  3. 现在序列变为:
13
27
38
48
49
55
65
76
97
000
001
002
003
004
005
006
007
008
009
04

注意:代码实现中,默认每个子表的第一个元素自身有序,从第一个子表的第二个元素开始遍历,直到遍历完所有子表的第二个元素,目标是实现每个子表的前2个元素形成有序序列。然后开始遍历每个子表中第 3... d 1 3...d_1 3...d1个元素。

性能分析

  • 常数个辅助单元,空间复杂度: O ( 1 ) O(1) O(1)

  • 最好情况下时间复杂度 O ( n 1.3 ) O(n^{1.3}) O(n1.3)(无法证明)

  • 最坏情况下时间复杂夫 O ( n 2 ) O(n^2) O(n2)

  • 适用性:仅适用于顺序存储

  • 稳定性:如果相同关键词的元素被分到了不同子表,可能会改变相对位序,所以不稳定,例如:

    1
    2
    9
    2*

    d = 2 d=2 d=2时:第一趟排序结果如下:

    1
    2
    9
    2*

三、交换排序

1、冒泡排序

算法流程(个人理解):

  1. 相当于构造一个长度为2的滑动窗口,窗口内要做的就是保证窗口内有序
  2. 窗口左端从第一个元素出发,直到窗口右端包含第 n − i + 1 n-i+1 ni+1个元素位置完成一趟排序(n为数组长度,i为当前趟数)
  3. 终止条件:
    • 在某一趟中窗口内一次交换都没有发生
    • n-i-1=1
//冒泡排序
int Bubble_Sort(shu a){
	int i,j,temp;
	int flag;
	for (j=1;j<=a.len-1;j++){
		flag=0;
		for (i=1;i<=a.len-j;i++){
			if (a.num[i+1]<a.num[i]){//如果逆序,发生交换
				flag=1;
				temp=a.num[i+1];
				a.num[i+1]=a.num[i];
				a.num[i]=temp;
			}
		}

		if (flag=0)
			break;
	}
	my_print(a);
	return 1;
}

性能分析

  • 常数个辅助单元,空间复杂度 O ( 1 ) O(1) O(1)
  • 最好情况下:序列有序,从第一个元素遍历到第n-1个元素,比较了n-1次,元素移动了0次,时间复杂度 O ( n ) O(n) O(n)
  • 最坏情况下:序列逆序,需要进行n-1趟排序,第i趟排序要进行n-i次比较,每次比较后,移动元素三次来交换元素。
    • 比较次数= ∑ i = 1 n − 1 ( n − i ) = n ( n − 1 ) 2 \sum_{i=1}^{n-1}(n-i)=\frac{n(n-1)}{2} i=1n1(ni)=2n(n1),
    • 移动次数= ∑ i = 1 n − 1 3 ( n − i ) = 3 n ( n − 1 ) 2 \sum_{i=1}^{n-1}3(n-i)=\frac{3n(n-1)}{2} i=1n13(ni)=23n(n1).
  • 因此最坏时间复杂度=平均时间复杂度= O ( n 2 ) O(n^2) O(n2)
  • 稳定性:算法稳定-相等元素不发生交换

2、快速排序

算法流程

  1. 在排序表 L [ 1... n ] L[1...n] L[1...n] 中选取一个基准元素 privot ,通过一次划分将表分为 L [ 1... k − 1 ] L[1...k-1] L[1...k1] L [ k + 1... n ] L[k+1...n] L[k+1...n] 左边元素全部小于 privot ,右边元素全部大于 privot ,k位置放入privot
  2. 分别再对左右区间进行划分
  3. 递归终止条件为:长度为1
void Quick(int begin,int end,shu &a){
	if (begin>end)
		return ;

	int flag=a.num[begin];
	int low=begin;
	int high=end;
	while (low<high){
		while (flag<=a.num[high] && low<high){
			high--;
		}
		if (low<high)
			a.num[low]=a.num[high];
		while (flag>a.num[low] && low<high){
			low++;
		}
		if (low<high)
			a.num[high]=a.num[low];
	}
	a.num[low]=flag;
	Quick(begin,low-1,a);
	Quick(low+1,end,a);
}

性能分析

  • 空间复杂度:递归栈的开辟,最好情况深度= O ( log ⁡ 2 n ) O(\log{2}{n}) O(log2n)(每次都分一半),最坏情况深度= O ( n ) O(n) O(n)(每次基准都为最大/最小元素),平均情况下深度= O ( log ⁡ 2 n ) O(\log{2}{n}) O(log2n)

  • 时间效率:递归树分析,

    • 最坏情况为:每次基准为最大/最小元素,递归树深度为 n n n,每层发生 O ( n ) O(n) O(n)次交换,时间复杂度= O ( n 2 ) O(n^2) O(n2)

    • 最好情况为:每次划分的部分不超过 n 2 \frac{n}{2} 2n的长度,递归树深度为 O ( log ⁡ 2 n ) O(\log{2}{n}) O(log2n),每层发生 O ( n ) O(n) O(n)次交换,时间复杂度= O ( n log ⁡ 2 n ) O(n\log{2}{n}) O(nlog2n)

    • 注意:平均情况是接近与最佳情况的

    • 数组原本有序或逆序,每次选择的基准均为最大/最小值,此时复杂度最高

    • 稳定性:不稳定,例如以下划分:

      6
      10
      11
      6*
      6
      11
      NN
      6*
      6
      11
      6*
      NN
      6
      11
      6*
      NN
      6
      10
      11
      6*

拓展:寻找序列中第K大的数

原理:根据快排原理,每次选出pivot元素,数组都被分为了三部分, A [ l . . . p − 1 ] A[l...p-1] A[l...p1] A [ p ] A[p] A[p] A [ p + 1... r ] A[p+1...r] A[p+1...r],如果K=p,A[p]即为第k大元素,若k<p,说明第k大元素在左边的部分,此时应该在左边 A [ l . . . p − 1 ] A[l...p-1] A[l...p1]中继续查找第k大元素,若k>p,说明第k大元素在右边的部分,此时应该在左边 A [ p + 1... r ] A[p+1...r] A[p+1...r]中继续查找第k大元素

int findKMax(int a[],int low,int high,int k) {
	int mid = divide(a, low, high);
	//包括a[mid]的右半边长度
	int length_of_right = high - mid + 1;
	if (length_of_right == k) return a[mid];
	else if (length_of_right > k) {
		//右半边长度比k长,说明第k大的元素还在右半边,因此在右半边找
		return findKMax(a, mid + 1, high, k);
	}
	else {
		return findKMax(a, low, mid - 1, k - length_of_right);
	}
}

四、选择排序

1、简单选择排序

算法流程

  1. 排序表为 L [ 1... n ] L[1...n] L[1...n]
  2. 第i趟从 L [ i . . . n ] L[i...n] L[i...n]中选择关键字最小的元素与 L ( i ) L(i) L(i)交换
  3. 每一趟确定一个元素的最终位置,经过n-1趟可以使表有序
//简单选择排序
int Select_Sort(shu a){
	for(int i=1;i<=a.len;i++){		//找到未排序表中最小的元素,下标记为min
		int min=i;
		for(int j=i+1;j<=a.len;j++){
			if (a.num[j]<a.num[min])
				min=j;
		}

		if(min!=i){
			a.num[0]=a.num[min];
			a.num[min]=a.num[i];
			a.num[i]=a.num[0];
		}

	}
	my_print(a);
	return 0;
}

性能分析

  • 空间复杂度:常数个辅助单元, O ( n ) O(n) O(n)

  • 时间复杂度:

    • 移动次数不超过 3 ( n − 1 ) 3(n-1) 3(n1)次,最好的情况是0次。
    • 每次都要遍历剩下的表找到最小的元素,比较次数与序列状态无关,始终是 ∑ i = 2 n i = n ( n − 1 ) 2 \sum_{i=2}^{n}i=\frac{n(n-1)}{2} i=2ni=2n(n1)
    • 时间复杂度= O ( n 2 ) O(n^2) O(n2)
    • 稳定性:在第i趟找到最小元素之后,和第i个元素交换,可能会导致被交换的元素与其关键字相同的元素位置发生改变。例:
    1
    6
    8
    6*
    1
    6
    8
    6*

2、堆排序

完全二叉树的性质

  • i i i的左孩子– 2 i 2i 2i
  • i i i的右孩子– 2 i + 1 2i+1 2i+1
  • i i i的父亲– i / 2 i/2 i/2向下取整
  • i i i的层次– ⌊ log ⁡ 2 ( n + 1 ) ⌋ \lfloor\log{2}{(n+1)}\rfloor log2(n+1) 或者 ⌊ log ⁡ 2 n ⌋ + 1 \lfloor\log{2}{n}\rfloor+1 log2n+1
  • i i i是否有左孩子– ( 2 i < = n ) ? (2i<=n)? (2i<=n)?
  • i i i是否有右孩子– ( 2 i + 1 < = n ) ? (2i+1<=n)? (2i+1<=n)?
  • i i i是否是叶子/分支节点– ( i > ⌊ ( n / 2 ) ⌋ ) ? (i>\lfloor(n/2)\rfloor)? (i>⌊(n/2)⌋)?
1
2
3
4
5
6
7
8
9

堆排序算法流程

  1. 初始序列转化为大根堆
  2. 在第 i i i趟排序过程中,将堆顶(最大的值)元素与 L ( i ) L(i) L(i)交换,此时 L [ i . . . n ] L[i...n] L[i...n]是排序完成的部分,后续不考虑此部分。
  3. 由于堆顶元素被交换,此时序列不是大根堆,将 L [ 1... i − 1 ] L[1...i-1] L[1...i1]重新调整为大根堆–HeapAdjust(a,1,i-1);
  4. 重复2、3步骤,直到有序

建立大根堆算法流程

  1. 设序列长度为 n n n
  2. 从第一个非叶子节点的节点K开始遍历(上图中是4号),temp暂时保持K的值,用K最大的孩子(记为G)取代K的位置
  3. 此时,若将K赋值到其孩子节点,那么有可能会导致其孩子节点为根的子树构不成大根堆,于是暂定K节点取代G节点,再次以K节点为根,建立大根堆
  4. 重复2、3,可以理解为K不断下坠

代码实现

  • 以k为根,建立大根堆( L [ 1... l e n ] L[1...len] L[1...len])
//堆排序
void HeapAdjust(shu &a,int k,int len){
	a.num[0]=a.num[k];						//A[0]暂存子树的根节点
	for(int i=2*k;i<=len;i*=2){				//以k为节点向下筛选,如果发生了交换,那么以
		if(i<len && a.num[i]<a.num[i+1])
			i++;							//取key较大的节点下标
		if(a.num[0]>=a.num[i])				//筛选结束
			break;
		else{
			a.num[k]=a.num[i];				//将A[i]调整到双亲节点上
			k=i;							//修改k值,以便继续向下筛选
		}
	}
	a.num[k]=a.num[0];						//被筛选的节点放入最终位置
}
  • 从最后一个分支节点开始向上建立大根堆
void BulidMaxHeap(shu &a){
	for(int i=a.len/2;i>0;i--){				//调整所有非终端节点
		HeapAdjust(a,i,a.len);
	}
}
  • 堆排序
int Heap_Sort(shu a){
	BulidMaxHeap(a);						//初始化大根堆
	for (int i=a.len;i>1;i--){
		int temp=a.num[i];					//将堆顶元素与元素i交换
			  								//(相当于把无序表中最大的元素放到有序表的第一个元素)
		a.num[i]=a.num[1];
		a.num[1]=temp;
		HeapAdjust(a,1,i-1);				//大于i以后的元素有序,无序参加堆的重新构建
											//继续将最大的元素放入堆顶
	}
	my_print(a);
	return 1;
}

性能分析

  • 空间效率:仅使用了常数个辅助单元,空间复杂度= O ( 1 ) O(1) O(1)
  • 时间效率:建堆时间为 O ( n ) O(n) O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为 O ( h ) O(h) O(h), h h h为堆的深度( h = log ⁡ 2 n h=\log{2}{n} h=log2n),所有最好最坏平均状态下时间复杂度 = O ( n log ⁡ 2 n ) =O(n\log{2}{n}) =O(nlog2n)
  • 稳定性:不稳定,筛选时,可能会改变相对位置,例如 L L L={1,2+,2},建堆时 L L L可能变为{2+,1,2},排序时,2+作为堆顶最先输出,也就是最坏结果为{1,2,2+}

堆的插入删除

  • 插入时,新元素放在堆底,不断向上筛选
  • 删除时,堆底元素取代被删除位置,以该元素为根调整堆

五、其他排序

1、归并排序

算法流程

(不是基于插入、交换、选择的排序算法,但仍然是基于比较的排序算法)

  1. n n n个元素组成的表 L ( n ) L(n) L(n)看作 n n n个有序表(每个表长度为1)
  2. 然后两两归并,得到 n 2 \frac{n}{2} 2n(向上取整)个长度为1或2的有序表
  3. 继续两两归并
  4. 直到合成一个长度为 n n n的有序表
void Merge(shu &a,int low,int mid,int high){	//将A[low:mid]与A[mid:high]两个数组合并为A
	int B[high+1];
	int i,j,k;
	for(k=low;k<=high;k++){
		B[k]=a.num[k];						//将a中所有元素复制到B
	}
	for(i=low,j=mid+1,k=low;i<=mid&&j<=high;k++){
		if(B[i]<=B[j])						//比较B左右两端中的元素
			a.num[k]=B[i++];				//较小的复制到a中
		else
			a.num[k]=B[j++];
	}
	while (i<=mid) a.num[k++]=B[i++];			//存在某个表没有被检查完全做以下处理
	while (j<=high) a.num[k++]=B[j++];
}

Merge()函数的作用是将两个相邻的有序表归并成一个有序表,每次归并时需要用到一个辅助数组 B B B.

void MergeSort(shu &a,int low,int high){
	if(low<high){
		int mid=(low+high)/2;	//从中间划出两个子表
		MergeSort(a,low,mid);	//对左侧子表递归地排序
		MergeSort(a,mid+1,high);//对右侧子表递归地排序
		Merge(a,low,mid,high);	//将左右子表归并

	}
}

MergeSort()函数的思想是:

  • 采用了递归(分治法)的思想
  • 将排序 L ( n ) L(n) L(n)的问题转为排序 L ( 1.. m i d ) L ( m i d . . n ) L(1..mid)L(mid..n) L(1..mid)L(mid..n)这两个字表的问题,然后再将这两个子表归并成一个有序的表
  • 同时解决排序上述两个子表的问题又递归地变成了排序子表的子表的问题

性能分析

  • 空间效率:Merge()操作中,辅助空间刚好为n个单元,所以算法的空间复杂度= O ( n ) O(n) O(n)

  • 时间效率:(使用递归树分析)

    • 递归树每一层的归并要遍历到所有元素,每一层的时间复杂度为 O ( n ) O(n) O(n)
    • 递归树一共有 log ⁡ 2 n \log_{2}{n} log2n(向上取整)层
    • 所有总的时间复杂度为 O ( n log ⁡ 2 n ) O(n\log_{2}{n}) O(nlog2n)
  • 稳定性:Merge()操作不会改变相同关键字的相对位置,因此算法时稳定的

  • 注意:一般而言,对于N个元素进行K路归并排序时,排序的趟数m满足 k m = n k^m=n km=n,从而又考虑到m为整数,所以 m = log ⁡ k N m=\log_{k}{N} m=logkN(向上取整),同时二路归并也是满足这些性质。

2、基数排序

  • 不基于比较和移动排序,而是基于关键字各位的大小进行排序
  • 基数排序时一种借助多关键字排序的思想对单逻辑关键字进行排序的方法
  • 分为两种方法:
    • 最高位优先(MSD)法,按关键字位权重递减一次逐层划分成若干个更小的子序列,最后将所有子序列依此连成一个有序序列
    • 最低位优先(LSD)法,按关键字位权重递增依次排序,最后将所有子序列依此连成一个有序序列

算法流程(基于手算理解)

  1. 假设对以下10个序列进行递减排序
111
168
211
233
438
520
666
888
985
996
007
  1. 第一趟基于个位进行分配(注意队列顺序此处不同)
111
168
211
233
428
520
666
888
985
996
Q9
Q8
Q7
007
Q6
Q5
Q4
Q3
Q2
Q1
Q0
  1. 第一趟收集:得到按”个位“递减排序的序列
111
168
211
233
438
520
666
888
985
996
007
  1. 第二趟分配+收集:得到按”十位“递减排序的序列,同时相同的”十位“按”个位“递减排序
111
168
211
233
438
520
666
888
985
996
007
  1. 第三趟分配+收集:得到按”百位“递减的序列,若”百位”相同则按“十位”递减排序,“十位”还相同则按“个位”递减排序
111
168
211
233
438
520
666
888
985
996
007

术语解释

  • 假设长度位 n n n的线性表中每个节点 a j a_j aj的关键词有 d d d元组 ( k j d − 1 , k j d − 2 , k j d − 3 , . . . , k j 0 ) (k_j^{d-1},k_j^{d-2},k_j^{d-3},...,k_j^{0}) (kjd1,kjd2,kjd3,...,kj0)组成,其中 0 < = k j i < = r − 1 ( 0 < = j < n , 0 < = i < = d − 1 ) 0<=k_j^i<=r-1(0<=j<n,0<=i<=d-1) 0<=kji<=r1(0<=j<n,0<=i<=d1),其中 r r r被称为基数 k j d − 1 k_j^{d-1} kjd1最高位关键字 k j 0 k_j^{0} kj0最低位关键字
  • 上例中: r − 1 = 9 r-1=9 r1=9, r = 10 r=10 r=10,最高位关键字为百位,最低位关键字为个位。

性能分析

  • 空间效率:一趟排序中需要的辅助存储空间为 r r r( r r r个队列: r r r个头指针, r r r个尾指针),队列可以重复使用,空间复杂度= O ( n ) O(n) O(n)
  • 时间效率:需要 d d d趟分配和收集,一趟分配需要 O ( n ) O(n) O(n),一趟收集需要 O ( r ) O(r) O(r),所以时间复杂度为 O ( d ( n + r ) ) O(d(n+r)) O(d(n+r)),与序列初始状态无关
  • 稳定性:对于基数排序来说,很重要的一点就是按位排序时必须是稳定的,所以算法整体是稳定的

总结

算法间的比较

算法种类最好时间复杂度平均时间复杂度最坏时间复杂度空间复杂度稳定性
直接插入排序 n n n n 2 n^2 n2 n 2 n^2 n2 1 1 1Y
冒泡排序 n n n n 2 n^2 n2 n 2 n^2 n2 1 1 1Y
简单选择排序 n 2 n^2 n2 n 2 n^2 n2 n 2 n^2 n2 1 1 1N
希尔排序 1 1 1N
快速排序 n log ⁡ 2 n n\log_{2}{n} nlog2n n log ⁡ 2 n n\log_{2}{n} nlog2n n 2 n^2 n2 n log ⁡ 2 n n\log_{2}{n} nlog2nN
堆排序 n log ⁡ 2 n n\log_{2}{n} nlog2n n log ⁡ 2 n n\log_{2}{n} nlog2n n log ⁡ 2 n n\log_{2}{n} nlog2n 1 1 1N
二路归并排序 n log ⁡ 2 n n\log_{2}{n} nlog2n n log ⁡ 2 n n\log_{2}{n} nlog2n n log ⁡ 2 n n\log_{2}{n} nlog2n n n nY
基数排序 d ( n + r ) d(n+r) d(n+r) d ( n + r ) d(n+r) d(n+r) d ( n + r ) d(n+r) d(n+r) r r rY

注意:根据待排序序列的状态选择不同的排序算法
(待补充)

算法种类最好情况最坏情况适用性比较次数移动次数
直接插入排序基本有序
冒泡排序基本有序
简单选择排序基本有序
希尔排序
快速排序划分平衡有序或者逆序
堆排序
二路归并排序
基数排序

例题


  • 对n个元素的顺序表采用直接插入排序算法进行排序,在最坏情况下所需要的比较次数是__;在最好情况下所需的比较次数是__.
  A.n-1           B.n=1           C.n/2           D.n(n-1)/2

分析:最坏情况下第 i i i个元素要对有序表中 i − 1 i-1 i1个元素进行比较。总比较次数为 ∑ i = 2 n ( i − 1 ) = 1 + . . . + n − 1 = n ( n − 1 ) 2 \sum_{i=2}^{n}(i-1)=1+...+n-1=\frac{n(n-1)}{2} i=2n(i1)=1+...+n1=2n(n1)。最好情况下第 i i i个元素只要与 1 1 1个元素比较,总的比较次数为 ∑ i = 2 n 1 = n − 1 \sum_{i=2}^{n}1=n-1 i=2n1=n1

  • 对任意7个关键字进行基于比较的排序,至少进行( )次关键字的比较
A.13    B.14    C.15    D.6
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值