1.1排序的基本概念和方法概述
1.1.1排序的基本概念
定义:是按关键字的非递减或非递增顺序对一组记录重新进行排列的操作。
1.1.2稳定性
当排序中存在两个或两个以上关键字相等时,若排序后的序列中R1仍领先于R2,则所用的排序的方法是稳定的,反之则是不稳定的。
1.1.3内部排序和外部排序
内部排序:是指排序记录数据全部存放在计算机内存中进行排序的过程。
外部排序:是指待排序记录的数量很大,以至于内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
1.2内部排序方法的分类
插入类,交换类,选择类,归并类,分配类。
1.3待排序记录的储存方式
1)顺序表:记录的次序关系由储存位置决定,实现排序需要移动记录。
2)链表:记录的次序关系由储指针指示,实现排序仅需修改指针。
以顺序表储存为例,待排序的数据类型为:
#define MAXSIZE 1000//顺序表的最大长度
typedef int KeyType;//关键字为整形
typedef struct{
KeyType key;//关键字
InfoType otherinfo;//其他数据项
}RedTye;
typedef struct{
RedType r[MAXSIZE+1];//r[0]闲置或者做哨兵单元
int length;//顺序表长度
}SqList;//顺序表类型
2.插入排序
三种方法:直接插入排序,折半插入排序和希尔排序。
2.1直接插入排序
直接插入排序是一种最简单的排序方法。
void insertList(Sqlist &L)//直接插入法
{
if(L.r[i]<L.r[i-1])
{
for(i=2;i<=L.length;i++)
{
L.r[0]=L.r[i];//复制监视哨
for(j=i-1;L.r[0]<L.r[j];j--)//从后往前
{
L.r[j+1]=L.r[j];
}
L.r[j+1]=L.r[0];//注意是j+1
}
}
}
时间复杂度为O(n*n)
空间复杂度为O(1)
特点:稳定排序,方法简单容易实现,也适用于链式存储结构。
但时间复杂度高,当n较大时不适宜使用。
2.2折半插入排序
就是查找操作时用折半查找来实现。
void BinsertSot(Sqlist &L)//折半插入排序,这个就是先把折半查找算法写出来,就是找插入的位置,然后再写后移的算法,一定要列举列子
{
int i,low,j,m,high;
for(i=2;i<L.length;i++)//跟直接插入排序相似
{
L.r[0]=L.r[i];
low=1;
high=i-1;
while(low<=high)//折半查找找到位置插入,注意这里等号能不能取
{
m=(low+high)/2;
if(L.r[0]<L.r[m])
{
high=m-1;//从其前面一个开始查找,不能从m位置开始
}
else{
low=m+1;//从后一位开始
}
}
for(j=i-1;j>=high+1;j--)//不一定用high,也可以用low或者m
{
L.r[j+1]=L.r[j];//找个简单的例子试试,容易出错
}
L.r[high+1]=L.r[0];
}
}
时间复杂度也是O(n*n)但是就平均性能来说,折半查找要优于直接插入排序
空间复杂度为O(1)。
算法特点:稳定排序,适合初始记录无序、n较大的情况;不能用于链式存储。
2.3希尔排序
希尔排序又称缩小增量排序。
基本思想:从减少记录个数和序列基本有序两个方面对直接插入排序进行改进。(分别对每个子序列进行插入排序)
就是将n个记录分成d个子序列,其中d为增量,增量是从大到小是我一组值,增量序列中的值没有除1以外的公因子,而且最后一个增量一定为1。
void ShellInsert(Sqlist &L,int k)//希尔排序,就是将直接排序中的减一换成减k
{
for(i=k+1;i<=L.length;i++)//注意不能从1开始,而是从K+1开始
{
if(L.r[i]<L.r[i-k])
{
L.r[0]=L.r[i];
for(j=i-k;j>0 && L.r[j]<L.r[0];j=j-k)
{
L.r[j+k]=L.r[j]
}
L.r[j+k]=L.r[0];
}
}
}
void ShellSort(SqList &L,int dt[],int t)
{
int k;
for(k=0;k<t;k++)//希尔排序需要很多次排序,按照数组中的增量来排序
{
ShellInsert(L,dt[k]);
}
}
需要对顺序表L进行t趟希尔插入排序。
空间复杂度为O(1)
算法特点:排序算法不稳定,不能用于链式存储,增量序列中没有除1之外的公因子,而且最后一个增量值必须等于1,其中n越大越明显。
这三个排序都是将插入的数据放入到监视哨中。
3.交换排序
3.1冒泡排序
冒泡排序是一种简单的交换排序方法,通过两两比较相邻记录的关键字来进行排序。
//冒泡排序
void BubbleSort(SqList &L)//这个是两两交换,先确定最后一个,所以先写一次交换的,再写嵌套循环
{
int a,change;
change=1;
for(j=1;j<L.length && change=1;j++)//change是用来判断循环是否结束的
{
change=0;
for(i=1;i<L.length-j+1;i++)//注意r0的位置是被闲置的,这个是相邻的依次做比较,与交换法区分开
{
if(L.r[i]>L.r[i+1])
{
a=L.r[i+1];
L.r[i+1]=L.r[i];
L.r[i]=a;
change=1;
}
}
}
}
//交换法
void SwapSort(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])
{
int a;
a=L.r[j];
L.r[j]=L.r[i];
L.r[i]=a;
}
}
}
}
在平均的情况下,时间复杂度为O(n的平方)。
空间复杂度为O(1)。
算法特点:是一种稳定排序,也可用于链式存储结构,当n较大时此算法不宜采用。
3.2快速排序
快速排序是由冒泡排序改进而得的。
快速排序的目标:找一个记录,以他的关键字作为枢纽,凡是关键字小于枢纽的记录均移动到该记录之前,反之,凡是关键字大于枢纽的记录均移动到该记录之后,在一趟快速排序之后将无序序列分成两部分,利用递归,重复n次,最后只剩一个数,可完成排序。
//快速排序
int Partition(SqList &L,int low,int high)//这个不太好想
{
L.r[0]=L.r[low];//low一般就是1
int a;
a=L.r[low];//low作为中枢,来分割
while(low<high)
{
while(L.r[high]>=a &&low<high)
{
high=high-1;
}
L.r[low]=L.r[high];
while(L.r[low]<=a && low<high)
{
low=low+1;
}
L.r[high]=L.r[low];
}
L.r[low]=L.r[0];
}
void QSort(SqList &L,int low,int high)//low一般就是初值1
{
int pivotloc;
if(low<high)
{
pivotloc=Partition(L,low,high);//寻找枢纽位置,方便递归的使用
QSort(L,low,pivotloc-1);//这里的low跟调用函数中的low不同,对左子表进行排序
QSort(L,pivotloc+1,high);//对右子表进行排序
}
}
void QuickSort(SqList &L)
{
QSort(L,1,L.length);
}
平均情况下,快速排序的时间复杂度为O(n*logn)
快速排序下,最好的空间复杂度为O(logn),最坏的情况下为O(n)
算法特点:是不稳定的排序方法,需要确定上下界,很难适用于链式存储中,当n较大时,在平均情况下,快速排序是所有内部排序中速度最快的一种,所以适合于初始记录无序、n较大的情况,反而不适用于已经排好的数据中。