【算法与实现】8大排序详解

8大排序详解

1.冒泡法排序

算法描述:冒泡排序是一种交换排序,主要思想就是比较相邻元素,然后将较小的元素交换到前面,较大的元素交换到后面。

 

稳定性描述:由于交换是逐个进行的,且相等的元素不进行交换,所以冒泡法排序是一种稳定排序算法。


算法实现:

void bubbleSort(listNode L[], int length)
{
	for(int i=0;i<length;i++)
	{
		for(int j=0;j<length-i-1;j++)
		{
			if(L[j]>L[j+1])
				swap(&L[j],&L[j+1]);
		}
	}
}


 

2.直接选择排序

算法描述:直接选择排序是一种选择排序,主要思想是在给定序列中选择最小的元素,与序列中第1个元素进行交换,然后在余下的元素中选择总序列中第2小的,与序列中第2个元素进行交换,以此类推,不断选择出第i小的元素与总序列第i位元素交换,直到选到最后一个最大元素。

 

稳定性描述:这种选择的方法会导致稳定性的问题,例如:序列{4,4,2},在第一次选择交换后序列变为{2,4,4},原序列中第一个4就移动到了原序列中第二个4的后面,所以直接选择排序是一种不稳定排序算法。


算法实现:

void selectSort(listNode L[], int length)
{
	for(int i=0;i<length;i++)
	{
		listNode min=L[i];
		int index=i;
		for(int j=i+1;j<length;j++)		//选择出当前最小值并用index记录
		{
			if(L[j]<min)
			{
				min=L[j];
				index=j;
			}
		}
		swap(&L[i],&L[index]);			//与相应位置元素交换
	}
}


 

3.直接插入排序

算法描述:直接插入排序是一种插入排序,主要思想是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,如果不比它小则直接插入在其后面,否则一直往前找直到找到它该插入的位置。

 

稳定性描述:如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以直接插入排序是一种稳定的排序算法。

 

算法实现:

void insertSort(listNode L[], int length)
{
	int i,j;
	for(i=0;i<length;i++)
	{
		int index=0;
		for(j=i-1;j>=0;j--)		//找到插入位置记入index
		{
			if(L[i]>=L[j])
			{
				index=j+1;
				break;
			}
			index=j;
		}
		for(j=i;j>index;j--)		//插入并移动元素
		{
			swap(&L[j],&L[j-1]);
		}
	}
}


 

4.快速排序

算法描述:快速排序是一种交换排序,可以看做冒泡法排序的升级版,快速排序中会选择一个中枢元素a[center_index],一般取为当前子序列第1个元素(取最后一位也是可以的)。将j下标置于第一个元素的位置,i下标置于第二个元素的位置,然后将i下标向右移动,当a[i]< a[center_index]时交换a[i]和a[j+1],并将j下标右移一位,如此操作至i遍历完当前子序列。最后,交换a[j]和a[center_index],完成一趟快速排序。简单来说就是找出第一位元素在序列中的正确位置并放到此正确位置上,同时将小于它的元素置于它之前,大于等于它的元素置于它之后,然后将交换后的中枢元素前后两个子序列进行递归即可完成整个序列的排序。

 

稳定性描述:在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为{5,3,3,4,3,8,9,10,11},现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法。


算法实现:

/*将中枢元素位置找出并将大于等于它的元素置于它之后,小于它的元素置于它之前*/
int partition(listNode L[], int p, int r)
{
	int i,j;
	j=p;
	for(i=p+1;i<=r;i++)
	{
		if(L[i]<L[p])
		{
			swap(&L[i],&L[j+1]);
			j++;
		}
	}
	swap(&L[p],&L[j]);
	return j;
}
/*递归分组快速排序*/
void quickSort(listNode L[], int p, int r)
{
	if(p<r)
	{
		int q=partition(L,p,r);
		quickSort(L,p,q-1);
		quickSort(L,q+1,r);
	}
}


 

5.堆排序

