大话数据结构第9章笔记(排序)

9.1开场白
9.2排序的基本概念和分类
      9.2.1排序的稳定性
      9.2.2内排序与外排序
      9.2.3排序用到的结构与函数
9.3冒泡排序
      9.3.1最简单排序实现
      9.3.2冒泡排序算法
      9.3.3冒泡排序优化
      9.3.4冒泡排序复杂度分析

9.4简单选择排序
      9.4.1简单选择排序算法
      9.4.2简单选择排序复杂度分析

9.5直接插入排序
      9.5.1直接插入排序算法
      9.5.2直接插入排序复杂度分析

9.6直接插入排序
      9.6.1希尔排序原理
      9.6.2希尔排序算法
      9.6.3希尔排序复杂度分析

9.8 归并排序
      9.8.1归并排序算法
      9.8.2归并排序复杂度分析
      9.8.3非递归实现归并排序

9.7堆排序
      9.7.1堆排序算法
      9.7.2堆排序复杂度分析

9.9快速排序
      9.9.1快速排序算法
      9.9.2快速排序复杂度分析
      9.9.3快速排序优化

9.2排序的基本概念和分类
1.排序就是将无序的数据集合,按照某一定顺序进行排列
2.对于有很多关键字的排序,可以将多个关键字的排序转化成单个关键字的排序,如下图所示:
大话数据结构第9章笔记(排序)_第1张图片
大话数据结构第9章笔记(排序)_第2张图片
9.2.1排序的稳定性
如图:
大话数据结构第9章笔记(排序)_第3张图片

9.2.2内排序与外排序
     1.什么叫内排序,外排序:(如下图)

大话数据结构第9章笔记(排序)_第4张图片
     2.排序算法的性能主要是受3个方面影响:

           1.时间性能:(如下图)
大话数据结构第9章笔记(排序)_第5张图片
           2.辅助空间:(如下图)
大话数据结构第9章笔记(排序)_第6张图片
           3.算法的复杂性:(如下图) 大话数据结构第9章笔记(排序)_第7张图片

9.2.3排序用到的结构与函数
1.排序用到的顺序表结构:

#define MAXSIZE 10     //用于要排序数组个数最大值,可根据需要修改
typedef struct         
{
	int r[MAXSIZE+1];     //用于存储要排序的数组,r[0]用作哨兵或临时变量
	int length;           //用于记录顺序表长度
}SqList;    

2.排序所用到的交换函数

//交换L中数组r的下标为i和j的值
void swap(SqList *L,int i,int j)
{
	int temp=L->r[i];
	L->r[i]=L->r[j];
	L->r[j]=temp;
}

9.3冒泡排序
      9.3.1最简单的排序实现

冒泡排序一种交换排序,它的基本思想:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止

//对顺序表L做交换排序(冒泡排序初级版)
void BubbleSort0(SqList *L)
{
	int i,j;
	for(i=1; i<L->length; i++)
	{
		for(j=i+1; j<=L->length; j++)
		{
			if(L->r[i]>L->r[j])
			{
				swap(L,i,j);    //交换L->r[i]与L->r[j]的值
			}
		}
	}
}//详见代码如下图

上述代码的思路:让每一关键字,都和它后面的每一个关键字比较,如果前面的大于后面的,则交换,这样第一位置的关键字在一次循环后一定是最小的
上述代码缺陷:每次只能一个一个去排列,这样对其余关键字没有任何帮助
大话数据结构第9章笔记(排序)_第8张图片

      9.3.2冒泡排序算法

此冒泡排序的思想:i从第一个位置开始,然后j从最后向前冒泡,不断地将小的值放到前面,这样不断的循环下去,便于将一部分小的数值移动一些位置

//对顺序表L做冒泡排序
void BubbleSort(SqList *L)
{
	int i,j;
	for(i=1; i<L->length; i++)
	{
		for(j=L->length-1; j>=i; j--)     //注意j是从后往前循环
		{
			if(L->r[j]>L->r[j+1])   //若前者大于后者
			{
				swap(L,j,j+1);   //交换L->r[j]与L->r[j+1]的值
			}
		}
	}
}//详见代码如下图:

大话数据结构第9章笔记(排序)_第9张图片
大话数据结构第9章笔记(排序)_第10张图片

      9.3.3冒泡排序优化

