数据结构与算法分析(五)——常用排序

(一)插入排序

算法时间复杂度:O(N2)
算法实现步骤(设需要排序的数组为a[]):
1.构造一个新数组copy[],内容为空;
2.从前向后取出a[]中的元素,取出的元素记为a[i];
3.每取出一次元素,将a[i]与copy[]中的元素(i之前的位置,这里新开了一个变量index=i用来在copy[]中移位)进行比较,从copy[index-1]开始比较,如果小于的话则copy[index-1]向后移一位,空出index-1的位置,然后index自减,下一次的比较则是a[i]和copy[i-2],而copy[i-1]留空,copy[i]存入原先的copy[i-1](max值);
4.直到a[i]大于copy[]中的某个元素,此时将a[i]插入上一次比较留下的空位copy[index];
5.对所有a[]中的数遍历一遍即可在copy[]中完成排序;

/**
 *@name Sort_insert_array:使用插入排序对数组进行排序
 *@param1 a:需要排序的数组名
**/
void Sort::Sort_insert_array(int* a,int N)
{
	int copy[100000]={0};
	int temp;
	int index;
	copy[0]=a[0];
	for (int i=1;i<N;i++)
	{
		temp=a[i];
		index=i;
		//默认copy在i之前的序列都已排序完毕
		//将a[i]从copy的i-1开始向前比较,如果小于,则copy在该位置后移,留出空位
		while((index>0)&&(temp<copy[index-1]))
		{
			//必须先移位再自减
			copy[index]=copy[index-1];
			index--;
		}
		copy[index]=temp;
	}
	for (int i=0;i<N;i++)
	{
		a[i]=copy[i];
	}
}

注:
1.必须先移位再自减,因为自减是针对下一次操作的。

(二)归并排序

算法时间复杂度:O(Nlog(N))
算法简介:合并两个已排序的表
算法实现步骤:
1.利用递归实现分治,将前半部分数据和后半部分数据各自进行归并排序;
2.然后将排序完成的两部分数据进行合并;
3.合并的过程:
(1)用一个指针指向前半部分数据头部,另一个指针指向后半部分数据头部;
(2)对两指针分别指向的数据进行比较,小的数据放入存放排序结果的数组(这里需要一个index来记录存放数据的位置,即已排序的长度),对应的指针自加;
(3)如果一指针已将某一部分的数据全部遍历完全,则直接将另一部分剩下的数据全部导入新数组的剩余位置;

/**
 *@name merge:对数组的[start]-[end]经过排序后合并
 *@param1 a:传入的需要排序的数组
 *@param2 copy:中间量的数组
 *@param3 start:需要合并的起始位置
 *@param4 midden:需要合并的中间位置
 *@param5 end:需要合并的末端位置
**/
void merge_array(int* a,int *copy,int start,int midden,int end)
{
	int ptr_i=start;
	int ptr_j=midden+1;
	int ptr_k=start;
	int len=end-start;
	//首先先从start和midden+1开始比较,直到满足某一序列已全部进入copy
	while((ptr_i<=midden)&&(ptr_j<=end))
	{
		if(a[ptr_i]<a[ptr_j])
			copy[ptr_k++]=a[ptr_i++];
		else
			copy[ptr_k++]=a[ptr_j++];
	}

	//将start-midden序列中剩下的元素全部丢入copy
	while(ptr_i<=midden)
	{
		copy[ptr_k++]=a[ptr_i++];
	}

	//将midden+1-end序列中剩下的元素全部丢入copy
	while(ptr_j<=end)
	{
		copy[ptr_k++]=a[ptr_j++];
	}

	//将copy中的元素反赋值给a
	for(int i=start;i<=end;i++)
	{
		a[i]=copy[i];
	}
}