算法描述:堆排序是一种选择排序,可以看做直接选择排序的升级版,堆排序是以大顶堆(小顶堆也能进行实现,这里使用大顶堆进行说明)的性质保持为基础进行选择排序,堆的结构是节点i的孩子为2 * i和2 * i + 1节点(根节点i=1,我实现时为了几个算法的统一是令根节点i=0来进行实现的),大顶堆要求父节点大于等于其2个子节点。为了维持这个性质,对于一个以i为根的子树A,从元素A[i],A[left(i)],A[right(i)]中选出最大值,如果最大值是根节点则结束,如果是左(右)节点则与根节点进行交换,并对左(右)子树继续进行选择交换,对一个序列构造大顶堆时从第n/2个元素向前进行性质维持就能构造出一个大顶堆。堆排序的思想是选出当前大顶堆的根节点,将其与最后的节点元素进行交换,再移除最后的节点,然后维持大顶堆的性质进行选择交换,直到所有元素移除即完成排序工作。

 

稳定性描述:堆排序的过程是从第n/2开始和其子节点共3个值选择大顶堆,这3个元素之间的选择当然不会破坏稳定性。但当为(n/2)-1,(n/2)-2 ... 这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第(n/2)-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。


算法实现:

/*为了方便程序理解将这两个函数写在外部*/
int left(int i)
{
	return (i+1)*2-1;
}

int right(int i)
{
	return (i+1)*2;
}
/*大顶堆化,默认i节点的两个子树已为大顶堆,在此基础上将以i节点为根的子树大顶堆化*/
void maxHeapify(listNode L[], int i, int heapSize)
{
	int largest;
	int l=left(i);
	int r=right(i);
	//比较出i节点和两个子节点的最大值
	if(l<heapSize && L[l]>L[i])
		largest=l;
	else
		largest=i;
	if(r<heapSize && L[r]>L[largest])
		largest=r;
	//将最大节点置于i节点位置,然后视情况继续调整维持大顶堆的性质
	if(largest!=i)
	{
		swap(&L[i],&L[largest]);
		maxHeapify(L,largest,heapSize);
	}
}
/*构建大顶堆*/
void buildMaxHeap(listNode L[], int length)
{
	//从第一个非叶节点开始向上实现大顶堆化
	for(int i=length/2-1;i>=0;i--)
	{
		maxHeapify(L,i,length);
	}
}
/*堆排序*/
void heapSort(listNode L[], int length)
{
	buildMaxHeap(L,length);
	//将大顶堆的根节点置于最后然后保持性质不断循环
	for(int i=length-1;i>=1;i--)
	{
		swap(&L[0],&L[i]);
		maxHeapify(L,0,i);
	}
}

 

 

6.希尔排序

算法描述:希尔排序是一种插入排序,可以看做直接插入排序的升级版,将需要排序的序列划分成为若干个较小的子序列,对子序列进行插入排序,通过则插入排序能够使得原来序列成为基本有序。这样通过对较小的序列进行插入排序,然后对基本有序的数列进行插入排序,能够提高插入排序算法的效率。对于子序列的构成不是简单的分段,而是采取相隔某个增量的数据组成一个序列。一般的选择原则是:去上一个增量的一半作为此次序列的划分增量。直至增量为1时进行最后一次插入排序。

 

稳定性描述:由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

 

算法实现:

/*插入排序*/
void insertSort(listNode L[], int length, int d, int p)
{
	int i,j;
	for(i=p;i<length;i+=d)
	{
		int index=p;
		for(j=i-d;j>=0;j-=d)		//找到插入位置记入index
		{
			if(L[i]>=L[j])
			{
				index=j+d;
				break;
			}
			index=j;
		}
		for(j=i;j>index;j-=d)		//插入并移动元素
		{
			swap(&L[j],&L[j-d]);
		}
	}
}
/*希尔排序*/
void shellSort(listNode L[], int length)
{
	//递减增量分组进行插入排序
	for(int d=length/2;d>=1;d/=2)
	{
		for(int i=0;i<d;i++)
			insertSort(L,length,d,i);
	}
}


 