为什么要有冒泡排序的优化?如下图中,如果不优化,还要不断地进行比较,浪费时间

大话数据结构第9章笔记(排序)_第11张图片

//对顺序表L做改进冒泡算法
void BubbleSort2(SqList *L)
{
	int i,j;
	Status flag=TRUE;     //flag用来作为标记
	for(i=1; i<L->length && flag; i++)    //若flag为false则循环结束
	{
		flag=FALSE;    //初始为false
		for(j=L->length-1; j>=i; j--)
		{
			if(L->r[j] > L->[j+1])
			{
				swap(L,j,j+1);    //交换L->r[j]与L->r[j+1]的值
				flag=TRUE;       //如果有数据交换,则flag改true
			}
		}
	}
}//详见如下图:

此代码的思想是:加入一个flag判断,flag初始值为TRUE,进入循环后如果后面循环中一但有交换则改flag值为TRUE,再次循环;如果循环中已经是升序排列,不会有前面的值大于后面的值,则flag为false,循环结束

大话数据结构第9章笔记(排序)_第12张图片

9.3.4冒泡排序复杂度分析
最好情况是o(n)
最坏情况是o(n^2)

9.4简单选择排序

      9.4.1简单选择排序算法

简单选择排序的思想:从第一个位置开始,循环找到后面的最小值,然后与第一个位置的值进行交换,如此进行下去,则就是简单选择排序

//对顺序表L做简单选择排序
void SelectSort(SqList *L)
{
	int i,j,min;
	for(i=1; i<L->length; i++)
	{
		min=i;       //将当前下标定义为最小值下标
		for(j=i+1; j<=L->length; j++)   //循环之后的数据
		{	
			if(L->r[min]>L->r[j])   //如果有小于当前最小值的关键字
				min=j;         //将此关键字的下标赋值给min
		}
		if(i!=min)                     //若min不等于i,说明找到最小值了,交换
			swap(L,i,min);    //交换L->r[i]与L->r[min]的值
	}
}

大话数据结构第9章笔记(排序)_第13张图片

      9.4.2简单选择排序复杂度分析

1.对上面代码中第二层的for(就是找最小值下标)的那个代码而言,对第一个值其比较次数为n-1,对于第2个值而言,其比较次数为n-2,则一共的比较次数为n(n-1)/2,后面中就是交换次序的代码,最坏情况需要交换n-1次,则一共的时间复杂度为o(n^2)

9.5直接插入排序
      9.5.1 直接插入排序算法

直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表

//对顺序表L作直接插入操作
void InsertSort(SqList *L)
{
	int i,j;
	for(i=2; i<=L->length; i++)
	{
		if(L->r[i]<L->r[i-1])          //需将L->r[i]插入有序子表
		{
			L->r[0]=L->r[i];            //设置哨兵
			for(j=i-1; L->r[j]>L->r[0]; j--)
				L->r[j+1]=L->r[j];              //记录后移
			L->r[j+1]=L->r[0];            //插入到正确位置
		}
	}
}

大话数据结构第9章笔记(排序)_第14张图片
大话数据结构第9章笔记(排序)_第15张图片

      9.5.2直接插入排序复杂度分析

大话数据结构第9章笔记(排序)_第16张图片
9.6希尔排序

      9.6.1希尔排序原理

1.希尔排序就是为了突破O(n^2)的时间复杂度,思想就是每次循环一次后都将原来的数组排序成基本有序(P.S.基本有序就是大的数字基本在后面,小的数字基本在前面,不大不小的基本在中间)
2.采用的是跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在自序列内分别进行直接插入排序后得到的结果时基本有序而不是局部有序

大话数据结构第9章笔记(排序)_第17张图片

      9.6.2 希尔排序算法

//对顺序表L做希尔排序
void ShellSort(SqList *L)
{
	int i,j;
	int increment=L->length;
	do
	{
		increment=increment/3+1;    //增量序列
		for(i=increment+1; i<=L->length; i++)
		{
			if(L->r[i]<L->r[i-increment])
			{//需将L->r[i]插入有序增量子表中
				L->r[0]=L->r[i];          //暂存到L->r[0]
				for(j=i-increment; j>0 && L->r[0]<L->r[j]; j-=increment)
					L->r[j+increment]=L->r[j];        //记录向后移,查找插入位置
				L->r[j+increment]=L->r[0];          //插入
			}
		}
	}while(increment>1);
}//详见如下图

