目 录
先看下排序的思维导图,虽然做的依然不好。。
排序基本上都是程序题。考点如下:
程序:简单插入排序、折半插入排序、简单选择排序
算法思想:2-路归并排序、快速排序、希尔排序、堆排序、基数排序
概念
1、排序:排序是按关键字的非递减或非递增顺序对一组记录重新进行排列操作(将杂乱无章的数据按一定规律顺次排列,目的是便于查找。)
2、排序的分类:内部排序和外部排序。
内部排序:待排序的所有记录全部被放置在内存中。
外部排序:待排序的记录一部分在内存,一部分在外存。
内部排序分类:
①插入类:直接插入排序、折半插入排序、希尔排序。
②交换类:冒泡排序、快速排序。
③选择类:简单选择排序、树形选择排序、堆排序。
④归并类:2-路归并排序。
⑤分配类:基数排序。
根据逐步扩大记录有序序列长度的原则不同,可以将内部排序分为以下几类。
(1)插入类 : 将无序子序列中的一个或几个记录“插入”到有序序列中,从而增加记录的有
序子序列的长度。
(2)交换类 : 通过“交换”无序序列中的记录从而得到其中关键字最小或最大的记录,并将
它加入到有序子序列中,以此方法增加记录的有序子序列的长度。
(3)选择类 : 从记录的无序子序列中“选择”关键字最小或最大的记录,并将它加入到有序子序
列中,以此方法增加记录的有序子序列的长度。
(4)归并类 : 通过“归并”两个或两个以上的记录有序子序列,逐步增加记录有序序列的长
度。
(5)分配类 : 是唯一一类不需要进行关键字之间比较的排序方法,排序时主要利用分配和收
集两种基本操作来完成。
3、排序算法的好坏衡量。
4、待排序记录的存储方式
书中所涉及的排序,除基数排序外,均按顺序表储存。
(1)顺序表:记录之间的次序关系由其存储位置决定,实现排序需要移动记录。
(2)链表:记录之间的次序关系由指针指示,实现排序不需要移动记录,仅需修改指针即可。
这种排序方式称为链表排序。
(3)待排序记录本身存储在一组地址连续的存储单元内,同时另设一个指示各个记录存储位
置的地址向量,在排序过程中不移动记录本身,而移动地址向量中这些记录的“地址”,在排序结
束之后再按照地址向量中的值调整记录的存储位置。这种排序方式称为地址排序。
插入排序
直接插入排序
最简单的排序法,基于顺序查找。
排序过程 : 整个排序过程为 n-1 趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录开始,逐个进行插入,直至整个序列有序。
算法描述(要求掌握)
#include <iostream>
using namespace std;
//对顺序表L做直接插入排序
void InsertSort(SqList &L)
{
for(i=2; i<=L.length; ++i) //从第二个数字(包括第二个)是未排序数集
if(L.r[i].key < L.r[i-1].key) //若比前一个数字小的话则需要插到前一个数字前面
{
L.r[0] = L.r[i]; //赋值,将待插入数字暂存到监视哨中
L.r[i] = L.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]; //将监视哨中储存的数字插入
}
}
算法分析
1、时间复杂度(关键字比较次数KCN、记录移动次数RMN)
最好情况(正序):比较1次,不移动
最坏情况(逆序):
平均:皆为n2/4
时间复杂度:O(n2)
2、空间复杂度
只需要一个辅助空间 r[0] ,所以空间复杂度为O(1)
3、稳定排序
折半插入排序
基于折半查找,相较于直接插入排序来说减少了比较次数
算法描述(要求掌握)
#include <iostream>
using namespace std;
//对顺序表L做折半插入排序
void BInsertSort(SqList &L)
{
for( i=2; i <= L.lengrth; ++i)
{
L.r[0] = L.r[i];//赋值,将待插入数字暂存到监视哨中
//定义两端位置。第一次划分有序比较区间,比较区间的第一个元素所在位置为1,比较区间的最后一个元素所在位置为i-1
low = 1;
high = i-1;
while(low <= high) //在(low,high)这个区间中进行折半查找要插入的位置
{
m = (low + high)/2; //折半
if(L.r[0].key < L.r[m].key) //和中间值比较,判断插入点在前半部分还是后半部分
{
high = m-1; //重新定义(low,high)区间,high前移到中点前一位置
}
else
{
low = m+1; //重新定义(low,high)区间,low后移到中点后一位置
} //在low <= high的情况下一直进行循环
for( j=i-1; j >= high+1; --j)
{
L.r[j+1] = L.r[j]; //将插入点之后的数据进行后移
}
L.r[high+1] = L.r[0]; //插入数据
}
}
算法分析
1、时间复杂度为O(n2)
2、空间复杂度为O(1)
3、稳定排序。
希尔排序
逐趟缩小增量。掌握的算法思想(会画图描述过程即可)
算法描述
void ShellInsert(SqList &L,int dk) //对顺序表L做一趟增量是dk的希尔插人排序
{
for( i=dk+1; i<=L.length; ++i)
{
if(L.r[i].key <L.r[i-dk]. key) //需将L.r[i]插入有序增量子表
{
L.r[0]=L.r[i]; //暂存在L.r[0]
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]; //将r[0]即原r[i],插人到正确位置
}
}
}
void ShellSort (SqList &L,int dt[],int t) //按增量序列dt[0..t-1]对顺序表L作t趟希尔排序
{
for (k=0;k<t;++k)
{
ShellInsert(L,dt[k]); //一趟增量为dt[t]的希尔插人排序
}
算法分析
交换排序
冒泡排序
基本思想:两两比较,逆序交换。
数字小的上浮,数字大的下沉。
算法描述(要求掌握)
#include <iostream>
using namespace std;
//对顺序表L做冒泡排序
void BubbleSort(SqList &L)
{
m = L.length-1; //有一个数字需要不停的用来比较,所以长度减一
flag = 1; //标记一趟排序是否发生交换
while( (m>0) && (flag==1) ) //当m>0,且发生交换时,循环继续
{
flag = 0; //将flag设为0,如果本趟排序没有发生交换,则不会执行下一趟排序
for( j=1; j<=m; j++)
{
if(L.r[j].key > L.r[j+1].key)
{
flag = 1; //flag置为1,表示本趟排序发生了交换
t = L.r[j]; //进行交换
L.r[j] = L.r[j+1];
L.r[j+1] = t;
}
}
--m; //无序表越来越短
}
}
算法分析
1、时间复杂度
最好情况(正序):n-1次关键字比较,不移动。
最坏情况(逆序):
平均:比较次数 n2/4 , 移动次数 3n2/4
时间复杂度为O(n2)
2、空间复杂度为O(1)
3、稳定排序
快速排序
由冒泡排序改进而得,一次交换可消除多个逆序。
基本思想:
理解图示:
①每一趟的子表的形成是采用从两头向中间交替式逼近法;
②由于每趟中对各子表的操作都相似,可采用递归算法。
算法描述(要求掌握)
#include <iostream>
using namespace std;
//对顺序表中的字表r[low..high]进行一趟排序,返回枢轴位置
int Partition(SqList &L, int low, int high)
{
L.r[0] = L.r[low]; //选择待排序表中的第一个记录作为枢轴
pivotkey = L.r[row].key; //枢轴记录关键字保存在pivotkey中
while( low<high ) //从表的两端向中间扫描
{
while( low < high && L.r[row].key >= pivotkey)
//如果没有出现小于枢轴的,右哨兵继续往左走
{
--high;
}
L.r[low] = L.[high]; //将比枢轴小的数移到靠近枢轴的位置
while( low < high && L.r[low].key <= pivotkey)
//如果没有出现大于枢轴的,左哨兵继续往右走
{
++low;
}
L.r[high] = L.r[low]; //将比枢轴小的数移到远离枢轴的位置
}
L.r[low] = L.r[0]; //将枢轴的值赋值给左哨兵现在所处位置
return low; //返回左哨兵现在所处位置
}
//进行快速排序
void Qsort(Sqlist &L, int low, int high)
{
if( low < high ) //如果长度大于1
{
pivotloc = Partition(L, low, high); //将L.r[low..high]一分为二,pivotloc为枢轴位置
Qsort(L, low,pivotloc-1); //对左子表递归排序
Qsort(L, pivotloc+1, high); //对右子表递归排序
}
}
//对L快速排序
void QuickSort(SqList &L)
{
QSort(L, 1, L.length)
}
算法分析
1、时间复杂度
快速排序的趟数取决于递归树的深度。
最好情况(每趟排序后都能将记录序列均匀的分割成两个长度大致相等的子表):时间复杂度为O(nlog2n)
最坏情况(在待排序序列已经排好序的情况下,其递归树成为单支树,每次划分只得到一个比上一次少一个记录的子序列):
2、空间复杂度
快速排序是递归的,执行时需要有一个栈来存放相应的数据。最大递归调用次数与递归树的深度一致。
最好情况:空间复杂度为O(log2n)
最坏情况:O(n)。
3、不稳定
选择排序
基本思想:
每一趟在后面 n-i+1 个中选出关键码最小的对象,作为有序序列的第 i 个记录
简单(直接)选择排序
算法描述(要求掌握)
#include <iostream>
using namespace std;
//简单选择排序
void SelectSort(SqList &L)
{
for( i=1; i<L.length; ++i) //在下标为[i..L.length]的范围内找最小的数
{
k = i; //则将最小的记录的下标赋值给k
for( j=i+1; j<=L.length; ++j) //在下标为[j..L.length]的范围内找最小的数
{
if( L.r[j].key < L.r[k].key ) //如果查找到最小数
{
k=j; //则将其下标赋值给k
}
}
if(k != i) //当k不等于i的时候,即刚刚查找到最小值时,进行交换操作
{
t=L.r[i];
L.r[i] = L.r[k];
L.r[k] = t;
}
}
}
算法分析
1、时间复杂度
最好情况(正序):不移动
最坏情况(逆序):移动 3(n- 1) 次。
比较次数始终相同,均为n2/2。时间复杂度为O(n2)
2、空间复杂度为 O(1)
3、稳定排序
树形选择排序(了解)
堆排序
基本思想:
1.将无序序列建成一个堆
2.输出堆顶的最小(大)值
3.使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值
4.重复执行,得到一个有序序列
筛选法调整堆——算法描述
//假设r[s+1 ..m]已经是堆,将r[s..m]调整为以r[s]为根的大根堆
void HeapAdjust(SqList &L, int s, int m)
{
rc = L.r[s];
for( j=2*s; j<=m; j*=2) //沿key较大的孩子结点向下筛选
{
if(j<m && L.r[j].key < L.r[j+1].key)
{
++j; //j为key较大的记录的下标
}
if( rc.key >= L.r[j].key)
{
break; //rc应插人在位置s上
}
L.r[s] = L.r[j];
s=j;
}
L.r[s] = rc; //插人
}
建初堆——算法描述
void CreatHeap (SqList &L) //把无序序列L.r[1..n]建成大根堆
{
n=L. length;
for(i=n/2;i>0; --i) //反复调用HeapAdjust
{
HeapAdjust(L,i,n) ;
}
}
堆排序——算法描述
void HeapSort (SqList &L) //对顺序表L进行堆排序
{
CreatHeap(L); //把无序序列L.r[1..L. length]建成大根堆
for( i=L.length;i> 1;--i)
{
x=L.r[l]; //将堆顶记录和当前未经排序子序列L.r[l..i]中最后一个记录互换
L.r[1]=L.r[i];
L.r[i]=x;
HeapAdjust(L,1, 1-1); //将L.r[1..i-1]重新调整为大根堆
}
}
算法分析
归并排序
归并排序:就是将两个或两个以上的有序表合并成一个有序表的过程。
2-路归并排序
算法分析
基数排序
算法分析
多关键字排序|
最高位优先MSD ( Most Si gnificant Digit first )
最低位优先LSD ( Least Significant Digit first)
链式基数排序
链式基数排序:用链表作存储结构的基数排序
小结(背)
排序的选择方法
(1)当待排序的记录个数 n 较小时,n2和 nlog2n 的差别不大,可选用简单的排序方法。而当关键字基本有序时,可选用直接插人排序或冒泡排序,排序速度很快,其中直接插人排序最为简单常用、性能最佳。
(2)当 n 较大时,具体选用的原则是:
①当关键字分布随机,稳定性不做要求时,可采用快速排序
②当关键字基本有序,稳定性不做要求时,可采用堆排序;
③当关键字基本有序,内存允许且要求排序稳定时,可采用归并排序。
(3)从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为 O(n2) 的简单排序法也是稳定的,然而,快速排序、堆排序和希尔排序等时间性能较好的排序方法都是不稳定的。