您的位置:首页 > 编程语言 > C语言/C++

常用排序算法分析实现(C语言)

2015-10-07 10:25 197 查看



最近复习了一下内部排序的几种排序算法,并用c语言实现(都是以数组形式存储非负整数数据的,非链表)。此次实现的排序算法分别是冒泡排序,选择排序,插入排序,希尔排序,快速排序,堆排序,归并排序,基数排序。下面分别对这几种算法进行分析(均是由小到大排序):

冒泡排序

void maopao(int arr[])
{
int i,j,flag,temp;
for(i=MAX-1;i>0;i–)
{
flag=0;
for(j=0;j<i;j++)
{
if(arr[j]>arr[j+1])
{
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
flag++;
}
}
if(flag==0) break;
}
}

算法描述:

MAX是待排序数组元素数目,下同。i控制每轮要比较的次数,j控制要主动进行比较的元素位置。j=0首先从第一个元素开始跟其后面相邻的一个元素比较,如果前面元素大与后面元素,则对调。然后将调换后的第二个元素和第三个元素进行比较并决定是否调换。后面的元素依次进行比较,这样第一轮比较之后最大值就会放在了最后一个位置上。然后进行第二轮比较,还是从第一个元素开始,不过这次只需要比较到倒数第二个元素即可。依次第三轮,第四轮,比较次数每轮减少一次。flag控制每一轮的调换次数,每一轮中都初始化为0,然后该轮中调换一次flag就自加1,该轮结束后判断flag,如果flag为0说明这一轮没有发生元素对调,也就是现在数据已经有序,即可跳出,结束算法。

算法分析:

冒泡排序的对调是发生在相邻两个数据之间,两数据相等时不会进行对调。不管相等的两个数据原本是不是相邻,都不会将其前后顺序改变的,所以冒泡排序是稳定排序。

n个元素,平均情况下,第一轮比较n-1次,第二轮n-2次,共需比较(n-1)+(n-2)+…+1=n(n-1)/2次,时间复杂度O(n2)。

整个过程中只需要一个空间用来暂存待交换数据,空间复杂度O(1)。

选择排序

