算法 基于比较的排序

插入排序

void insert_sort(int *ary,int n)
{
	int temp;
        int i,j;
	for(i=1;i<n;i++)// 从第二个数开始
	{
		temp=ary[i];
		for(j=i;j>0&&temp<ary[j-1];j--)
		{
			ary[j]=ary[j-1];
		}
		ary[j]=temp;
	}
}

生活中可以用来模拟插入排序的事件,例如打牌中的将牌排序,十分形象具体

通常情况下插入排序O(N^2),基本有序(最好情况下)时是O(N),是稳定排序

折半插入排序可以在比较次数上进行优化,但移动次数未改变,复杂度仍然是O(N^2)

2-路插入排序

表插入排序



希尔SHELL排序

int shell_sort(int *ary,int size)
{
	int op=0;
	int temp;
	int incs[5]={1,5,19,41,109};
	for(int t=4;t>=0;t--)
	{
		int h=incs[t];
		for(int i=h;i<size;i++)
		{
			temp=ary[i];
			for(j=i;j>0&&temp<ary[j-h];j=j-h)
			{
				ary[j]=ary[j-h];
				op++;
			}
			ary[j]=temp;
			op++;
		}
	}
	return op;
]

SHELL排序是不稳定的排序,

通常情况下性能优于插入排序



快速排序

// 从左遍历版本,以结尾为主元,返回的轴位置为i+1,因为这里i是从-1开始移动的
int partition(int *ary,int left,int right)
{
	int i=left-1;
	int j=left;
	int pivot=ary[right];
	for(;j<right;j++)
	{
		if(ary[j]<=pivot)
		{
			i++;
			swap(i,j);
		}
	}
	swap(i+1,right);
	return i+1;
}
void quick_sort(int *ary,int left,int right)
{
	if(left<right)
	{
		int index=partition(ary,left,right);
		quick_sort(ary,left,index-1);
		quick_sort(ary,index+1,right);
	}
}

// HOARE版本,可以任意位置为主元,这里返回的轴位置是j+1,最后j在i前,可能j+1=i也可能j+2=i
void quicksort(int *ary,int left,int right)
{
     if(left<right){
     int i=left,j=right;
     int pivot=ary[left];
     while(i<=j){
                 while(ary[j]>pivot)j--;
                 while(ary[i]<pivot)i++;
               
                if(i<=j){
                        int temp=ary[i];
                        ary[i]=ary[j];
                        ary[j]=temp;
                        i++;j--;
                        }
     }
     if(left<j)
     quicksort(ary,left,j);
     if(i<right)
     quicksort(ary,i,right);
     }
}
// 替换版本,非交换版本,这里返回的轴位置是i
void quicksort(int *ary,int left,int right)
{
	if(left<right){
	int i=left;
	int j=right;
	int key=ary[i];
	while(i<j)
	{
		while(i<j&&ary[j]>=key)j--;
		ary[i]=ary[j];
		while(i<j&&ary[i]<=key)i++;
		ary[j]=ary[i];
	}
	ary[i]=key;
	if(left<i-1)
		quicksort(ary,left,i-1);// 这里可以用i-1,因为ary[i]已经在有序的位置上了
	if(i+1<right)
		quicksort(ary,i+1,right);
	}

}


快速排序基于分治法的排序算法,

时间复杂度为O(n*logn),基本有序的情况下(也就是最坏情况下)是O(n^2),但通常情况下性能是最佳的,常数因子较小

不稳定排序

为了避免基本有序的情况,通过引入随机化技术,使得在等概率的情况下获得期望性能,所作的操作只需要随机一个位置与每次作pivot的位置元素交换;

另外一种改进随机是三数取中划分,随机三个位置,然后取中位数为pivot





堆排序

void swap(int *a,int *b)
{
     int temp=*a;
     *a=*b;
     *b=temp;
 }
// 调整堆  siftdown
void heapify(int *ary,int i,int size)
{
	int left,right,max;
	while(i<size)
	{ 
        left=2*i+1;
        right=left+1;
		max=i;
		if(left<size&&ary[i]<ary[left])
			i=left;
		if(right<size&&ary[i]<ary[right])
			i=right;
		if(i!=max)
			swap(&ary[i],&ary[max]);
		else
			break;
		
	}
}
// 建堆 siftup
void buildHeap(int *ary,int size)
{
	for(int i=(size-2)/2;i>=0;i--)// 利用siftdown实现siftup,FOR 每个结点的父母结点 TO 根结点
	{
		heapify(ary,i,size);
	}
}

// 堆排序过程 ,先建堆,然后交换顶点与末尾,交换一次再调整一次,当前代码是大根堆,所以最后顺序是从小到大
 void heapSort(int *ary,int size)
{
	buildHeap(ary,size);
	for(int i=size-1;i>0;i--)
	{
		swap(&ary[0],&ary[i]);
		heapify(ary,0,i);
	}
}

堆排序的前身:树形选择排序,又称锦标赛排序(类似比赛分组树结构)

堆的定义是:一个完全二叉树,每个结点大于等于其两个孩子结点(大根堆)

堆排序的过程,首先建堆,然后将堆顶与末尾元素交换,紧接着调整堆使其继续保持定义,循环交换n次,形成最终有序排列

时间复杂度O(n*logn),其中调整堆的复杂度为O(logn),排序O(n),不稳定排序

堆排序的应用:

优先队列




归并排序

// 归并排序,不过new和delete写在子函数中,效率可能不高,可以直接在排序前申请O(n)的空间
void mergeArray(int *ary,int left,int mid,int right,int *temp)
{
	int i=left,j=mid+1,k=0;
	int temp[]=new int[right-left+1];
	while(i<mid&&j<right)
	{
		if(ary[i]<ary[j])
			temp[k++]=ary[i++];
		else
			temp[k++]=ary[j++];
	}
	while(i<=mid)
		temp[k++]=ary[i++];
	while(j<=right)
		temp[k++]=ary[j++];
	// 复制回原数组
	for(int i=0;i<k;i++)
	{
		ary[left+i]=temp[i];
	}
	// 考虑是否删除额外空间
	delete[] temp;
}
void mergeSort(int *ary,int left,int right,int *temp)
{
	int mid=((right-left)>>1)+left;
	if(left<right)
	{	
		mergeSort(ary,left,mid);//注意这里不是 mid-1,之前写错了
		mergeSort(ary,mid+1,right);
		mergeArray(ary,left,mid,right,temp);
	}
}

归并排序的时间复杂度最好最坏下均是O(n*logn),但空间复杂度是O(n)

是稳定排序

利用递归树可以清晰地分析归并排序的过程


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值