数据结构——排序 1

排序的基本概念

排序
  将一组杂乱无章的数据按一定规律顺次排列起来,即将无序序列排成一个有序序列(由小到大或由大到小)的运算。

  • 如果参加排序的数据结点包含多个数据域,那么排序往往是针对某个域而言。

排序方法的分类

按存储介质可分为:
  内部排序:数据量不大、数据在内存,无需内外存交换数据
  外部排序:数据量较大、数据在外存(文件排序)

  外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂的多。

  
按比较器个数可分为:
  串行排序单处理机(同一时刻比较一对元素)
  并行排序多机处理(同一时刻比较多对元素)

按主要操作可分为:
比较排序:用比较的方法
  插入排序、交换排序、选择排序、归并排序

基数排序:不比较元素的大小,仅仅根据元素本身的取值确定其有序位置

按辅助空间可分为:
原地排序:辅助空间用量为 O ( 1 ) O(1) O(1) 的排序方法
(所占的辅助存储空间与参加排序的数据量大小无关)

非原地排序:辅助空间用量超过 O ( 1 ) O(1) O(1) 的排序方法

按稳定性可分为:
稳定排序:能够使任何数值相等的元素,排序以后相对次序不变
非稳定排序:不是稳定排序的方法
在这里插入图片描述
在这里插入图片描述

排序的稳定只对结构类型数据排序有意义。

在这里插入图片描述

按自然性可分为:
自然排序:输入数据越有序,排序的速度越快的排序方法
非自然排序:不是自然排序的方法

学习内容:

  • 按排序依据原则

    • 插入排序:直接插入排序、折半插入排序、希尔排序
    • 交换排序:冒泡排序、快速排序
    • 选择排序:简单选择排序、堆排序
    • 归并排序:2-路归并排序
    • 基数排序
  • 按排序所需工作量

    • 简单的排序方法:T(n) = O(n2)
    • 先进的排序方法:T(n) = O(nlogn)
    • 基数排序:T(n) = O(d.n)


顺序表存储结构

#define MAXSIZE 20		//设记录不超过20个
typedef int KeyType;	//设关键字为整型量(int型)
Typedef struct{			//定义每个记录(数据元素)的结构
	key Type key;		//关键字
	InfoType otherinfo;	//其他数据项
}RedType;
Typedef struct{				//定义顺序表的结构
	RedType r[MAXSIZE + 1];	//存储顺序表的向量
							//r[0]一般作哨兵或缓冲区
	int length;		//顺序表长度
}SqList;


插入排序


基本思想:
  每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,知道对象全部插入为止。

  即边插入边排序,保证子序列中随时都是排好序的

  • 在插入a[i]前,数组a的前半段(a[0] ~ a[i - 1])是有序段,后半段(a[i] ~ a[n-1])是停留于输入次序的“无序段”
  • 插入a[i]使a[0] ~ a[i - 1]有序,也就是要为a[i]找到有序位置 j(0 ≤ j ≤ i),将a[i]插入在a[j]的位置上

在这里插入图片描述

直接插入排序


采用顺序查找法查找插入位置

在这里插入图片描述
  在 7 插入后,可以先将 32 和已经排序的末尾 16 进行比较,比较后可以直接插入末尾,省略了将 32 复制为“哨兵”的过程。

直接插入排序算法:

void InsertSort(SqList &L){
	int i, j;
	for(i = 2; i <= L.length; ++i){
		if(L.r[i].key < L.r[i - 1].key){			//若“<”,需将L.r[i]插入有序子表
			L.r[0] = L.r[i];			//复制为哨兵
			for(j = i - 1; L.r[0].key < L.r[j].key; --j){
				L.r[j + 1] = L.r[j];		//记录后移
			}
			L.r[j + 1] = L.r[0];			//插入到正确位置
		}
	}
}

时间复杂度为 O(n)

要提高查找速度:
1)减少元素的比较次数
2)减少元素的移动次数


折半插入排序

在这里插入图片描述

算法描述:

void BInsertSort(SqList &L){
	for(i = 2; i <= L.length; ++i){		//依次插入第2 ~ 第n个元素
		L.r[0] = L.r[i];		//当前插入元素存到“哨兵”位置
		low = 1;				//采用二分法查找插入位置
		high = i - 1;
		while(low <= high){
			mid = (low + high) / 2;
			if(L.r[0].key < L.r[mid].key)
				high = mid -1;
			else low = mid + 1;
		} 	//循环结束,high + 1则为插入位置
		for(j = i - 1; j >= high + 1; --j)
			L.r[j + 1] = L.r[j];			//移动元素
		L.r[high + 1] = L.r[0];				//插入到正确位置
	}
}//BInsertSort

在这里插入图片描述

  • 折半插入排序的对象移动次数与直接插入排序相同,依赖于对象的初始排列
    • 减少了比较次数,但没有减少移动次数
    • 平均性能优于直接插入排序
    • 时间复杂度为O(n2)
    • 空间复杂度为O(1)
    • 是一种稳定的排序方法

希尔排序


基本思想:
  先将整个待排记录序列分割成若干子序列,分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。

希尔排序算法特点:
1)缩小增量
2)多遍插入排序

