数据结构与算法——排序(看书做笔记加个人理解)

本文详细介绍了几种排序算法,包括直接插入排序、折半插入排序、希尔排序、冒泡排序、快速排序、简单选择排序、树形选择排序、堆排序和2路归并排序。各种排序算法通过实例解释了其基本思想、操作过程、时间复杂度和空间复杂度,以及适用场景。例如,快速排序在平均情况下时间复杂度为O(nlog2n),而直接插入排序在初始基本有序的情况下更适用。
摘要由CSDN通过智能技术生成

数据结构与算法——排序

排序
待排序记录用顺序表存储,下列举例均是从小到大排序

#define MAXSIZE 20
typedef int KeyType;
typedef struct{
   
	KeyType key;
	InfoType otherinfo;
}RedType;
typedef struct{
   
	RedType r[MAXSIZE+1];//r[0]闲置或者做哨兵单元
	int length;
}SqList;

排序

1.1插入类排序

基本思想
每一趟将一个待排序的记录,按关键字的大小插入到已经排好序的记录的适当的位置上,直到所有待排序记录全部插入。

1.1.1直接插入排序

首先把第一个关键字作为只有一个关键字的排好序的记录,看下一个记录是否小于第一个关键字,小于就插到第一个关键字之前,大于就继续下一个。如果排好序的记录不只两个,还需要在已经排好序的记录中找合适的插入位置,在这个记录中从后往前依次比较。

举个例子:
{49,38,65,97,76,13,27,49}
i=1 <49>,38,65,97,76,13,27,49

  1. i=2 <38 49> 65 97 76 13 27 49 (把38移到了49以前)
  2. i=3 <38 49 65> 97 76 13 27 49 (65比49大,所以不变直接下一个)
  3. i=4 <38 49 65 97> 76 13 27 49 (97比65大,所以直接下一个)
  4. i=5 <38 49 65 76 97> 13 27 49 (76比97小,所以要插入记录,然后找位置,找位置在记录里找,然后发现76小于97大于65,所以插入到65 和97 中间)
  5. i=6 <13 38 49 65 76 97> 27 49 (13比97小,所以要插入,然后再找插入位置,从记录中后往前比,65,49,38都比13大所以13插入最前面,也就是38以前)
  6. i=7 <13 27 38 49 65 76 97> 49 (27比97小,所以要插入,然后找插入位置,65,49,38大于27,13小于27,所以插入到13与38中间)
  7. i=8 <13 27 38 49 49 65 76 97> (49比97小,所以要插入,然后找插入位置,65大于49,49等于49,所以插入到49和65之间)

算法描述

void InsertSort(SqList& L)
{
   
	int i, j;
	for (i = 2; i <= L.length; ++i)
		if (L.r[i].key < L.r[i - 1].key)
		{
      										//"<",需将r[i]插入有序子表
			L.r[0] = L.r[i];				 			//将待插入的记录暂存到监视哨中
			L.r[i] = L.r[i - 1];	            		//r[i-1]后移
			for (j = i - 2; L.r[0].key < L.r[j].key; --j)			//从后向前寻找插入位置
				L.r[j + 1] = L.r[j];					//记录逐个后移,直到找到插入位置
			L.r[j + 1] = L.r[0];						//将r[0]即原r[i],插入到正确位置
		}											//if
}													//InsertSort

算法分析

  • 时间复杂度:最好的情况下比较次数达n-1,记录不需移动,最坏的情况比较次数和移动次数都达到最大值n2/2
  • 空间复杂度: 需要一个记录的辅助空间r[0]所以空间复杂度为O(1)

算法特点

  • 稳定排序
  • 简单容易实现
  • 适用于链式存储结构,单链表上无需移动记录,只需修改相应的指针
  • 更适合初始记录基本有序的情况(正序:非递减有序排列),当初始记录无序,n也较大时,此算法时间复杂度较高,不适合。

1.1.2折半插入排序

直接插入排序采用顺序查找法查找当前记录在已经排好序的序列中的插入位置,这个查找操作可以利用折半查找实现,这叫折半插入排序。

没啥可说的,就是查找这一步优化了一下,其他的和直接插入排序一样

算法描述