1.待排数组
大话数据结构第9章笔记(排序)_第18张图片
大话数据结构第9章笔记(排序)_第19张图片
大话数据结构第9章笔记(排序)_第20张图片
大话数据结构第9章笔记(排序)_第21张图片

      9.6.3希尔排序时间复杂度分析

1.时间复杂度为O(n^3/2),要直接好于O(n的平方)
2.增量序列的最后一个增量值必须等于1才行

9.8归并排序

说白了就是对数组进行两两合并,详见下图

大话数据结构第9章笔记(排序)_第22张图片
      9.8.1归并排序算法

归并排序的原理,见下图:

大话数据结构第9章笔记(排序)_第23张图片

总的归并排序代码:

//对顺序表L做归并排序
void MergeSort(SqList *L)
{
	MSort(L->r, L->r, 1, L->length);
}

上述代码,为了和前面的排序算法统一,用了同样的参数定义SqList *L,MSort的代码实现如下:

递归拆分代码:

void MSort(int SR[], int TR1[], int s, int t)  //SR[]数组相当于原始数组,TR1[]相当于排好序的数组
{
	int m;
	int TR2[MAXSIZE+1];
	if(s==t)
		TR1[s]=SR[s];    
	else
	{
		m=(s+t)/2;     //相当于从中间开始分
		MSort(SR,TR2,s,m);    //将原始数组SR[]的前半部分递归到TR2[]中
		MSort(SR,TR2,m+1,t);     //将原始数组SR的后半部分放到TR2[]中
		Merge(TR2,TR1,s,m,t);   //将TR2[]递归到已排好序的数组TR1[]中
	}
}//详见如下图

大话数据结构第9章笔记(排序)_第24张图片

Merge函数如何实现(归并算法):

//将数组直接归并为有序的
void Merge(int SR[], int TR[], int i,int m,int n)
{
	int j,k,l;
	for(j=m+1,k=i; i<=m && j<=n; k++)   //将SR中记录有小到大归并入TR中
	{
		if(SR[i]<SR[j])
			TR[k]=SR[i++];
		else
			TR[k]=SR[j++];
	}
	if(i<=m)                                 //将剩余的归并到TR中
	{
		for(l=0; l<=m-i; l++)
			TR[K+l]=SR[i+1];
	}
	if(j<=n)                                 //将剩余的归并到TR中
	{
		for(l=0; l<=n-j; l++)
			TR[k+l]=SR[j+l];
	}
} //详见如下图:

大话数据结构第9章笔记(排序)_第25张图片
大话数据结构第9章笔记(排序)_第26张图片

      9.8.2 归并排序复杂度分析

