插入排序,交换排序,选择排序,归并排序
期末复习总结,笔记图片来源于张海清老师的ppt
数据结构
排序
插入排序
直接插入排序
基本思想:是将一个记录插入到已排好序的有序表中,从而得到一个新的、记录数增1的有序表。
一、排序过程:
整个排序过程为n-1趟插入,即先将序列中第1个记录看成是一个有序子序列,然后从第2个记录起逐个进行插入,直至整个序列变成按关键字非递减有序序列为止。
二、算法过程演示及描述:
已知关键字序列为{49 38 65 97 76 13 27 49},采用直接插入排序方法对其进行排序。
初始关键字:(49) 38 65 97 76 13 27 49
i=2: (38) (38 49) 65 97 76 13 27 49
i=3: (65) (38 49 65) 97 76 13 27 49
i=4: (97) (38 49 65 97) 76 13 27 49
i=5: (76) (38 49 65 76 97) 13 27 49
1-6: (13) (13 38 49 65 76 97) 27 49
i=7: (27) (13 27 38 49 65 76 97) 49
1-8: (49) (13 27 38 49 49 65 76 97)
//对顺序表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];//r[0]作为哨兵单元,暂存元素
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];//插入到正确位置
}
}
三、算法评价(稳定排序)
对象个数为n,则执行n-1躺
最好情况下:每趟只需比较1次,不移动,总比较次数为n-1
平均情况比较次数和移动次数为n²/4
时间复杂度:O(n²)
空间复杂度:O(1)
折半排序插入法
一、排序过程:用折半查找方法确定插入位置的排序叫~
二、算法过程演示及描述:
在插入 r[i] 时,利用折半查找法寻找 r[i] 的插入位置
三、算法评价(稳定排序)
时间复杂度:T(n)=O(n²)
空间复杂度:S(n)=O(1)
折半插入排序和直接插入排序相比,折半插入排序仅减少了关键字比较次数,而记录的移动次不变。其时间复杂度仍为O(n²)。
希尔排序
基本思想:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序。
一、排序过程:
先取一个正整数d1<n,把所有相隔d1的记录放到一组组内进行直接插入排序;然后取d2<d1,重复上述分组和排序操作;直至di=1,即所有记录放进一个组中排序为止。
二、算法过程演示及描述:
初始:49 38 65 97 76 13 27 48 55 4
步长最好为质数
void ShellSort(SqList L){
for(d=L.length/2;d>=1;d=d/2)
for(i=d+1;i<=L.length;i++)//完成一趟Shell排序
if(L.r[i].key<L.r[i-d].key){//寻找插入位置
L.r[0]=L.r[i];//暂存在L.r[0]中
for(j=i-d;j>0&&L.r[0].key<L.r[j].key;j-=d)
L.r[j+d]=L.r[j];//记录数据向后搬移
L.r[j+d]=L.r[0];//插入
}
}
性能分析:时间复杂度:O(n3/2);空间复杂度:O(1)。
void ShellInsert(SqList L, int d){
for(k=1;k<=d;k++)//O(d),d组子序列分别排序
for(i=d+k;i<=L.length;i+=d){//O(n/d)
e=L.r[i];
for(j=i-d;j>0&&e.key<L.r[j].key;j-=d)
L.r[j+d]=L.r[j];//O(n/d)
L.r[j+d]=e;
}}//O(n*n/d)
void ShellSort(SqList &L,int dlta[],intt){
for(k=0;k<t;k++)ShellInsert(L,dlta[k]);
}
希尔排序的特点:(不稳定排序)
1.子序列的构成不是简单的“逐段分割”,而是将相隔某个增量的记录组成一个子序列。
2.希尔排序可提高排序速度,因为
①分组后n值减小,n²更小,而T(n)=O(n²),所以T(n)从总体上看是减小了;
②关键字较小的记录跳跃式前移,在进行最后一趟增量为1的直接插入排序时,序列已基本有序。
3.增量序列(即步长)取法
①无除1以外的公因子;
②最后一个增量值必须为1。
步长的取法:d1=n/2
d2=d1/2 ………
直到di=1为止
交换排序
冒泡排序 (Bubble Sort)
(一)排序过程:
1.首先将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序(L.r[1].key>L.r[2].key) ,则交换两个记录,然后比较第二个记录与第三个记录的关键字。依次类推,直至第n-1个记录和第n个记录的关键字比较过为止——上述过程称为第一趟冒泡排序,结果使关键字最大的记录被安置在最后一个记录的位置上。
2.然后对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录的位置上。
3.重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止。
(二)算法过程演示:
初始关键字序列:49 38 65 97 76 13 27 49
第一趟排序后:38 49 65 76 13 27 49 97
第二趟排序后:38 49 65 13 27 49 76
第三趟排序后:38 49 13 27 49 65
第四趟排序后:38 13 27 49 49
第五趟排序后:13 27 38 49
第六趟排序后:13 27 38
第七趟排序后:13 27
起泡排序
基本思想:
每趟不断将记录两两比较,并按“前小后大”规则交换
21,25, 49, 5*, 16, 08
21,25, 25*, 16, 08, 49
21, 25, 16, 08, 25*, 49
21,16,08, 25, 25*, 49
16, 08, 21, 25, 25*, 49
08,16, 21, 25, 25*, 49
优点:
每趟结束时,不仅能挤出- -个最大值到最后面位置,还能同时部分理顺其他元素;一旦下趟没有交换,还可提前结束排序
void bubble sort(SqList &L)
{ int m,i,j,flag=1; RedType X;
m=n-1;
while((m>0)&(lag==1)))
{ flag=0;
for(j=1;j<=m;j++)
if(L.r[j].key>L.[j+1].key)
{ flag=1;
x=L.r[j];L.r[j]=L.r[j+1];L.r[j+1]=x; //交换
}//endif
m--;
}//endwhil.
}
•设对象个数为n
•比较次数和移动次数与初始排列有关
最好情况:只需 1趟排序,比较次数为 n-1,不移动
算法评价(稳定排序)
时间复杂度:T(n)=O(n²)
空间复杂度:S(n)=O(1)
①最好情况(正序)
关键字比较次数: n-1
记录移动次数:0
②最坏情况(逆序)
关键字比较次数: 1/2(n(n-1))
记录移动次数:3/2(n(n-1))
快速排序(Quick Sort)—— 霍尔排序
基本思想:
1.通过一趟排序,将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序以达到整个序列有序。
★快速排序实际上是对冒泡排序的一-种改进。
★改进的着眼点:在冒泡排序中,记录的比较和移动是在
相邻单元中进行的,记录每次交换只能上移或下移一个单
元,因而总的比较次数和移动次数较多。
int QuickOne(SqList L, int i, int j){
L.r[0]=Lr[i]; e=L.r[i].key; //e是控制关键字
while(i<){ //i=j时,找到e的最终位置
while(e <=L.r[j].key && i<j) j--; //与后比较
L.r[i]=L.r[j];
while(e >=L.r[i].key && i<j) i++; //与前比较
L.r[j]=L.r[i];
}//while
L.r[i]=L.r[0];
return i; //返回枢轴位置
} //end
//递归
void main ( )
{ QSort ( L, 1, L.length ); }
void QSort ( SqList &L,int low, int high )
{ if ( low < high )
{ pivotloc = Partition(L, low, high ) ;
Qsort (L, low, pivotloc-1) ;
Qsort (L, pivotloc+1, high )
}
}
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(nlog₂n)。
最大递归调用层次数与递归树的深度一致,因此,要求存储开销为 O(log2n) 。
算法评价:(不稳定排序)
时间复杂度:
① 最好情况(每次总是选到中间值作枢轴)**T(n)=O(log2n)
② 最坏情况(每次总是选到最小或最大元素作枢轴)T(n)=O(n²)
空间复杂度:需栈空间以实现递归
最坏情况:S(n)=O(n)
一般情况:S(n)=O(log 2 n)
快速排序是对冒泡排序的一种改进方法,算法中元素的比较和交换是从两端向中间进行的,关键字较大的元素一次就能够交换到后面单元,关键字较小的元素一次就能够交换到前面单元,记录每次移动的距离较远,因而总的比较和移动次数较少。
选择排序
简单/直接选择排序 (Simple/Straight Selection Sort)
一、排序过程:
首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换
再通过n-2次关键字比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换
重复上述操作,共进行n-1趟排序后,排序结束
二、算法过程演示及描述:
//对顺序表L作简单选择排序
void SelectSort ( Sqlist &L)
{ for ( i=1; i<L.length; ++i ) { //选择第i小的记录,并交换到位
//在L.r[i…L.length]中选择最小的记录
j=i;
for ( k=i+1; k<=L.length; ++k )
if (L.r[k].key<L.r[j].key)
j=k; //找到关键字最小的记录,用j记下位置
if ( i != j ) { //第j个记录与第i个记录交换
L.r[0]=L.r[j];
L.r[j]= L.r[i];
L.r[i]=L.r[0]; }
}
}
三、算法评价**(不稳定排序)**
时间复杂度:T(n)=O(n²)
关键字比较次数:1/2(n*(n-1))
记录移动次数
最好情况(正序):0
最坏情况(逆序):**3(**n-1)
空间复杂度:S(n)=O(1)
堆排序
将无序序列建成一个堆,得到关键字最小(或最大)的记录;得到堆顶的最小(或最大)值后,使剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次小值(或次大值)。如此重复执行,便得到一个有序序列,这个过程称之为堆排序(降序)(或升序)。
三、算法过程演示及描述:
HeapSort(SqList L){
for( i=L.length/2 ; i>0 ; i--)
HeapCreate( L , i , L.length );
for( i=L.length ; i>1 ; i-- ) {
L.r[1] L.r[i];
HeapCreate(L , 1 , i-1);
}
}
HeapCreate( SqList L , int s , int len ){
e=L.r[s];
for(j=2*s ;j<=len ; j*=2){
if(j<len && L.r[j].key<L.r[j+1].key) j++;
if(e.key>=L.r[j].key)break; //无须调整
/* 大的数据往上提,s 往下移 ,继续调整*/
L.r[s]=L.r[j] ; s =j ;}
L.r[s]=e ;
}
四、算法评价(不稳定排序)
时间复杂度:最坏情况下 **T(n)=O(**nlogn)
空间复杂度:S(n)=O(1)
堆经常用来实现优先队列和外排序算法。
归并排序
2-路归并排序
排序过程
设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1
然后两两归并,得到 [n/2]个长度为2或1的有序子序列
再两两归并,……如此重复,直至得到一个长度为n的有序序列为止
算法演示及描述:
//两两归并
Merge( int a[ ], int b[ ] , int i , int n , int m){
for(j=n+1 , k=i ; i<=n && j<=m ; k++){
if(a[i]<=a[j]) b[k]=a[i++];
else b[k]=a[j++];
}
if(i<=n) for( ; i<=n;i++,k++) b[k]=a[i];
if(j<=m)for( ; j<=m ; j++,k++) b[k]=a[j];
} //O(两个归并段长度之和 )
归并排序的非递归算法
typedef struct {int *elem; int len;}SqList;
void MergeSort( SqList &L){ h=1; //h是归并段长
B=(int*)malloc(L.len*sizeof(int));
while( h <(L.len)){i=0;
while((i+h)<(L.len)){ //一趟归并排序
if((i+2*h)<(L.len)) m=i+2*h-1;
else m=(L.len)-1 ;
Merge(L.elem , B , i , i+h-1 , m); //O(2*h)
i=i+2*h;} //O( L.len/(2*h) )
for(i=0;i<=m;i++) L.elem[i]=B[ i ];
h=2*h; } //O(log n) (n=L.len)
free( B ); } //O(n*log n )
算法评价:(稳定排序)
时间复杂度:T(n)=O(nlog 2 n)
空间复杂度:S(n)=O(n)