//对顺序表做折半插入排序
void BInsertSort(SqList& L)
{
   
	int i, j, low, high, m;
	for (i = 2; i <= L.length; ++i)
	{
   
		L.r[0] = L.r[i];
		low = 1;
		high = i - 1;
		while (low <= high)
		{
   
			m = (low <= high);
			if (L.r[0].key < L.r[m].key) 
				high = m - 1;
			else low = m + 1;
		}
		for (j = i - 1; j > high + 1; --j) 
			L.r[j + 1] = L.r[j];
		L.r[high + 1] = L.r[0];
	}
}

算法分析

  • 时间复杂度:比较次数与待排序序列的初始排列无关,尽依赖记录的个数,移动次数依赖于初始排列,在平均情况下,折半插入排序只减少了关键字间的比较次数,记录移动次数不变,时间复杂度仍然为O(n2)
  • 空间复杂度:与直接插入排序相同,O(1)

算法特点

  • 稳定排序
  • 只能用于顺序结构,因为要折半,不能用于链式结构
  • 适合初始记录无序、n较大的情况。

1.1.3希尔排序

实际上是采用分组插入的方法,先将整个待排序记录序列分割成几组,从而减少参与直接插入排序的数据量,对每组分别进行直接插入排序,然后增加每组的数据量,重新分组。这样经过几次分组排序后,整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
举例:
{49,38,65,97,76,13,27,49,55,04}

  1. 一趟排序结果:13,27,49,55,04,49,38,65,97,76
  2. 二趟排序结果:13,04,49,38,27,49,55,65,97,76
  3. 三趟排序结果:04,13,27,38,49,49,55,65,76,97

(1)第一趟,取增量(就是分组时将相隔某个值的记录分成一组,这个值就是增量)为5,即所有间隔为5的记录分在同一组,全部记录分成5组,第一趟排序过程如下:

  1. { 49,38,65,97,76,13,27,49,55,04} 49和13分在一组,49大于13,进行交换。
  2. {13,38,65,97,76,49,27,49,55,04} 38和27分在一组,38大于27,进行交换。
  3. {13,27,65,97,76,49,38,49,55,04} 65和49分在一组,65大于49,进行交换。
  4. {13,27,49,97,76,49,38,65,55,04} 97和55分在一组,97大于55,进行交换。
  5. {13,27,49,55,76,49,38,65,97,04} 76和04分在一组,76大于04,进行交换。
  6. {13,27,49,55,04,49,38,65,97,76} 第一趟排序完毕。

(2)第二趟取增量为3,过程如下:

  1. { 13,27,49,55,04,49,38,65,97,76} 13,55,38,76为一组,用直接插入排序,排序结果为13,38,55,76。按顺序插入到空出来的位置。
  2. {13,27,49,38,04,49,55,65,97,76} 27,04,65为一组,直接插入排序,排序结果为04,27,65.按顺序插入到空出来的位置。
  3. {13,04,49,38,27,49,55,65,97,76} 49,49,97为一组,直接插入排序,排序结果为49,49,97。按顺序插入到空出来的位置。
  4. {13,04,49,38,27,49,55,65,97,76}第二趟排序完毕。

(3)第三趟排序,增量为1,相当与对整个序列进行一趟直接插入排序。

算法描述:

//对顺序表L做一趟增量是dk的希尔插入排序,有两个函数
void ShellInsert(SqList& L, int dk)
{
   
	int i, j;
	for (i = dk + 1; i <= L.length; ++i)
	{
   
		if (L.r[i].key < L.r[i - dk].key)
		{
   
			L.r[0] = L.r[i];
			for (j = i - dk; j > 0 && L.r[0].key < L.r[j].key; j -= dk)
				L.r[j + dk] = L.r[j];
			L.r[j + dk] = L.r[0];
		}
	}
}
void ShellSort(SqList& L, int dk[], int t)
{
   //按增量序列dt[0..t-1]对顺序表L作t趟希尔排序
	int k;
	for (k = 0; k < t; ++k)
		ShellInsert(L,dk[k]);
}