1.时间复杂度分析:
对于上面那个merge函数中,将原始数列SR[]放入到TR1[]中,由于要遍历循环所有的记录所以时间复杂度为O(n),而由完全二叉树的深度可知,整个总的归并排序需要进行log2n次,总的时间复杂度为O(nlogn)
2.空间复杂度分析:
由于归并排序在归并过程中需要与原始记录序列同样数量的存储空间存放归并结果,所以空间复杂度为o(n)。同时还有递归时深度为log2n的栈空间,因此空间复杂度为O(n+logn)
3.Merge函数中有if(SR[i]

      9.8.3 非递归实现归并排序

直接进行归并排序代码如下:

//对顺序表L作归并非递归排序
void MergeSort2(SqList *L)
{
	int* TR=(int *) malloc (L->length*sizeof(int));    //申请额外空间
	int k=1;
	while(k<L->length)
	{
		MergePass(L->r,TR,k,L->length);
		k=2*k;                           //子序列长度加倍
		MergePass(TR,L->r,k,L->length);
	    k=2*k;                    	//子序列长度加倍
	}
} //详见代码如下图:

大话数据结构第9章笔记(排序)_第27张图片

非递归算法和递归算法的区别

1.非递归算法:对数组直接归并
2.递归算法: 先递归拆分数组然后再归并退出递归

上面的MergePass(归并排序)代码如下:

void MergePass(int SR[],int TR[],int s,int n)
{
	int i=1;
	int j;
	while(i<=n-2*s+1)
	{
		Merge(SR,TR,i,i+s-1,i+2*s-1);       //两两归并
		i=i+2*s;
	}
	if(i<n-s+1)         //归并最后两个序列
		Merge(SR,TR,i,i+s-1,n);
	else
		for(j=i; j<=n; j++)
			TR[j]=SR[j];
}//详见如下图:

大话数据结构第9章笔记(排序)_第28张图片
大话数据结构第9章笔记(排序)_第29张图片

非递归归并排序的时间复杂度如下图:

大话数据结构第9章笔记(排序)_第30张图片

9.7堆排序算法

大顶堆+小顶堆如下图:

大话数据结构第9章笔记(排序)_第31张图片

如果按照层序遍历的方式给结点从1开始编号,有如下关系:

大话数据结构第9章笔记(排序)_第32张图片
大话数据结构第9章笔记(排序)_第33张图片

将大顶堆和小顶堆用层序遍历存入数组,如下图:

大话数据结构第9章笔记(排序)_第34张图片

      9.7.1堆排序算法

堆排序的基本思想如下:

大话数据结构第9章笔记(排序)_第35张图片
大话数据结构第9章笔记(排序)_第36张图片
堆排序的整体代码:

//对顺序表L进行堆排序
void HeapSort(SqList *L)
{
	int i;
	for(i=L->length/2; i>0; i--)         //将原来的数组构建为一个大顶堆
		HeapAdjust(L,i,L->length);
	
	for(i=L->length; i>1; i--)
	{
		swap(L,1,i);          //将堆顶记录和当前未经排序子序列的最后一个记录交换
		HeapAdjust(L,1,i-1);       //将其重新调整为大顶堆
	}
}//详见如下图

大话数据结构第9章笔记(排序)_第37张图片
大话数据结构第9章笔记(排序)_第38张图片
上述代码中HeapAdjust函数如何实现:

void HeapAdjust(SqList *L,int s,int m)
{
	int temp,j;
	temp=L->r[s];
	for(j=2*s; j<=m; j*=2)   //沿关键字较大的孩子结点向下筛选
	{
		if(j<m && L->r[j]<L->r[j+1])
			++j;            //j为关键字中较大的记录的下标
		if(temp>L->r[j])
			break;
		L->r[s]=L->r[j];
		s=j;
	}
	L->r[s]=temp;   //插入
}//详见如下图

大话数据结构第9章笔记(排序)_第39张图片

大话数据结构第9章笔记(排序)_第40张图片
大话数据结构第9章笔记(排序)_第41张图片
接下来就是HeapSort函数中正式的排序过程

	for(i=L->length; i>1; i--)
	{
		swap(L,1,i);          //将堆顶记录和当前未经排序子序列的最后一个记录交换
		HeapAdjust(L,1,i-1);       //将其重新调整为大顶堆
	}//详见如下图:

      9.7.2堆排序复杂度分析

1.在第一次构建堆的过程中,时间复杂度如下图:

大话数据结构第9章笔记(排序)_第42张图片
大话数据结构第9章笔记(排序)_第43张图片

2.在正式排序的过程中,时间复杂度如下:

大话数据结构第9章笔记(排序)_第44张图片

9.9快速排序

1.希尔排序相当于直接插入排序的升级,他们都属于插入排序类
2.堆排序相当于简单选择排序的升级,他们都属于选择排序类
3.快速排序就是冒泡排序的升级,他们都属于交换排序类

      9.9.1快速排序算法

快速思想的思想,如下图:

大话数据结构第9章笔记(排序)_第45张图片
快速排序的代码:

void QuickSort(SqList *L)
{
	QSort(L,1,L->length);
}

QSort函数的代码:

//对顺序表L中的子序列做快速排序
void QSort(SqList *L,int low,int high)
{
	int pivot;
	if(low<high)
	{
		pivot=Partition(L,low,high);        //将原来数组一分为2,算出枢轴pivot
		QSort(L,low,pivot-1);             //对低字表递归排序
		QSort(L,pivot+1,high);            //对高子表递归排序
	}
}//详见如下图

大话数据结构第9章笔记(排序)_第46张图片

上述代码中Partition函数(将原始数组一分为2的数组)如下:

//交换顺序表L中子表的记录,使枢轴记录到位,并返回其所在位置
//此时在他之前的记录均不大于他,在后面的记录均不小于他
int Partition(SqList *L,int low,int high)
{
	int pivotkey;                          //用子表的第一个记录做枢轴记录
	pivotkey=L->r[low];					//从表的两端交替向中间扫描
	while(low<high)
	{
		while(low<high && L->r[high]>=pivotkey)
			high--;
		swap(L,low,high);                 //将比枢轴记录小的记录交换到低端
		while(low<high && L->r[low]<=pivotkey)
			low++;
		swap(L,low,high);                      //将比枢轴记录大的记录交换到高端
	}
	return low;              //返回枢轴所在位置
}//详见如下图

大话数据结构第9章笔记(排序)_第47张图片
大话数据结构第9章笔记(排序)_第48张图片
大话数据结构第9章笔记(排序)_第49张图片

      9.9.2快速排序复杂度分析
大话数据结构第9章笔记(排序)_第50张图片
大话数据结构第9章笔记(排序)_第51张图片

      9.9.3快速排序优化
            1.优化选取枢轴
                  1.为啥要优化选取枢轴?
大话数据结构第9章笔记(排序)_第52张图片
大话数据结构第9章笔记(排序)_第53张图片

                  2.三数取中法:

大话数据结构第9章笔记(排序)_第54张图片
在Partition函数中的第3行与第4行之间加上如下代码:

3 int Pivotkey;
	int m=low+(high-low)/2;           //计算数组中间的元素的下标
	if(L->r[low]>L->r[high])
		swap(L,low,high);        //交换左端与右端,保证左端较小
	if(L->r[m]>L->r[high])
		swap(L,high,m);             //交换中间与右端数据,保证中间较小
	if(L->r[m]>L->r[low])
		swap(L,m,low);               //交换中间与左端数据,保证左端较小
	//此时L.r[low]已经是整个序列做左中右三个关键字的中间值
4 pivotkey=L->r[low];          //还是用子表的第一个记录做枢轴记录
//详见如下图

大话数据结构第9章笔记(排序)_第55张图片
                  3.优化不必要的交换:
1.原因如下图:
在这里插入图片描述
2.详细代码:

//快速排序优化算法
int Partition1(SqList *L,int low,int high)
{
	int pivotkey;                          //用子表的第一个记录做枢轴记录
	//这里省略三数取中的代码
	pivotkey=L->r[low];					//从表的两端交替向中间扫描
	L-r[0]=L->r[high];              //增加1:叫枢轴关键字备份到L->r[0]
	while(low<high)
	{
		while(low<high && L->r[high]>=pivotkey)
			high--;
		L->r[low]=L->r[high];              //增加2:采用的是替换而不是交换
		while(low<high && L->r[low]<=pivotkey)
			low++;
		L->r[high]=L->r[low];        //增加3:采用的是替换而不是交换
	}
	L->r[low]=L->r[0];              //增加4:将枢轴数值替换回L.r[low]
	return low;              //返回枢轴所在位置

大话数据结构第9章笔记(排序)_第56张图片

                  4.优化小数组的排序方案
1.原因如下:
大话数据结构第9章笔记(排序)_第57张图片
2.详见代码:

#define MAX_LENGTH_INSERT_SORT 7            //数组长度阈值
void QSort(SqList *L,int low,int high)
{
	int pivot;
	if((high-low)>MAX_LENGTH_INSERT_SORT)
	{//当high-low大于常数时用快速排序
		pivot=Partition(L,low,high);        //将原来数组一分为2,算出枢轴pivot
		QSort(L,low,pivot-1);             //对低字表递归排序
		QSort(L,pivot+1,high);            //对高子表递归排序
	}
	else
		InsertSort(L);       //当high-low小于等于常数时用直接插入排序
}//详见如下图

在这里插入图片描述

                  5.优化递归的操作
1.原因如下图:
大话数据结构第9章笔记(排序)_第58张图片
于是对QSort进行尾递归操作:

void QSort1(SqList *L,int low,int high)
{
	int pivot;
	if((high-low)>MAX_LENGTH_INSERT_SORT)
	{//当high-low大于常数时用快速排序
		while(low<high)
		{
			pivot=Partition(L,low,high);        //将原来数组一分为2,算出枢轴pivot
			QSort1(L,low,pivot-1);             //对低字表递归排序
			low=pivot+1;              //尾递归			
		}

	}
	else
		InsertSort(L);       //当high-low小于等于常数时用直接插入排序
}//详见如下图

大话数据结构第9章笔记(排序)_第59张图片

你可能感兴趣的:(大话数据结构)