/**
 *@name Sort_mergesort:归并排序
 *@param1 a:传入的需要排序的数组
 *@param2 copy:作为中间量的数组
 *@param3 start:需要合并的起始位置
 *@param4 midden:需要合并的中间位置
 *@param5 end:需要合并的末端位置
**/
void Sort::Sort_mergesort_array(int* a,int *copy,int start,int midden,int end)
{
	int len=end-start;
	//只有end>=start时,才需要分治
	if(len>=1)
	{
		//将start-end的序列分成两段,然后合并
		Sort_mergesort_array(a,copy,start,(midden+start)/2,midden);
		Sort_mergesort_array(a,copy,midden+1,(midden+1+end)/2,end);
		merge_array(a,copy,start,midden,end);
	}
	else
		return;
}

注:
1.个人认为,midden不一定要纯中间,稍微偏一点也无伤大雅

(三)快速排序

算法时间复杂度:最坏O(N2),平均O(Nlog(N))
算法简介:
分治:选取中间枢纽,比其小的构成一个序列,比其大的也构成一个序列,利用递归可完成各自的排序
合并:直接按小序列-中间值-大序列的顺序构成排序完成的新数组
算法实现步骤:
1.选取中间枢纽,将其与数组的首个元素交换;
2.通过与中间枢纽比较大小,将数组元素分成两个序列,具体步骤:
(1)选取中间枢纽,将其换到数组的首元素位置;
(2)新建两个指针,一个从头开始扫(用i表示),一个从尾部开始扫(用j表示);
(3)j从尾部开始扫,直到扫到小于中间枢纽的元素时停下,i从头部开始扫,直到扫到大于中间枢纽的停下;
(4)如果i<j,证明还没有扫过头,此时交换i和j指向的元素;
(5)直到i>=j时,此时将数组首元素与j指向的元素交换即可得到小序列、中间枢纽、大序列的组合(此时j指向的是小于中间枢纽的元素,故和中间枢纽交换即可得到上述组合);
3.利用递归,对小序列和大序列分别再使用快速排序;

/**
 *@name Sort_quicksort_array:快速排序(数组)
 *@name a:需要排序的数组
 *@start:起始位置
 *@end:终止位置
**/
void Sort::Sort_quicksort_array(int*a,int start,int end)
{
	if(start>end)
	{
		return;
	}

	int i=start;
	int j=end;
	Swap(&a[start],&a[(start+end)/2]);
	int cmp=a[start];

	//取基准元 并将序列分成左右两个序列
	//左边小于基准元,右边大于基准元
	while(i<j)
	{
		//找到右边开始的第一个小于cmp的元素 如果加上= 就会很慢。。。
		while((a[j]>=cmp)&&(i<j))
		{
			j--;
		}
		//找到左边第一个大于cmp的元素
		while((a[i]<=cmp)&&(i<j))
		{
			i++;
		}
		
		if(i<j)
		{
			//交换 小于cmp的元素从右边序列到左边序列 大于cmp的元素从左边序列到右边序列
			Swap(&a[i],&a[j]);
		}
	}
	//将基准元放到中间
	Swap(&a[j],&a[start]);
	
	//对左边序列进行快排
	Sort_quicksort_array(a,start,j-1);
	//对右边序列进行快排
	Sort_quicksort_array(a,j+1,end);
}

注:
1.快排确实比归并快(在release模式下)
2.起始位置一定从start开始,否则j只能到start+2的位置,会存在排序错误
3.交换完了不需要自加和自减,针对两者中间仅有一个元素的情况下会出错

(四)堆排序

算法时间复杂度:O(Nlog(N))
算法简介:利用数组建立一个二叉堆(大根堆),然后利用最大元素永远出现在root的特性,不断作删除堆(下滤,放弃最大元素,使其沉底),重建堆的操作(对剩下的元素重建堆),最终堆中元素仅剩1个,数组的结果即排序结果。
算法实现步骤:
1.从最后一个节点开始,建立(max)堆,具体步骤:
(1)确定开始的节点位置,将其作为分析节点,找到其两个子节点中较大的那个;
(2)如果开始节点的值较小的话与子节点中较大的那个交换,此时分析节点变为交换到较小值的那个子节点(分析节点对应的元素是较小值);
(3)一直顺着树向下走,直到分析节点的子节点的值都小或者分析节点没有子节点为止;
2.删除root,堆中节点数-1,再重建堆,具体步骤:
(1)将数组的最后一个元素与首元素交换位置;
(2)以数组首元素作为分析节点,重建大小-1的堆(目的是将置换而来的元素沉下去);
(3)直到堆的大小为1时完成排序;