算法分析

  • 时间复杂度:希尔排序的时间复杂度是所取“增量”序列的函数,这涉及一些数学上尚未解决的难题,到目前为止未有人求得一种最好的增量序列。有人在大量实验基础上推出,当n在某个特定范围内,希尔排序所需的比较和移动次数约为n1.3 ,当n趋近无穷大时,可减少到n(log2n)2
  • 空间复杂度:只要一个辅助空间r[0],空间复杂度为O(1)

算法特点

  • 记录跳跃式的移动导致排序方法是不稳定的。
  • 只能用于顺序结构,不能用于链式结构
  • 增量序列的值不能有除了1以外的公因子,并且最后一个必须是1
  • 记录总的比较次数和移动次数都比直接插入排序要少,n越大,效果越明显,所以适合初始记录无序,n较大时的情况。

1.2交换类排序

基本思想:
两两比较待排序的记录的关键字,一旦发现两个记录不满足次序要求时就进行交换,直到整个序列全部满足要求,

1.2.1冒泡排序

第一趟从前到后,每个记录和它后一个记录比较,如果不满足次序要求就交换,第二趟操作到倒数第二个,操作和第一趟一样,第三趟操作到倒数第三个……直到需要操作的记录只剩下一个。

在第一趟过程中,在一次次交换中,最大的那个记录被“传递”到最后一位,在第二趟中,除去第一趟被归位的最大的记录,剩下部分的最大值被传递到倒数第二位,这样一来,最大的和第二大的归位了,重复第二趟的操作,依次归位所有记录。

举例
{49,38,65,97,76,13,27,49}
有8个记录,所以要排7趟,但是最后一趟只有一个可操作记录了,所以实际上6趟就完成了。

  1. 第一趟排序 38,49,65,76,13,27,49,97
  2. 第二趟排序 38,49,65,13,27,49,76,97
  3. 第三趟排序 38,49,13,27,49,65,76,97
  4. 第四趟排序 38,13,27,49,49,65,76,97
  5. 第五趟排序 13,27,38,49,49,65,76,97
  6. 第六趟排序 13,27,38,49,49,65,76,97

算法描述:

void BubbleSort(SqList& L)
{
   
	int m, j, flag;//flag用于标记某一趟排序是否发生交换
	ElemType t;
	m = L.length - 1;
	flag = 1;
	while ((m > 0) && (flag == 1))
	{
   
		flag = 0;
		for (j = 1; j <= m; j++)
		{
   
			if (L.r[j].key > L.r[j + 1].key)
			{
   
				flag = 1;
				t = L.r[j];
				L.r[j] = L.r[j + 1];
				L.r[j + 1] = t;
			}
			--m;
		}
	}
}

算法分析

  • 时间复杂度:最好情况(初始正序)只需一趟排序,排序中进行n-1次关键字比较,且不移动记录。最坏的情况(初始逆序,即非递增序列)需要n-1趟排序,比较次数约n2/2,移动次数约3n2/2,所以平均情况下,比较次数和记录移动次数分别约为n2/4和3n2/4,时间复杂度为O(n2)
  • 空间复杂度:需要一个辅助空间暂存记录,空间复杂度为O(1)

算法特点

  • 稳定排序
  • 可用于链式存储结构
  • 移动记录次数较多,平均时间性能比直接插入排序差,当记录初始无序,n较大的时候不宜采用。

1.2.2快速排序

由冒泡排序改进而来,在待排序的n个记录中任取一个记录(通常是第一个记录)作为枢轴,设其关键字为pivotkey,一趟排序后,把所有关键字小于pivotkey的记录交换到前面把所有关键字大于pivotkey的记录交换到后面,结果将待排序记录分成两个子表,最后将枢轴放在分界处的位置。然后分别对左右子表分别重复上述过程,直到每一个字表只有一个记录,排序完成。
简化步骤话版(这个好像更好理解)

  1. 用r[0]记录枢轴(pivotkey),同时low=1;high=L.length;(就是说low是第一个记录,high是最后一个记录) 然后一般pivotkey是r[1]
  2. 从表右侧开始向左搜索,当low<high时,若high所指的关键字大于等于pivotkey,执行–high;否则high所指的记录与枢轴记录交换。
  3. 再从表的最左侧位置,依次想右搜索,当low<high时,若low所指的关键字小于等于pivotkey,执行++low;否则low所指的记录与枢轴记录交换。
  4. 重复2,3,直到low==high

