排序算法及其效率分析(一)内排序

来源:互联网 发布:爱剪辑 for mac 下载 编辑:程序博客网 时间:2024/06/05 20:37

只要是接触编程,接触算法,排序是必修的一门基础课,它的应用之大不言而喻,下面就几种常用排序进行实现并进行时间复杂度的分析:

1:选择排序法

举个例子:对于2 9 5 4 8 1 6

step1:在列表中先选择最大元9并与最后一个数字6交换 2 6 5 4 8 1 9;

step2:在剩余列表选择最大元8并与最后一个数字1交换 2 6 5 4 1 8 9;

step3:在剩下列表选择最大元6并与最后一个数字1交换 2 1 5 4 6 8 9;

step4:在剩下列表选择最大元5并与最后一个数字4交换 2 1 4 5 6 8 9;

step5:剩下列表最大元4已经在合适位置不用交换;

step6:在剩下列表选择最大元2并与最后一个数字1交换 1 2 4 5 6 8 9;

最后只剩一个数字,无需再排序,排序完成。

算法时间复杂度分析:第一步迭代需要n-1次比较操作;第二步需要n-2个比较。。。C表示其他常见操作(赋值,每次迭代的比较)

T(n)=n-1+n-2+...+1+c*(n-1)=n^2/2-n/2+c*(n-1)=O(n^2)

代码:

void sort_sele(int a[],int size){int i,j;int k,max;for(i=0;i<size-1;i++){  k=0;   max=a[0];  for(j=0;j<size-i;j++)  if(max<a[j]) {max=a[j];k=j;}  if(k!=i){int t=a[k];  a[k]=a[size-1-i];  a[size-1-i]=t;}}}
2:插入排序法

还是例子2 9 5 4 8 1 6

step1:初始时,已排序列表只含列表第一个元素。将9插入:2 9;

step2:已经排序的2,9,将接下来的5插入列表:2 5 9;

step3:已经排序的2 5 9 ,将接下来的4插入列表:2 4 5 9;

step4:已经排序的2 4 5 6,将接下来的8插入列表:2 4 5 8 9;

step5:已经排序的2 4 5 8 9,将接下来的1 插入列表:1 2 4 5  8 9;

step6:已经排序的1 2 4 5 8 9,接下来的6插入列表:1 2 4 5 6 8 9.

step:整个排序完成。

算法性能:第k个迭代,需要将元素插入一个规模k的有序数组,需要k次比较k次数据移动,C是其他操作