void xuanze(int arr[])
{
int i,j,temp;
for(i=0;i<MAX-1;i++)
{
for(j=i+1;j<MAX;j++)
{
if(arr[i]>arr[j])
{
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
}
}

算法描述:

i控制要比较的一个元素位置,j控制要比较的另一个元素位置。i先定住第一个元素,然后跟j控制的第二个元素比较,如果前面的元素大,则交换,j自加1。然后交换后的由i控制的新的第一个元素继续跟j控制的第三个元素比较,前面的元素大则交换。依次直到跟j控制的最后一个元素比较并决定是否交换。第一轮结束,i自加1,能够保证第一个元素是最小值。第二轮开始,i控制第二个元素,j从i控制的元素后一个元素开始直到最后一个元素。依次第三轮,第四轮,当进行MAX-1轮之后排序完成。

算法分析:

因为选择排序是将最小(大)值直接跟要比较的值交换,这就使得值相同的两个元素前后相对顺序可能发生改变,所以是不稳定的排序。

n个元素,平均情况下,共需要比较(n-1)+(n-2)+…+1=n(n-1)/2次,所以时间复杂度O(n2)。

整个过程中只需要一个空间用来暂存待交换数据,空间复杂度O(1)。

插入排序

void charu(int arr[])
{
int i,j,k,temp;
for(i=1;i<MAX;i++)
{
for(j=i-1;j>=0;j–)
{
if(arr[i]>arr[j]) break;
}
temp=arr[i];
for(k=i;k>=j+2;k–)
arr[k]=arr[k-1];
arr[j+1]=temp;
}
}

算法描述:

插入排序是逐一将数组中的元素与前面已经排好序的元素比较,再将该元素插入到合适的位置上。i控制一个要比较的元素,j控制另一个要比较的元素。i从第二个元素(下标1)开始自加,j从i控制的前一个元素开始自减。i控制的第二个元素跟j控制的第二个元素之前的所有元素都按下标由大到小逐个进行比较,当比较到i控制的某个元素比j控制的元素大时,通过break跳出,然后j控制的元素后面的元素开始直到i控制的前一个元素的所有元素整体后移一个位置。将i控制的元素插入到前面空出来的位置,而现在i控制的位置正好因为整体的后移补上。此时第一轮比较结束。i自加之后依次进行第二轮,第三轮,直到MAX-1轮为止。排序完成。

算法分析:

资料中都讲插入排序是稳定的排序,但是我这里实现的算法,当待插入值遇到前面的与其相等的数值时,会插入到等值的前面,这样就改变了两个相等值的前后相对位置,这就是不稳定的排序。其实这里的关键就是在判断插入位置的地方,如果本程序中我把if(arr[i]>arr[j]) break 改为if(arr[i]>=arr[j]) break 那就变成了稳定的排序。正统的插入排序还是按照稳定的排序来对待吧。

n个元素,平均情况下,需要比较1+2+3+…+(n-2)+(n-1)=n(n-1)/2次,时间复杂度O(n2)。

整个过程中只需要一个空间用来暂存待交换数据,空间复杂度O(1)。

因为插入排序每一轮都会进行大量的数据移动,所以在数组结构上使用代价比较大,更适合在链表结构上使用。

希尔排序

void xier(int arr[])
{
int i,j,jmp,temp;
jmp=MAX/2;
while(jmp!=0)
{
for(i=jmp;i<MAX;i++)
{
temp=arr[i];
j=i-jmp;
while(arr[j]>temp&&j>=0)
{
arr[j+jmp]=arr[j];
j=j-jmp;
}
arr[j+jmp]=temp;
}
jmp=jmp/2;
}
}

算法描述:

jmp控制间距步长,从MAX的一半(下取整)开始每次取半,直到为0。根据步长划分子集,然后在自己的子集里面利用插入排序方法排好序,结束一轮排序,然后改变步长开始第二轮,依次第三轮,第四轮,直到步长变为0。这里i初始化为jmp,正好是控制第一个子集的最后一个元素,然后i-jmp正好控制着该子集中前一个元素。

算法分析:

希尔排序的稳定性分析跟插入排序一样,判断插入位置是关键,正统还是认为是稳定排序。

希尔排序的时间复杂度比较复杂,跟取的步长也有关系。所以这里就不作出分析了。有的资料说O(n3/2),有的说O(nlgn),还有的说O(n^1.3),仁者见仁吧,在此不作结论。

整个过程中只需要一个空间用来暂存待交换数据,空间复杂度O(1)。

快速排序

void kuaisu(int arr[],int start,int end)
{
int key=arr[start],i=start,j=end,temp;
if(start>=end) return;
while(i<j)
{
while(arr[i]<=key&&i<end) {i++;}
while(arr[j]>=key&&j>start) {j–;}
if(i>=j) break;
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
i++;
j–;
}
temp=arr[j];
arr[j]=arr[start];
arr[start]=temp;
kuaisu(arr,start,j-1);
kuaisu(arr,j+1,end);
}

算法描述:

首先(1)先把整个待排序数组作为一个子集,选定第一个元素为基点值K。(2)然后由左向右找出第一个值Ki,使得Ki大于K。(3)再由右向左找出第一个值Kj,使得Kj小于K。(4)如果i小于j,则将Ki和Kj交换,然后继续执行第(2)步。(5)若i大于等于j,则将K与Kj交换,这样就以j为中点将数据分成左右两部分,并且左边都小于等于K,右边都大于等于K。以递归的方式分别对左右两个子集进行排序,直到完成排序,也就是子集中只有一个数据了。

算法分析:

排序过程中相等的两个值的前后相对顺序可能改变,所以不是稳定的排序。

平均情况下,时间复杂度O(nlogn)。

每一次调用快速排序需要申请一个暂存待交换数据的空间,所以空间复杂度就是O(1)x调用次数。当原数据已经有序,每次划分子集都是只能减少一个数据,调用次数为n次,即最坏情况下空间复杂度O(n)。最好或者说平均情况下,需要调用logn次,所以空间复杂度O(logn)。

堆排序(非最优,更优算法见后面)

void dui(int arr[],int end)
{
if(end==0) return;
int i,temp;
for(i=1;i<=end;i++)
{
int t=i;
while(arr[t]>arr[(t-1)/2]&&t>=1)
{
temp=arr[t];
arr[t]=arr[(t-1)/2];
arr[(t-1)/2]=temp;
t=(t-1)/2;
}
}
temp=arr[end];
arr[end]=arr[0];
arr[0]=temp;
dui(arr,end-1);
}

算法描述:

从小到大排序,这里采用大顶堆,然后最大值是根节点,每次建立大顶堆之后根节点与最末尾结点交换,这样每一轮结束都会确定一个最大值放在末尾,全部排序完成后整体顺序就变成了由小到大了。首先将原始数据表示成二叉树,然后由根节点开始,与其两个孩子分别进行比较,如果孩子节点大于父节点,进行交换,然后此时的父节点再跟他的父节点进行比较,处理方式相同,直到孩子节点小于等于父节点,就继续将下一个元素与其父节点比较。全部元素都跟其父节点比较之后,创建了大顶堆,将根节点与末尾结点交换,并递归地调用处理除去最后一个元素的数组。依次递归调用直到排序完成。

算法分析:

堆排序是不稳定的排序。

查阅资料,正统堆排序时间复杂度O(nlogn)。但是分析这里实现的算法,应该是O(n2)。这里时间复杂度不好分析,其实有点怀疑自己实现的算法是不是正统公认的最优堆排序。。

这里实现的算法,每一次递归调用都要申请一个空间temp暂存待排序数据,一共调用n次,空间复杂度O(n)。如果将temp地址作为参数传入函数中,每次调用都是用的同一个空间,那空间复杂度可以为O(1)。

基数排序

void jishu(int arr[],int p)
{
int n,i,j,m;
for(n=1;n<=pow(10,p);n=n*10)
{
int temp[10][MAX]={0};
for(i=0;i<MAX;i++)
{
m=(arr[i]/n)%10;//取其N位上的值
temp[m][i]=arr[i];
}
int k=0;
for(i=0;i<10;i++)
{
for(j=0;j<MAX;j++)
{
if(temp[i][j]!=0)
arr[k++]=temp[i][j];
}
}
}
}

算法描述:

算法思想就是根据个位、十位这种单位上的数值进行划分排列,最终形成有序数据。没有图不好形容。。。直接看代码吧。

算法分析:

稳定排序。

时间复杂度O(nlogpk),其中k是原始数据最大值,p是数据位数。

空间复杂度O(np)。

当n很大,p固定或很小时候,效率比较高。

归并排序

//两数组合并
void merge(int arr[],int first,int mid,int last)
{
int temp[MAX];
int i=first,j=mid+1,k,n=0;
while(i<=mid&&j<=last)
{
if(arr[i]<=arr[j])
temp[n++]=arr[i++];
else
temp[n++]=arr[j++];
}
while(i<=mid)
{
temp[n++]=arr[i++];
}
while(j<=last)
{
temp[n++]=arr[j++];
}
for(k=first;k<=last;k++)
arr[k]=temp[k-first];
}
//归并排序
void guibing(int arr[],int first,int last)
{
if(first<last)
{
int mid=(first+last)/2;
guibing(arr,first,mid);
guibing(arr,mid+1,last);
merge(arr,first,mid,last);
}
}

算法描述:

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

其基本思路就是将数组分成二组A、B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。

算法分析:

稳定的排序。

时间复杂度O(nlogn)。

空间复杂度O(n)。

排序算法全部完成,可以写一个main函数测试一下。

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define MAX 10
int array[MAX];
int main()
{
int i,t;
//生成两位数以内的伪随机数
for(i=0;i<MAX;i++)
{
t=rand()%50;
if(t!=0)
array[i]=t;
else
array[i]=t+1;
}
printf(“排序前:\n”);
for(i=0;i<MAX;i++)
{
printf(“%d “,array[i]);
}
printf(“\n”);
//maopao(array);
//xuanze(array);
//charu(array);
//xier(array);
//kuaisu(array,0,MAX-1);
//dui(array,MAX-1);
//jishu(array,2);
guibing(array,0,MAX-1);
printf(“排序后: \n”);
for(i=0;i<MAX;i++)
{
printf(“%d “,array[i]);
}
printf(“\n”);
return 0;
}

最优堆排序

#include<stdio.h>
#include<stdlib.h>

void heapadjust(int arr[],int start,int length)
{
int child,tmp;
int i=start,n=length;
for (tmp=arr[i];n>=2*i;i=child)
{
// i的左孩子为2*i,右孩子为2*i+1
child=2*i;
// 让child指向孩子中较大的一个
if (child!=n&&arr[child+1]>arr[child])
child++;
//如果孩子节点大
if (tmp<arr[child])
{
//交换孩子节点和根节点
arr[i]=arr[child];
}
else break;
}
// 将根放在合适位置
arr[i]=tmp;
}

// 对a[1…n]进行排序
void heapsort(int arr[],int length)
{
int i,tmp;
// 将a[1…n]建成大根堆
for(i=length/2;i>=1;i–)
{
heapadjust(arr,i,length);
}
// 进行n-1趟排序
for(i=length;i>=2;i–)
{
//交换堆顶元素和最后一个元素
tmp=arr[1];
arr[1]=arr[i];
arr[i]=tmp;
// 将a[1…i-1]重建为堆
heapadjust(arr,1,i-1);
}
}

int main()
{
//排序是从下标1开始排序,数组0号元素忽略
int array[11]={0,3,5,7,9,1,6,2,4,8,10};
int i;
printf(“排序前:\n”);
for(i=1;i<=10;i++)
printf(“%d “,array[i]);
printf(“\n”);
heapsort(array,10);
printf(“排序后:\n”);
for(i=1;i<=10;i++)
printf(“%d “,array[i]);
printf(“\n”);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: