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

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

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:归并排序算法:

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

排序算法及其效率分析(一)内排序_第1张图片

算法性能分析: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)

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

你可能感兴趣的:(排序算法及其效率分析(一)内排序)