数据结构之常见排序算法

#include "stdafx.h"
#include <memory>


#define MAXSIZE 10
typedef struct  
{
	int r[MAXSIZE+1];//用于存储要排序数组,r[0]用作哨兵或临时变量
	int nlength;//用于记录顺序表长度
}SqList;

//交换L中i,j位置的数据
void swap(SqList *L, int i, int j)
{
	int temp = L->r[i];
	L->r[i] = L->r[j];
	L->r[j] = temp;
}

/************************************************************************/
/* 一、交换排序                                                         */
/************************************************************************/
//1.1冒泡排序      稳定性:稳定       时间复杂度:O(n^2)       空间复杂度:O(1)
//基本思路:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止
//对顺序表L做冒泡排序
void BubbleSort(SqList *L)
{
	int i,j;
	bool bchange;//判断是否发生交换
	for (i=1;i<L->nlength;i++)
	{
		bchange=false;
		for (j=L->nlength-1;j>=i;j--)
		{
			if (L->r[j]>L->r[j+1])
			{
               swap(L,j,j+1);
			   bchange=true;
			}
			   
		}
		if (!bchange)//如果没有发生交换说明已经是排好序了
		   return;
	}
}


//1.2快速排序      稳定性:不稳定      时间复杂度:平均O(nlogn)      有序时最差O(n^2)       空间复杂度:O(logn)
//基本思路:通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小
//则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的

//交换顺序表L中子表的记录,使枢轴到位,并返回其所在位置,此时它前面的值均不大于它,后面的值均不小于它
int Partition(SqList *L, int low, int high)
{
	int pivotkey = L->r[low];//用子表的第一个记录作枢轴记录
	while(low<high)//从表的两端交替向中间扫描
	{
		while(low<high && L->r[high]>=pivotkey)
			high--;
		swap(L,low,high);//将比枢轴记录小的记录交换到低端
		while(low<high && L->r[low]<=pivotkey)
			low++;
		swap(L,low,high);//将比枢轴记录大的记录交换到高端
	}
	return low;//返回枢轴位置
}

//对顺序表L中的子序列L->r[low.....high]做快速排序
void QSort(SqList *L, int low, int high)
{
	int pivot;
	if (low<high)
	{
		pivot = Partition(L,low,high);//将L->r[low.....high]一分为二,算出枢轴值pivot
		QSort(L,low,pivot-1);//对低子表做递归排序
		QSort(L,pivot+1,high);//对高子表做递归排序
	}
	
}

//对顺序表L做快速排序
void QuickSort(SqList *L)
{
	QSort(L,1,L->nlength);
}

//快排的优化策略
//1.优化选择枢轴:三数取中,九数取中
//2.优化不必要的交换:修改Partition()将替换改为交换   high--,r[low]=r[high];  low++,r[high]=r[low];
//3.优化小数组时的排序方案:修改QSort()当数组个数不大于某个常数(7?50?)时用直接插入排序
//4.优化递归操作:每次分区后先处理较短的部分





/************************************************************************/
/* 二、选择排序                                                         */
/************************************************************************/
//2.1简单选择排序        稳定性:不稳定        时间复杂度:O(n^2)     相比于冒泡排序性能更优,因为交换次数少      空间复杂度:O(1)
//基本思路:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换之
//对顺序表L做简单选择排序
void SelectSort(SqList *L)
{
	int i,j,min;
	for (i=1;i<L->nlength;i++)
	{
		min=i;//将当前下标定义为最小值下标
		for (j=i+1;j<=L->nlength;j++)//循环比较n-i次
		{
			if (L->r[min]>L->r[j])
			   min=j;//找出n-i+1个记录中的最小值
		}
		if (min!=i)//若min!=i,则交换
		   swap(L,i,min);
	}
}


//2.2堆排序              稳定性:不稳定        时间复杂度:O(nlogn)        空间复杂度:O(1)
//基本思路:利用堆进行排序,重复构造大顶锥,将根结点与末尾元素交换,再将剩余的元素重新构造大顶锥

//本函数调整L->r[s]的关键字,使L->r[s.....m]成为一个大顶锥
void HeapAdjust(SqList *L, int s, int m)
{
	int temp,j;
	temp=L->r[s];   //s为子根结点
	for (j=s*2;j<=m;j*=2)//沿关键字较大的孩子结点向下筛选
	{
		if (j<m && L->r[j]<L->r[j+1])
		   j++;//j为关键字中较大的记录的下标
		if (temp<L->r[j])
		{
			L->r[s]=L->r[j];//更新L->r[s]为最大值
			s=j;
		}
	}
	L->r[s]=temp;//交换
}

//对顺序表L做堆排序
void HeapSort(SqList *L)
{
	int i;
	for (i=L->nlength/2;i>0;i--)//把L中的r构造成一个大顶锥   此时的i都为子根结点
	{
		HeapAdjust(L,i,L->nlength);
	}
	for (i=L->nlength;i>1;i--)
	{
		swap(L,1,i);//将大顶锥的根结点与末尾元素交换
		HeapAdjust(L,1,i-1);//将剩余的结点重新构造成一个大顶锥
	}
}




