内部排序方法图解以及代码
1. 直接插入排序
直接插入排序是一种简单的排序方法,具体做法是:在插入第i个记录时,R1、R2、…、Ri-1已经排好序,这时将Ri的关键字Ki依次与关键字Ki-1、Ki-2等进行比较,从而找出应该插入的位置并将Ri插入,插入位置及其后的记录依次向后移动。
void Insertsort(int data[],int n)
/*将数组data[0]~data[n-1]中的n个整数按非递减有序的方式进行排列*/
{
int i,j;
int tmp;
for(i=1;i<n;i++)
if(data[i]<data[i-1]){
tmp=data[i];
data[i]=data[i-1];
for(j=i-1;j>=0&&data[j]>tmp;j--)
data[j+1]=data[j];
data[j+1]=tmp;
}
}
2. 冒泡排序
n个记录进行冒泡排序的方法是:首先将第一个记录的关键字和第二个记录的关键字进行比较,若为逆序,则交换这两个记录的值,然后比较第二个记录和第三个记录的关键字,以此类推,直到第n-1个记录和第n个记录的关键字比较过位置。上述过程为第一趟冒泡排序,其结果是关键字最大的记录被交换到第n个记录的位置上。然后进行第二趟冒泡排序,对前n-1个记录进行同样的操作,其结果是关键字次大的记录被交换到第n-1个记录的位置上。最多进行n-1趟,所有记录有序排列。若在某趟冒泡排序过程没有进行相邻位置的元素交换处理,则可结束排序过程。
3. 简单选择排序
n个记录进行简单选择排序的基本方法是:通过n-i(1<=i<=n)在次关键字之间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录进行交换,当i等于n时,所有记录有序排列。
void SelectSort(int data[],int n)
/*将数组data中的n个整数按非递减有序的方式进行排序*/
{
int i,j,k,tmp;
for(i=0;i<n-1;i++){
k=i;
for(j=i+1;j<n;j++) /*找出最小关键字的下标*/
if(data[j]<data[k])
k=j;
if(k!=i){
tmp=data[i];
data[i]=data[k];
data[k]=tmp;
}
}
}
4. 希尔排序
希尔排序又称为“缩小增量排序”,它是对直接插入排序方法的改进。
希尔排序的基本思想是:先将整个待排序记录序列分割成若干子序列,然后分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序。具体做法是:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分为d1个组,即将所有距离为d1倍数序号的记录放在同一个组中,在各组内进行直接插入排序;然后取第二个增量d2(d2<d1),重复上述分组和排序工作,以此类推,直到所取的增量di=1(di<di-1<…d2<d1),即所有记录放在同一组进行直接插入排序为止。
void ShellSort(int data[],int n)
{int *delta,k,i,t,dk,j;
k=n;
/*从k=n开始,重复k=k/2运算,直到k=1,所得k值的序列作为增量序列存入delta*/
i=0;
do{
k=k/2;
delta[i++]=k;
}while(k>1);
i=0;
while((dk=delta[i])>0){
for(k=delta[i];k<n;++k)
if(data[k]<data[k-dk]){ /*将元素data[k]插入到有序增量子表中*/
t=data[k]; /*备份待插入的元素,空出一个元素位置*/
for(j=k-dk;j>=0 && t<data[j];j-=dk)
data[j+dk]=data[j]; /*寻找插入位置的同时元素后移*/
data[j+dk]=t; /*找到插入位置,插入元素*/
}
++i; /*取下一个增量值*/
}
}
5. 归并排序
所谓“归并”,是将两个或两个以上的有序文件合并成为一个新的有序文件。归并排序的一种实现方法是把一个有n个记录的无序文件看成是由n个长度为1的有序子文件组成的文件,然后进行两两归并,得到n/2个长度为2或1的有序文件,再两两归并,如此重复,直到最后形成包含n个记录的有序文件为止。这种反复将两个有序文件归并成一个有序文件的排序方法称为两路归并排序。
两路归并排序的核心操作是将一维数组中前后相邻的两个有序序列归并为一个有序序列。
void Merge(int data[],int s,int m,int n){
/*将分别有序的data[s..m]和data[m+1..n]归并为有序的data[s..n]*/
int i,start=s,k=0;
int *temp;
temp=(int *)malloc((n-s+1)*sizeof(int)); /*辅助空间*/
for(i=m+1;s<=m && i<=n;++k) /*将data[s..m]与data[m+1..n]归并后存入temp*/
if(data[s]<data[i])
temp[k]=data[s++];
else temp[k]=data[i++];
for(;s<=m;++k) /*将剩余的data[s..m]复制到temp*/
temp[k]=data[s++];
for(i=0;i<k;i++)
data[start++]=temp[i];
free(temp);
}
void MSort(int data[],int s,int t){ /*对data[s..t]进行归并排序*/
{
int m;
if(s<t){ /*将data[s..t]均分为data[s..m]和data[m+1..t]*/
m=(s+t)/2;
MSort(data,s,m); /*递归地对data[s..m]进行归并排序*/
MSort(data,m+1,t); /*递归地对data[m+1..t]进行归并排序*/
Merge(data,s,m,t); /*将data[s..m]和data[m+1..t]归并为data[s..t]*/
}
}
6. 快速排序
快速排序的基本思想是:通过一趟排序将待排的记录划分为独立的两部分,称为前半区和后半区,其中,前半区中记录的关键字均不大于后半区记录的关键字,然后再分别对这两部分记录继续进行快速排序,从而使整个序列有序。
一趟快速排序的过程称为一次划分,具体做法是:附设两个位置指示变量i和j,它们的初值分别指向序列的第一个记录和最后一个记录。设枢轴记录(通常是第一个记录)的关键字为pivot,则首先从j所指位置起向后搜索,找到第一个关键字小于pivot的记录时将该记录向后移到j所指位置,重复该过程直至i与j相等为止。
int partition(int data[],int low,int high)
/*用date[low]作为枢轴元素pivot进行划分*/
/*使得data[low..i-1]均不大于pivot,data[i+1..high]均不小于pivot*/
{int i,j;
int pivot;
pivot=data[low];
i=low; j=hight;
while(i<j){ /*从数组的两端交替向中间扫描*/
while(i<j && data[j]>=pivot)
j--;
data[i]=data[j]; /*比枢轴元素小者往前移*/
while(i<j && data[i]<=pivot)
i++;
data[j]=data[i]; /*比枢轴元素大者往后移*/
}
data[i]=pivot;
return i;
}
void quickSort(int data[],int low,int high)
/*用快速排序方法对数组元素data[low..high]作非递减排序*/
{
if(low<high){
int loc=partition(data,low,high) /*进行划分*/
quickSort(data,low,loc-1); /*对前半区进行快速排序*/
quickSort(data,loc+1,high); /*对后半区进行快速排序*/
}
}
7. 堆排序
堆排序的思想:对一组待排序记录的关键字,首先按堆的定义排成一个序列(即建立初始堆),从而可以输出堆顶的最大关键字(对于大根堆而言),然后将剩余的关键字再调整成新堆,便得到次大的关键字,如此反复,直到全部关键字排成有序序列为止。
初始堆的建立方法是:将待排序的关键字分放到一颗完全二叉树的各个结点中(此时完全二叉树并不一定具备堆的特性),显然,所有i>(n/2)的结点Ki都没有子结点,以这样的Ki为根的子树已经是堆,因此初始建堆可从完全二叉树的第i(i=(n/2))个结点Ki开始,通过调整,逐步使以K(n/2)、K(n/2)-1、K(n/2)-2、…、K2、K1为根的子树满足堆的定义。
在对Ki为根的子树建堆的过程中,可能需要交换Ki与K2i(或K2i+1)的值,如此一来,以K2i(或K2i+1)为根的子树可能不再满足堆的定义,则应继续以K2i(或K2i+1)为根进行调整,如此层层地递推下去,可能会一直延伸到叶子结点为止。这种方法就像过筛子一样,把最大(或最小)的关键字一层一层地筛选出来,最后输出堆顶的最大(或最小)元素。
原始序列
建立初始堆(大根堆)
void HeapAdjust(int data[],int s,int m)
/*在data[s..m]所构成的一个元素序列中,除了data[s]外,其余元素均满足大根堆的定义*/
/*调整元素data[s]的位置,使data[s..m]成为一个大根堆*/
{
int tmp,j;
tmp=data[s]; /*备份元素data[s],为其找到适当位置后再插入*/
for(j=2*s+1;j<=m;j=j*2+1){ /*沿值较大的孩子结点想下筛选*/
if(j<m && data[j]<data[j+1])
++j; /*j是值较大的元素的下标*/
if(tmp>=data[j])
break;
data[s]=data[j]; s=j; /*用s记录待插入元素的位置(下标)*/
}
data[s]=tmp; /*将备份元素插入由s所指出的插入位置*/
}
void HeapSort(int data[],int n)
/*数组data[0..n-1]中的n个元素进行堆排序*/
{
int i; int tmp;
for(i=n/2;i>=0;--i) /*将data[0..n-1]调整为大根堆*/
HeapAdjust(data,i,n-1);
for(i=n-1;j>0;--i)
{
tmp=data[0]; data[0]=data[i];
data[i]=tmp; /*堆顶元素data[0]与序列末的元素data[i]交换*/
HeapAdjust(data,0,i-1); /*待排元素的个数减一,将data[0..i-1]重新调整为大根堆*/
}
}
8. 内部排序方法小结
排序方法 | 时间复杂度 | 辅助空间 | 稳定性 |
---|---|---|---|
直接插入 | O(n²) | O(1) | 稳定 |
简单选择 | O(n²) | O(1) | 不稳定 |
冒泡排序 | O(n²) | O(1) | 稳定 |
希尔排序 | O(n^1.3) | O(1) | 不稳定 |
快速排序 | O(nlogn) | O(logn) | 不稳定 |
堆排序 | O(nlogn) | O(1) | 不稳定 |
归并排序 | O(nlogn) | O(n) | 稳定 |