经典排序总结

冒泡排序

#include <stdio.h>

void BubbleSort(int a[],int n)
{
	int change=1;
	for (int i=n-1;i>0&&change==1;i--)
	{
		change=0;
		for (int j=0;j<i;j++)
		{
			if (a[j]>a[j+1])
			{
				int tmp;
				tmp=a[j];
				a[j]=a[j+1];
				a[j+1]=tmp;
				change=1;
			}
		}
	}

}
最坏的情况为逆序时,比较1+2+3.....+n-1次,时间复杂度为O(n^2);增添标志位进行优化,避免不必要的操作!

选择排序

基本操作为选择最小的交换

void SelctionSort(int a[],int n)
{
	for (int i=0;i<n;i++)
	{
		int min=i;
		for (int j=i+1;j<n;j++)
		{
			if (a[min]>a[j])
			{
				min=j;
			}
		}
		int tmp;
		tmp=a[i];
		a[i]=a[min];
		a[min]=tmp;
	}
}

每次都从j=i+1开始选择出最小数的下标,然后将最小数与i交换。

1最大的特点是交换次数相当少

2无论数据好坏,比较次数都是一样的。为1+2+3....+n-1;时间复杂度仍然为O(n^2)

尽管时间复杂度与冒泡排序相当,但简单排序还是优与冒泡排序

直接插入排序:PS:总把这个选择排序搞混!!

基本操作为将一个记录插入到已经排好的有序表中,将后续元素插入到合适位置

void InserSort(int a[],int n)
{
	for (int i=2;i<n;i++)
	{
		if (a[i]<a[i-1])
		{
			a[0]=a[i];
			for (int j=i-1;a[j]>a[i];j--)//跳出循环时,j指向正确位置的前一个位置;
			{
				a[j+1]=a[j];
			}
		a[j+1]=a[i];
		}
	}
}
算法要求一个哨岗位,a[0]作为哨岗,使寻找插入位置的循环可以正确跳出

对于数组而言 若数组a[0]也是要加入排序的数据,则也要开辟个存储空间,保存待插入元素,

a[j]>tmp&&j>=0

void InsertSort(int a[],int n)
{
	for (int i=1;i<n;i++)
	{
		if (a[i]<a[i-1])
		{
			int tmp=a[i];
			for (int j=i-1;a[j]>tmp&&j>=0;j--)//跳出循环时,j指向正确位置的前一个位置;
			{
				a[j+1]=a[j];
			}
		a[j+1]=tmp;
		}
	}
}

时间复杂度仍然为O(O^2)同样的的时间复杂度,直接插入发比简单选择和冒泡排序的性能要好。

1.在记录本身就是基本有序的时候,我们只需要少量的插入操作,就可以完成整个记录的集的排序工作,此时插入效率很高

2.在记录比较少时,直接插入法的优势也比较明显.

为了利用直接插入法的这两个优势 希尔发明了希尔排序

希尔排序:为了利用插入排序的当元素基本有序时候的优势

采用跳跃分割的策略,将相距摸个增量的记录组成一个字序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序.

void ShellSort(int a[],int n)
{
	int add=n;
	do 
	{
		add=add/3+1;
		for (int i=add;i<n;i++)
		{
			if (a[i]<a[i-add])
			{
				int tmp=a[i];
				for (int j=i-add;j>=0&&tmp<a[j];j-=add)
				{
					a[j+add]=a[j];
				}
				a[j+add]=tmp;
			}
		}
	} while (add>1);
}

do while(add>1)这个循环很关键,控制了add增量等于一时就是最后一次循环,对比直接插入法发现,希尔算法就是分别对增量子集进行插入排序,并逐渐缩小增量,最后一次排序时是用直接插入法对基本有序的序列进行排序,

其时间复杂度 突破了前人的O(n^2), 为:O(n^(3/2));

堆排序

堆排序是对简单选择排序的一种改进,在进行选择排序时,每次都要从后续序列中选出最小的元素来插入,显然,这样的比较重复了很多次,堆排序就改正了这样一个缺点。

1.如何由一个无序序列构建一个堆。

2如果在输出堆顶元素后,调整剩余元素成为一个新的堆.

所谓的将待排序列构建成为一个大顶堆,其实就是从下往上,从右往左,将每个非终端结点当做根节点,将其调整为大顶堆。

void HeapAdjust(int a[],int s,int n)
{
	int tmp=a[s];
	for (int j=2*s;j<n;j*=2)
	{
		if (j<n&&a[j]<a[j+1])
		{
			++j;
		}
		if (tmp>=a[j])
		{
			break;
		}
		else
		{
			a[s]=a[j];//
			s=j;//很精妙的两句话!! 注意理解
		}
	}
	a[s]=tmp;
}
void HeapSort(int a[],int n)
{
	for (int i=n/2;i>0;i--)
	{
		HeapAdjust(a,i,n);
	}
	for (i=n;i>0;i--)
	{
		int tmp;
		tmp=a[1];
		a[1]=a[i];
		a[i]=tmp;
		HeapAdjust(a,1,i-1);
	}
}