举例:
{49,38,65,97,76,13,27,49}(前面的不标也不影响,此处为便于区分两个49,其中一个我加粗)

  1. 第一趟排序 {27,38,13,}49,{76,97,65,49}
  2. 第二趟排序 {13,}27,{38},49,{76,97,65,49}
  3. 第三趟排序 13,27,38,49,{ 49,65,}76,97
  4. 第四趟排序 13,27,38,49,49,{65,}76,97

(1)第一趟排序过程
首先,是顺序储存,pivotkey=r[1]=49,low=1,high=8

  1. low指49,high指49,pivotkey=r[high],high左移,low=1,high=7 {49,38,65,97,76,13,27,49}
  2. low指49,high指27,pivotkey=r[low],low右移,low=2,high=7 {49,38,65,97,76,13,27,49}
  3. low指38,high指27,pivotkey>r[high],27和49换位置,low=2,high=7{27,38,65,97,76,13,49,49}
  4. low指38,high指49,pivotkey>r[low],low右移,low=3,high=7{27,38,65,97,76,13,49,49}
  5. low指65,high指49,pivotkey=r[high],high左移,low=3.high=6 {27,38,65,97,76,13,49,49}
  6. low指65,high指13,pivotkey<r[low],65和49换位置,low=3.high=6 {27,38,49,97,76,13,65,49}
  7. low指49,high指13,pivotkey>r[high],13与49换位置,low=3.high=6 {27,38,13,97,76,49,65,49}
  8. low指13,high指49,pivotkey>r[low],low右移,low=4,high=6 {27,38,13,97,76,49,65,49}
  9. low指97,high指49,pivotkey=r[high],high左移,low=4,high=5 {27,38,13,97,76,49,65,49}
  10. low指97,high指76, pivotkey<r[low],97和49换位置,low=4,high=5{27,38,13,49,76,97,65,49}
  11. low指49,high指76,pivotkey<r[high],low右移,low=5,high=5{27,38,13,49,76,97,65,49}
  12. low指76,high指76,low=high,第一趟排序完毕。排序结果为{27,38,13,49,76,97,65,49}

(2)第二趟排序过程
排上一趟排序枢轴49左边的部分{27,38,13,}

  1. pivotkey=27,low=1,high=3,
  2. low指27,high指13,pivotkey>r[high],13和27换位置,low=1,high=3,{13,38,27,}
  3. low指13,high指27, pivotkey>r[low],low右移,low=2,high=3,{13,38,27,}
  4. low指38,high指27,pivotkey=r[high],high左移,low=2,high=2,{13,38,27,}
  5. low指38,high指38,low=high,第二趟排序完毕。排序结果为{13,38,27,}

(3)第三趟排序过程
排第一趟枢轴49右边的部分{76,97,65,49}

  1. pivotkey=76,low=1,high=4,
  2. low指76,high指49,pivotkey>r[high],49和76换位置low=1,high=4,{ 49,97,65,76}
  3. low指49,high指76,pivotkey>r[low],low右移,low=2,high=4,{ 49,97,65,76}
  4. low指97,high指76,pivotkey=r[high],high左移,low=2,high=3,{ 49,97,65,76}
  5. low指97,high指65,pivotkey<r[low],97和76换位置,low=2,high=3,{ 49,76,65,97}
  6. low指76,high指65,pivotkey>r[high],65和76换位置,low=2,high=3,{ 49,65,76,97}
  7. low指65,high指76,pivotkey>r[low],low右移,low=3,high=3,{ 49,65,76,97}
  8. low指76,high指76,第三趟排序完毕。排序结果为{ 49,65,76,97}

(4)第四趟排序过程
排第三趟排完后枢轴76左边的部分{ 49,65,}

  1. pivotkey=49,low=1,high=2,
  2. low指49,high指65,pivotkey<r[high],high左移,low=1,high=1
  3. low指49,high指49,第四趟排序完毕。排序结果为{ 49,65,}

算法描述

//快速排序,有三个函数
//对顺序表L中的子表r[low..high]进行一趟排序,返回枢轴位置
int Partition(SqList& L, int low, int high)
{
   
	int pivotkey;
	L.r[0] = L.r[low];
	pivotkey = L
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值