7. 归并排序

算法描述:归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,合并时采用相互比较两边序列最小值先放入合并序列的方法,然后按顺序不断比较放入合并序列。不断合并直到原序列全部排好序。

 

稳定性描述:可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。

 

算法实现:

/*将有序子序列合并*/
void merge(listNode L[], int p, int q, int r)
{
	int l1=q-p+1;
	int l2=r-q;
	listNode A[10],B[10];	//这里数组大小视数据规模而定,本人测试数据很小所以只定了10
	for(int i=0;i<l1;i++)	//将两边有序子序列分开
		A[i]=L[p+i];
	for(int j=0;j<l2;j++)
		B[j]=L[q+j+1];
	A[l1]=B[l2]=LISTNODE_MAX;	//在两边的子序列最后设置一个最大哨兵一边操作
	i=j=0;
	for(int k=p;k<=r;k++)	//合并操作
	{
		if(A[i]<=B[j])
		{
			L[k]=A[i];
			i++;
		}
		else
		{
			L[k]=B[j];
			j++;
		}
	}
}
/*归并排序*/
void mergeSort(listNode L[], int p, int r)
{
	int q;
	if(p<r)
	{
		q=(p+r)/2;
		mergeSort(L,p,q);	//递归分组
		mergeSort(L,q+1,r);
		merge(L,p,q,r);		//子序列合并
	}
}


 

8.基数排序

算法描述:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序是基于任意一种稳定排序的算法,但现在多应用于基于桶排序的排序,所以在下面给出基于桶排序(一种采取先分配再搜集的线性时间排序)的整数基数排序。

 

稳定性描述:基数排序基于任意一种稳定的排序算法,所以其是稳定的排序算法。


算法实现:

/*求当前整数x从个位起第d位数*/
int num(int x, int d)
{
	for(int i=1;i<d;i++)
		x/=10;
	x%=10;
	return x;
}
/*桶排序*/
void bucketSort(int L[], int length, int d)
{
	int i,j,k;
	int R[10][20];	//这里数组大小视数据规模而定,本人测试数据很小所以只定了20
	int count[10]={0};	//对当前位数相应的元素个数进行计数
	//分配
	for(i=0;i<length;i++)
	{
		k=num(L[i],d);	//k取得当前元素的d位数
		R[k][count[k]++]=L[i];
	}
	k=0;			//重置k用作收集的计数
	//收集
	for(i=0;i<10;i++)
	{
		if(count[i]!=0)
		{
			for(j=0;j<count[i];j++)
				L[k++]=R[i][j];
		}
	}
}
/*求序列元素的最大位数*/
int maxBit(int L[], int length)
{
	int d=1;
	int p=10;
	for(int i=0;i<length;++i)
	{
		while(L[i]>=p)
		{
			p*=10;
			d++;
		}
	}
	return d;
}
/*基数排序*/
void radixSort(int L[], int length)
{
	int d=maxBit(L,length);
	for(int i=1;i<=d;i++)
		bucketSort(L,length,i);
	//此处使用桶排序进行,可以根据实际情况采用其他任意稳定排序,如快速排序等
	//只需将比较的值设成各元素当前位数的值即可
}



排序算法稳定性参考:http://www.cnblogs.com/codingmylife/archive/2012/10/21/2732980.html


※说明:对于常用的8种排序方法进行了一个详细总结,代码使用C++完成,所有代码均在VC6.0上运行测试过,只是所写代码没有经过优化,只是简单的实现了各个不同的排序算法,关于这些算法的空间时间复杂度,实际运行速度以及详细图解我在这里并未作出说明,日后有时间的话也许会补上吧。博文里只给出了各个算法的核心实现以方便阅读,所有算法的实现以及测试代码已经打包好以方便大家测试或者理解,需要请戳这里



本文固定链接:http://blog.csdn.net/fyfmfof/article/details/26270613

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值