例如:
在这里插入图片描述
希尔排序思路:

  1. 定义增量序列Dk:DM > DM-1 > …> D1 = 1
    刚才例子中:D3 = 5,D2 = 3, D1 = 1
  2. 对每个Dk进行 “Dk-间隔” 插入排序(k = M, M-1,…1)

希尔排序特点:

  • 一次移动,移动位置较大,跳跃式地接近排序后的最终位置
  • 最后一次只需要少量移动
  • 增量序列必须是递减的,最后一个必须是1
  • 增量序列应该是互质


希尔排序算法(主程序)

void ShellSort(SqList &L, int dlta[], int t){		//dk值一次存在dlta[t]中
	//按增量序列dlta[0...t-1]对顺序表L作希尔排序
	for(k = 0; k < t; ++k)
		ShellInsert(L, dlta[k]);		//一趟增量为dlta[k]的插入排序
}//ShellSort

希尔排序算法(其中某一趟的排序操作)

void ShellInsert(SqList &L, int dk){
			//对顺序表L进行一趟增量为dk的Shell排序,dk为步长因子
	for(i = dk + 1; i <= L.length; ++i)
		if(r[i].key < r[i - dk].key){
			r[0] = r[i];
			for(j = i - dk; j > 0 && (r[0]. key < r[j].kry); j = j - dk)
				r[j + dk] = r[j];
			r[j + dk] = r[0];
		}
}

希尔排序算法分析:

在这里插入图片描述

  • 时间复杂度是n和d的函数
    O ( n 1.25 )   O ( 1.6 n 1.25 ) ——经验公式 O(n^{1.25}) ~ O(1.6n{1.25})——经验公式 O(n1.25) O(1.6n1.25)——经验公式
  • 空间复杂度为O(1)
  • 如何选择最佳d序列,目前尚未解决
  • 是一种不稳定的排序方法
  • 最后一个增量值必须为1,无除了1之外的公因子
  • 不宜在链式存储结构上实现

交换排序

基本思想: 两两比较,如果发生逆序则交换,知道所有记录都排好序为止

常见的交换排序方法:

  • 冒泡排序 O ( n 2 ) O(n^2) O(n2)
  • 快速排序 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

冒泡排序法

  ——基于简单交换思想

基本思想:每趟不断将记录两两比较,并按“前小后大”规则交换
在这里插入图片描述
n 个记录,共需要 n 趟;第 m 趟需要比较 n-m 次

冒泡排序算法:

void bubble_sort(SqList &L){
	int m, i, j;
	RedType x;			//交换时临时存储
	for(m = 1; m <= n - 1; m++){		//总共需要m趟
		for(j = 1; j <= n - m; j++)
			if(L.r[j].key > L.r[j + 1].key){	//交换
				x = L.r[j];
				L.r[j] = L.r[j + 1];	
				L.r[j + 1] = x;
			}//endif
	}//for
}

一旦某一趟比较时不出现记录交换,说明已经排好序了,可以结束算法。

改进的冒泡排序算法:

void bubble_sort(SqList &L){
	int m, i, j;
	int flag = 1;		//flag作为是否有交换的标记
	RedType x;			//交换时临时存储
	for(m = 1; m <= n - 1; m++){		//总共需要m趟
		flag = 0;
		for(j = 1; j <= n - m; j++)
			if(L.r[j].key > L.r[j + 1].key){	//交换
			flag = 1;		//发生交换,falg职位1,若本趟没法发生交换,flag保持为0
				x = L.r[j];
				L.r[j] = L.r[j + 1];	
				L.r[j + 1] = x;
			}//endif
	}//for
}
  • 冒泡排序最好时间复杂度是O(n)
  • 冒泡排序最坏时间复杂度O(n2)
  • 冒泡排序平均时间复杂度O(n2)
  • 冒泡排序是稳定

在这里插入图片描述

快速排序

   —— 改进的交换排序

基本思想:

  • 任取一个元素为中心
  • 所有比它的元素一律前放,比它的元素一律后放,形成左右两个子表
  • 对各子表重新选择中心元素并依此规则调整
  • 直到每个子表的元素只剩一个

算法思想:https://www.bilibili.com/video/BV1nJ411V7bd?p=164(8:27)

快速排序算法:

void mian(){
	QSort(L , 1, L.length);
}
void QSort(SqList &L, int low, int high){
	if(low < high){
		pivotloc = Partition(L, low, high);		
			//将L.r[low...high]一分为二,pivotloc为枢轴元素排好序的位置
		QSort(L, low, pivotloc - 1);		//对低子表递归排序
		QSort(L, pivotloc + 1, high);		//对高子表递归排序
	}//endif
}//QSort

int Partition(SqList &L, int low, int high){
	L.r[0] = L.r[low];
	pivotkey = L.r[low].key;
	while(low < high){
		while(low < high && L.r[high].key >= pivotkey){
			--high;
			L.r[low] = L.r[high];	
		}
		while(low < high && L.r[low].key <= pivotkey){
			++low;
			L.r[high] = L.r[low]
		}
	}
	L.r[low] = L.r[0];
	return low;
}

空间复杂度:

  由于程序使用了递归,需要递归调用栈的支持,而栈的长度取决于递归调用的深度。(即使不用递归,也需要用用户栈)

  • 平均情况下:需要O(logn)的栈空间
  • 最坏情况下:栈空间可达O(n)

稳定性: 快速排序是一种不稳定的排序方法。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值