1、 插入排序,时间复杂度O(n^2)
一个数组A[0,...,n-1],将A[j]插入到子数组A[0,...,j-1]中,其中数组A[0,...,j-1]是排好序的(假如为非降序),把A[j]和A[j-1]->A[0]逐个进行比较,大的数往后移位,将A[j]插入适当的位置(找到最后一个比它大的,放在前面)。
递归法,为排序A[0,...,n-1],先排序A[0,...,n-2],然后把A[n-1]插入到已经排序的数组A[0,...,n-2]中去。如下:
#include<stdio.h>
#include<iostream.h>
//算法导论2.3-4
//将数number插入已经排好序(非降序)的数组中
void insert(int num[],int len,int number){
int i=len-1;
while(i>=0&&num[i]>number){
num[i+1]=num[i];
i--;
}
num[i+1]=number;
}
//递归程序
void insert_into(int num[],int n){
if(n>1){
insert_into(num,n-1);
}
insert(num,n,num[n]);
}
int main(){
int num1[8]={4,2,5,1,8,10,3,7};
int len=8;
insert_into(num1,len-1);
for(int k=0;k<len;k++){
cout<<num1[k]<<endl;
}
return 0;
}
2、选择排序,时间复杂度O(n^2)
首先找出A中最小的元素,并将其与A[0]互换,接着找A中次小元素,与A[1]互换,以此类推。并且仅需要在n-1个元素上运行。伪代码如下:
for i<-1 to n-1 do//伪代码中下标从1开始
min<-Find_min(A[i,...,n])//此函数找出A中从下标i到n的最小元素,时间复杂度O(n)
min<->A[i]
endfor
根据以上伪代码可知,选择排序的时间复杂度为O(n^2)
最佳和最坏情况下的运行时间都为Θ(n²)。因为这种直接选择排序,其关键字的比较次数与各元素原来的排列顺序无关。
3、合并排序(递归法),时间复杂度O(n㏒2n)
分解:将n个元素分成各含n/2个元素的子序列
解决:用合并排序法对两个子序列进行递归排序
合并:合并两个已经排好序的子序列得到结果(merge函数)
#include<stdio.h>
#include<iostream.h>
#include <limits.h>
//算法导论p17
void merge(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
int n1=q-p+1;
int n2=r-q;
int *left=new int[n1+1];
int *right=new int[n2+1];
for(int i=0;i<n1;i++){
left[i]=num[p+i];
}
for(i=0;i<n2;i++){
right[i]=num[q+1+i];
}
left[n1]=INT_MAX;//哨兵牌
right[n2]=INT_MAX;
int j=0,k=0;
for(i=p;i<=r;i++){
if(left[j]>right[k]){
num[i]=right[k];
k++;
}
else{
num[i]=left[j];
j++;
}
}
for(i=p;i<=r;i++){
cout<<num[i]<<endl;
}
}
void merge_sort(int num[],int p,int r){
int q=0;
if(p<r){
q=(p+r)/2;
merge_sort(num,p,q);
merge_sort(num,q+1,r);
merge(num,p,q,r);
}
}
int main(){
int num[8]={2,4,5,7,1,2,3,6};
int p=0,r=7;
int q=(p+r)/2;
merge_sort(num,p,r);
return 0;
}
merge函数是从头开始分别比较两个子序列的数的大小,将较小的数字存入num中,相应队列的指针往后移。为了避免在每个步骤中都要检查每个序列是否为空,增加一个哨兵牌,包含一个特殊的值,来简化代码。此函数中,用正无穷来作为哨兵值,这样每当露出一张值为无穷的哨兵牌,其不可能是两张中最小的一张,除非另外一个序列也露出哨兵牌。
merge函数的时间复杂度为O(n),n=r-p+1
而合并排序,将问题分为1/2->1/4->1/8,总共分了㏒2n层,回推时每一层的代价是O(n)
4、合并排序的应用,求逆序对的个数,且算法的时间复杂度为O(n㏒2n)
逆序对:如果有i<j时,A[i]>A[j],则(i,j)叫做一个逆序对
合并排序法求逆序对的个数,当求出逆序对数目后,数组也已经排好序了,如果不想让原数组发生变化,可以把原数组拷贝到另一个数组,对新数组操作就好。整个过程一边排序一边数,递归到最深的地方如果有一个数,那么返回0,如果有两个数,看做有两个元素的子数组,两个子数组都已经排好序,并且左边只有一个元素的子数组的逆序对是0,右边只有一个元素的子数组的逆序对是0,那么两个元素的子数组的逆序对的个数就是1,若这两个数逆序,否则是0对。所以,当递归进行到最后一步,也就是这样的:a[1]到a[n/2]已经排好序,并且逆序对的个数为Inv1,a[n/2+1]到a[n]已经排好序,并且逆序对的个数为Inv2,那么整个数组的逆序对总数就是Inv1+Inv2+最后一步归并过程中的逆序对数。那么在merge这一步的逆序对个数又是怎么计算的呢?若i是左边子数组的下标(i的范围是p到q),j是右边子数组的下标(j的范围是q+1到r),假设某对(i, j),有a[i]>a[j],那么a[i]到a[q]都大于a[j],贡献的逆序对数为q-i+1。
#include<stdio.h>
#include<iostream.h>
#include <limits.h>
//算法导论p24 2-4d
int merge_num(int num[],int p,int q,int r){//假设num[p..q],num[q+1...r]已经排好序,将其合并成一个已经排好序的数组
int n1=q-p+1;
int n2=r-q;
int *left=new int[n1+1];
int *right=new int[n2+1];
int n=0;//n表示逆序对数
for(int i=0;i<n1;i++){
left[i]=num[p+i];
}
for(i=0;i<n2;i++){
right[i]=num[q+1+i];
}
left[n1]=INT_MAX;//32767
right[n2]=INT_MAX;
int j=0,k=0;
for(i=p;i<=r;i++){
if(left[j]>right[k]){
num[i]=right[k];
k++;
n+=n1-j;//注意不可是q-j+1,因为q和j指的意义不同,一个是原来数组里的下标,一个是新数组里的下标
}
else{
num[i]=left[j];
j++;
}
}
return n;
}
int merge_sort(int num[],int p,int r){
int q=0;
int total=0;
if(p<r){
q=(p+r)/2;
total+=merge_sort(num,p,q);//之前的左半部
total+=merge_sort(num,q+1,r);//之前的右半部分
total+=merge_num(num,p,q,r);//这次合并时加上total
}
return total;
}
int main(){
int num[8]={8,7,6,5,4,3,2,1};
int p=0,r=7;
int q=(p+r)/2;
//cout<<merge_num(num,p,q,r)<<endl;
cout<<merge_sort(num,p,r)<<endl;
for(int i=p;i<=r;i++){
cout<<num[i]<<endl;
}
return 0;
}
此时,因为要记录每次递归的逆序对数,因此,递归函数merge_sort中total的写法需要注意。
5、折半查找(递归程序),时间复杂度O(㏒2n)
int binary_search(int num,int num_arr[],int start,int end){
int mid=0;
if(start>=end&&num_arr[start]!=num){//注意递归终止条件
return 0;
}
mid=(start+end)/2;
if(num==num_arr[mid]){
return mid;
}
else if(num<num_arr[mid]){
binary_search(num,num_arr,start,mid);
}
else{
binary_search(num,num_arr,mid,end);
}
}
本文详细介绍了几种常见的排序算法,包括插入排序、选择排序、合并排序等,解析了它们的时间复杂度及应用场景。并通过示例代码展示了如何实现这些算法。
792

被折叠的 条评论
为什么被折叠?