/**
 *@name Build_Heap:构建大根堆(顺着一个节点,将小节点往下放)
 *@param1 a:传入需要建立堆的数组
 *@param2 i:输入需要操作的节点
 *@param3 len:数组的长度,防止越界,越界意味着没有子节点
**/
void Build_Heap(int* a,int i,int len)
{
	int Child;
	int temp;
	//只要存在儿子就继续向下比较
	while((2*i)<=len)
	{
		Child=2*i;
		//取左右儿子节点大的那个
		if(((Child+1)<=len)&&(a[Child]<a[Child+1]))
			Child++;
		//如果儿子比父亲大的话就交换,同时索引节点变为儿子
		//否则的话就证明顺着该节点的次序是对的
		if(a[i]<a[Child]) 
		{
			Swap(&(a[Child]),&(a[i]));
			i=Child;
		}
		else
			break;
	}
}

/**
 *@name Sort_heapsort_array:堆排序
  *@param1 a:传入需要堆排序的数组
 *@param2 N:数组的大小
**/
void Sort::Sort_heapsort_array(int* a,int N)
{
	//对每一个节点进行下滤(小的节点往下走)
	//得到一个大根堆
	for (int i=N;i>=0;i--)
	{
		Build_Heap(a,i,N);
	}
	//从最后一个位置开始,与根交换后再对前半部分重新建立堆
	//效果即成功排序
	for (int i=N;i>0;i--)
	{
		Swap(&(a[0]),&(a[i]));
		Build_Heap(a,0,i-1);
	}
}

注:
1.如果从0开始建立二叉堆的话,子节点对应的索引应为(2×i+1)和(2×i+2)

用链表做排序真是坑。。。

(五)基数排序

算法时间复杂度:O(k(N+r)),其中k最高位的位数,N是排序的个数,r是进制对应的数
算法简介:按低位优先级或者高位优先级进行排序
算法实现步骤:
1.根据进制r新建一个二维数组,一维代表十进制对应的0-9,另一位用于存放在比较过程中相应的数;
2.取第一个数的最低位,根据该位的值将该数放入二维数组对应的位值,找到存放数据的那一维中第一个零的位置,放入该数;
3.对数组中的所有数执行上述操作;
4.此时根据二维数组中的数据存储形式,从0-9取出数据重新放入原数组;
5.对二维数组清零;
6.然后再对所有数的其他位作相同操作(由低到高,代表最高位优先级最高);

/**
 *@name Radix_sort:基数排序
 *@param1 a:需要排序的数组
**/
void Radix_sort(int* a)
{
	int a_Radix[10][20]={0};
	int temp;
	int count=0;
	for (int i=0;i<3;i++)
	{
		//放入桶
		for (int j=0;j<5;j++)
		{
			//取出第i位 从后向前
			temp=(a[j]/(int)pow(10.0,i))%10;
			for (int k=0;k<20;k++)
			{
				//每一位可以最多挂20个在该位相同的数
				if(a_Radix[temp][k]==0)
				{
					//检测到该位没有挂数时就挂上,否则自加到下一个位置
					a_Radix[temp][k]=a[j];
					break;
				}
			}
		}
		//从桶中取出
		count=0;
		//遍历桶 对某一位从0-9检索排序
		for (int j=0;j<10;j++)
		{
			//将所有在该位相同的数从桶中取出,放回数组a中
			for (int k=0;k<20;k++)
			{
				if(a_Radix[j][k]==0)
					break;
				a[count]=a_Radix[j][k];
				count++;
			}
		}
		//将桶清零,方便下一次放入
		for (int j=0;j<10;j++)
		{
			for (int k=0;k<20;k++)
				a_Radix[j][k]=0;
		}
	}
}

发布了99 篇原创文章 · 获赞 29 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44586750/article/details/103098742
今日推荐