T(n)=2*(1+2+...+n-1)+c*(n-2)=O(n^2 ;

代码:

void sort_inse(int a[],int size){int i,j;int re;for(i=1;i<=size-1;i++){   j=i-1;   re=a[i];   while(j>=0&&a[j]>re){a[j+1]=a[j];j--;}a[j+1]=re;}}
3:冒泡排序算法

例子:2 9 5 4 8 1 6

step1:将2 9交换,因为已经是升序不用再动;将9 5交换成5 9;将9 4 交换成4 9;将9 8交换成8 9;将9 1交换成1 9;将9 6交换成6 9;----最大元素沉底2 5 4 8 1 6 9;

step2: 类似的将8沉底得到2 4 5 1 6 8 9;

step3:类似的将6沉底得到2 4 5 1 6 8 9;

step4:类似的将5沉底得到2 4 1 5 6 8 9;

step5:类似的将4沉底得到2 1 4 5 6 8 9;

step6:类似的将2沉底得到1 2 4 5 6 8 9;

最后完成

算法分析:

在最好情况时第一步就发现已经有序,则无需再操作,比较此时n-1,O(n);

最坏情况:总共n-1次扫描;第k次扫描需要比较n-k次比较

T(n)=1+2+...(n-1)+C*(n-1)=O(n^2);

代码:

void sort_mao(int a[],int size){int i,j;int k,t;for(i=0;i<size-1;i++)for(j=0;j<size-1-i;j++){if(a[j]>a[j+1]) {t=a[j];a[j]=a[j+1];a[j+1]=t;}}}
4:归并排序算法:

它是一种递归算法,将数组划分两半,对每一半不断递归知道得到一个元素的数组,然后对最终子数组进行归并排序

算法性能分析:T(n)=T(n/2)+T(n/2)+合并时间

第一个T(n/2)是数组前一半所需,第二个T(n/2)是数组后一半所需,合并时间包括n-1次比较和n次n次移动

T(n)=T(n/2)+T(n/2)+2*n-1=2*T(n/2)+2*n-1=2*(2*T(n/4)+2*n/2-1)+2*n-1=...=2nlogn+!=O(nlogn);

代码:

////归并算法之分半部分void copy(int source[],int sindex,int dest[],int dindex,int len){for(int i=0;i<len;i++)  dest[i+dindex]=source[i+sindex];}//将一个数组sindec处开始的len个值赋给dest
//归并算法之归并部分void merge(int a[],int len1,int b[],int len2,int temp[]){int i1=0;int i2=0;int i3=0;while(i1<len1&&i2<len2){if(a[i1]>b[i2]) temp[i3++]=b[i2++];else temp[i3++]=a[i1++];}//将a,b里面较小的放入temp,直到一个放完while(i1<len1) temp[i3++]=a[i1++];while(i2<len2) temp[i3++]=b[i2++];//将还有剩余的数组放入}
//归并算法void mergesort(int a[],int size){if(size>1){int *half1=new int[size/2];copy(a,0,half1,0,size/2);mergesort(half1,size/2);int *half2=new int[size-size/2];copy(a,size/2,half2,0,size-size/2);mergesort(half2,size-size/2);//归并算法之分半部分int *temp=new int[size];merge(half1,size/2,half2,size-size/2,temp);//归并算法之归并部分copy(temp,0,a,0,size);    delete []half1;delete []half2;delete []temp;}}
5:快速排序算法:

首先在数组中选择一个元素做枢轴,将数组分为两部分,第一部分都小于枢轴,第二部分都大于,随后对子数组递归使用快速排序

例如2 9 5 4 8 1 6

step1:选择第一个元素2为枢轴,分成两部分:1   2   5 4 8 9 6

step2:对于5 4 8 9 6 选择第一个元素5为枢轴,分成4 5  8 9 6

step3:对于8 9 6以排好序

算法性能:快速算法主要分为划分函数部分,在最坏情况下,需要花费n次比较,n次移动故划分时间复杂度O(n)

T(n)=T(n/2)*2+n(划分时间)=O(n*logn)

代码:

//快速排序之划分int part(int a[],int first,int last){int pivote=a[first];int low=first+1;int high=last;while(high>low){//从左向右搜索比枢轴大的while(low<=high&&a[low]<=pivote)low++;//从右向左搜索比枢轴小的while(low<=high&&a[high]>pivote) high--;//直到第一个大于枢轴的左边和第一个小于枢轴的右边出现,交换if(high>low){int t=a[low];a[low]=a[high];a[high]=t;}}//返回此次枢轴的坐标while(high>first&&pivote<=a[high])high--;if(pivote>a[high]){a[first]=a[high];a[high]=pivote;return high;}else return first;}
//快速排序void quicksort(int a[],int first,int last){if(last>first){int pri=part(a,first,last);quicksort(a,first,pri);quicksort(a,pri+1,last);}}void quicksort(int a[],int size){quicksort(a,0,size-1);}
5:堆排序算法

二叉树满足:每个节点都大于等于其任何子节点:首先使用heap类创建一个对象,使用add函数将元素入堆,然后使用remove函数删除返回,从而完成排序

算法性能分析:h表示一个n个元素的堆的高度,由于堆是完全二叉树,所以第K层有2^(k-1)个节点,第h层至少一个节点,至多2^(h-1)

1+2.。。+2^(h-2)<n<=1+2.。。+2^(h-2)+2^(h-1)--->可以得到  h-1<log(n+1)<=h,那么堆的高度log(n+1)<=h<log(n+1)+1,O(logn);由于add是从叶至根的,最多h个步骤添加一个元素那么对于一个n的数组,创建一个堆需要O(n*logn),remove函数是根到叶,删除根节点后重建堆的操作至多花费h个操作,同add一样调用了n次,所以O(n*logn)。

T(n)=O(n*logn)+O(n*logn)=O(n*logn)

代码:

#include"heap.h"void sort_sort(int a[],int size){heap<int> heap;for(int i=0;i<size;i++)heap.add(a[i]); for(int i=0;i<size;i++) a[i]=heap.remove();}


总结:一、归并排序和快速排序都使用了分而治之思想,归并重在列表合并操作,发生在子列表都排序之后;而快速排序重在列表划分,发生在子列表都排序之前。归并最坏情况好于快速,但是平均情况都是O(n*logn),但是归并还需要一个临时数组故空间复杂度高一些。堆的时间复杂度也是O(nlogn),不用额外数组。冒泡,插入,选择的平均复杂度O(n^2)

 二、选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而 冒泡 排序、插入排序、归并排序和基数排序是稳定的排序算法。

0 0
原创粉丝点击