/************************************************************************/
/* 三、插入排序                                                         */
/************************************************************************/
//3.1直接插入排序         稳定性:稳定       时间复杂度:O(N^2)       是简单排序中性能最好的        空间复杂度:O(1)
//基本思路:将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表
//对顺序表L做直接插入排序
void InsertSort(SqList *L)
{
	int i,j;
	for (i=2;i<=L->nlength;i++)
	{
		if (L->r[i]<L->r[i-1])//如果当前元素小于之前的元素,则需要把它插入到前面
		{
			L->r[0]=L->r[i];//设置哨兵
			for (j=i-1;L->r[j]>L->r[0];j--)//与前面的元素分别比较,将大的记录往后移
			{
				L->r[j+1]=L->r[j];//将记录后移
			}
			L->r[j+1]=L->r[0];//将元素插入到正确的位置
		}
	}
}

 
//3.2希尔排序              稳定性:不稳定(跳跃式的移动)       时间复杂度:O(N^(3/2))      空间复杂度:O(1)
//基本思路:将原本具有大量记录的序列,通过相隔某个增量的记录组成子序列,对子序列进行直接插入排序
//从而使整个序列基本有序,最后对整个序列做直接插入排序
//对顺序表L做希尔排序
void ShellSort(SqList *L)
{
	int i,j,increment;
	increment=L->nlength;
	do 
	{
		increment=increment/3+1;//增量序列
		for (i=increment+1;i<=L->nlength;i++)
		{
			if (L->r[i]<L->r[i-increment])//如果当前元素小于之前的元素,则需要把它插入到前面
			{
				L->r[0]=L->r[i];//设置哨兵
				for (j=i-increment;j>0&&L->r[j]>L->r[0];j-=increment)//与前面的元素分别比较,将大的记录往后移
				{
					L->r[j+increment]=L->r[j];//将记录后移
				}
				L->r[j+increment]=L->r[0];//将元素插入到正确的位置
			}
		}
	} while (increment>1);
}




/************************************************************************/
/* 四、归并排序         稳定性:稳定        时间复杂度:O(nlogn)      空间复杂度O(n)                                             */
/************************************************************************/
//基本思路:两两合并,倒置的完全二叉树

//将有序的SR[i...m]和SR[m+1...n]归并为有序的TR[i...n]
void Merge(int SR[], int TR[], int i, int m, int n)
{
	int j,k,l;
	for (j=m+1,k=i;j<=n&&i<=m;k++)//将SR中记录由小到大归并入TR
	{
		if (SR[i]<SR[j])
		   TR[k]=SR[i++];
		else
		   TR[k]=SR[j++];
	}
	if (i<=m)
	{
		for (l=0;l<=m-i;l++)
		   TR[k+l]=SR[i+l];//将剩余的SR[i...m]复制到TR
	}
	if (j<=n)
	{
		for (l=0;l<=n-j;l++)
			TR[k+l]=SR[j+l];//将剩余的SR[j...n]复制到TR
	}
}

//将SR[]中相邻长度为s的子序列两两归并到TR[]
void MergePass(int SR[], int TR[], int s, int n)
{
	int i=1;
	int j;
	while (i<=n-s*2+1)
	{
		Merge(SR,TR,i,i+s-1,i+2*s-1);//两两合并
		i=i+s*2;
	}
	if (i<n-s+1)//归并<最后>两个序列(只在最后一次出现)
	   Merge(SR,TR,i,i+s-1,n);
	else//若最后只剩下单个子序列
		for (j=i;j<=n;j++)
			TR[j]=SR[j];
}

//对顺序表L做归并排序
void MergeSort(SqList *L)
{
	int k=1;
	int *TR=(int*)malloc(sizeof(int)*L->nlength);//申请额外空间
	while (k<L->nlength)
	{
		MergePass(TR,L->r,k,L->nlength);
		k=k*2;//子序列长度加倍
		MergePass(L->r,TR,k,L->nlength);
		k=k*2;//子序列长度加倍
	}
}



/************************************************************************/
/* 五、总结                                                             */
/************************************************************************/
//5.1时间复杂度来说:
//(1)平方阶(O(n2))排序:各类简单排序:直接插入、简单选择和冒泡排序;
//(2)线性对数阶(O(nlog2n))排序:快速排序、堆排序和归并排序;
//(3)O(n^(1+§)))排序(§是介于0和1之间的常数):希尔排序
//(4)线性阶(O(n))排序:基数排序,此外还有桶、箱排序。
//说明:
//当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n)
//而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2)
//原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大

//5.2稳定性来说:
//排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的
//                 若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。 
//稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,
//              第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,
//              逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,
//              可以避免多余的比较;
//稳定的排序算法:冒泡排序、直接插入排序、归并排序和基数排序
//不稳定的排序算法:简单选择排序、快速排序、希尔排序、堆排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值