数组中a[0]不能存储元素,因为这样会打破父结点和左右孩子结点的2i和2i+1的关系

自下而上调整大顶堆:循环n/2到1实现了非根结点自下而上,从右到左的调整,遍历每个结点时,比较左右孩子的大小,若右孩子大,则j++;若此时根结点已经是最大,则退出循环,若不是,则将较大的赋值给跟结点,s=j,根结点的位置发生变化。

关于时间复杂度:无论是最好,最坏和平均时间复杂度均为O(nlogn)。这在性能上远远超过了冒泡,简单选择,直接插入的时间复杂度了.充分利用了完全二叉树的深度是log2n+1的性质。

第i次取堆顶记录重建需要O(logi)的时间,(因为完全二叉树的某个结点到根结点的距离为log2i+1,)需要取n-1次,每次取的记录与最后一个元素进行交换 时间复杂度均为O(nlogn)!又一次大突破!!

适合数据量大的排序:由于初始建堆所需的次数比较多,因此,它不适合排序序列个数较少的情况。


归并排序

void Merge(int SR[],int TR[],int i,int m,int n)
{
	int j,k,l;
	for (j=m+1,k=i;i<=m&&j<=n;k++)//每次将两个区域对比,择小插入,循环结束后至少一个区域已经全部插入,后续不需要比较直接全部插入,(后面两个循环)。
	{
		if (SR[i]<SR[j])
		{
			TR[k]=SR[i++];
		}
		else
		{
			TR[k]=SR[j++];
		}
	}
	if (i<=m)//若i....m还有元素没有插入
	{
		for (l=0;l<=m-i;l++)
		{
			TR[k+l]=SR[i+l];
		}

	}
	if (j<=n)
	{
		for (l=0;l<=n-j;l++)//一定要'='
		{
			TR[k+l]=SR[j+l];
		}
	}
	
}
void Msort(int SR[],int TR1[],int s,int t)//将SR【s...t】归并排序为TR[s...t]
{
	int m;
	int TR2[1000];
	if (s==t)
	{
		TR1[s]=SR[t];
	}
	else
	{
		m=(s+t)/2;
		Msort(SR,TR2,s,m);
		Msort(SR,TR2,m+1,t);
		Merge(TR2,TR1,s,m,t);//将TR2归并为TR1
	}
}

Merge为归并函数,将两个各自有序的数列归并成一个有序序列,Msort为递归主题函数,将SR【】归并排序为TR【】!!!

蛋疼问题:循环中改变了循环条件,居然没看出来

Merge时间复杂度为o(n),由完全二叉树的深度可知,整个归并排序需要进行logn2次,所以总的时间复杂度为o(nlogn);

排序中存在比较,是一种稳定的排序方法,

空间复杂度:由于归并排序在归并过程中需要与原记录序列同样数量的存储空间存放归并排序结果,以及归并时深度为log2n的栈空间,因此空间复杂度为O(n+logn)

也就是说归并排序是一种比较占用内存,但却效率高且稳定的算法.


快速排序:

void swap(int &a,int &b)//数组可以不用取地址符,单个整形数一定要用
{
	int tmp;
	tmp=a;
	a=b;
	b=tmp;
}
int Partition(int a[],int low,int high)
{
	int pivotkey;
	pivotkey=a[low];
	while (low<high)
	{
		while (low<high&&a[high]>=pivotkey)
		{
			high--;
		}
		swap(a[low],a[high]);
		while (low<high&&a[low]<=pivotkey)
		{
			low++;
		}
		swap(a[low],a[high]);
	}
	return low;
}   
void Qsort(int a[],int low,int high)
{
	int pivot;
	if(low<high)
	{
		pivot=Partition(a,low,high);
		Qsort(a,low,pivot-1);
		Qsort(a,pivot+1,high);
	}
}
在最优的情况下,快速排序算法的诗句复杂度为O(nlogn).

在最坏的情况下,待排序列为正序或者逆序,每次划分只得到一个比上一次少一个记录的子序列,注意另一个为空,需要n-1次分割,且第i次划分需要经过n-i次关键字的比较才能找到第i个记录,也就是枢轴的位置,比较次数为1+...+..n-1次,时间复杂度为O(n^2);

可惜的是,由于关键字的比较和交换式跳跃进行的,因此,快速排序是一种不稳定的排序方法.

快速排序还有优化的方案 ,以后再敲......


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值