排序算法的稳定性的意义:
如果一个排序算法将排序的关键字划分为若干段,然后对每段一次进行排序,若每段排序都是稳定的,那么最后整个关键字排列也是有序的。典型的就是基数排序。
算法的稳定性,一般用于上面种情况。
排序算法稳定性的依据:排序结束后“相等元素间的相对次序是否改变”。
要保证最后的相等元素次序不发生改变,一般有两种做法:
1、在过程中控制“相等元素的次序”。
2、在基本排序结束后再次对相等元素进行“重排”使保持最初的相对次序(这需要辅助空间的开销).
实用的是第一种方法,因为第二种会付出更多的时间和空间开销。
常见排序算法的稳定排序实现:
注意虽然冒泡排序和插入排序一般来说稳定的排序,但应该注意有些不恰当的冒泡和插入排序的算法他们并不稳定;比如下面的两个例子:
“不恰当的冒泡排序”
void BubbleSort(int* A,int n)
{
for(int i=0;i<n;i++)
{
for(int j=1;j<n-i;j++)
{
if(a[j-1]>=a[j]) //该处的“=”号将导致算法不稳定,应该改为 if(a[i-1]>a[j])
{
swap(a[j-1],a[j]);
}
}
}
}
"不恰当的插入排序"
void InsertionSort(int *A ,int n)
{
for(int i=1;i<n;i++)
{
int key=A[i];
int j=i-1;
while(j>=0 && A[j]>= key) //该处的第二个“=”会导致算法不稳定,应该改为 while(j>=0 && A[j]>key)
{
A[i+1]=A[i];
--j;
}
A[i+1]=key;
}
}
冒泡排序和插入排序只要注意细节就可以保证算法的稳定性,然而对于想快速排序、选择排序、堆排序.......的算法,就需要添加额外的控制条件和辅助空间来保证算法的稳定性,但他们稳定的改进方法都是类似的。下面以选择排序为例介绍如何将不稳定的排序算法改进为稳定的排序算法。
设排序如下数组:
int A[]={3, 5, 4, 7,3,1};
1、一般的选择排序算法:
void ChoosingSort(int *A ,int n)
{
for(int i=0;i<n-1;i++)
{
int min =i;
for(int j=i+1;j<n;j++)
{
if(A[j]<A[min])
min=j;
}
Swap(A[min],A[i]);
}
}
如果使用上面一般的排序算法后,排序结果为
由排序结果可以看出其中值为“3”的元素相对位置发生了变化。
2、实现稳定性的改进;
(1)数据改进:添加元素位置标识;
以结构体方法为例子(也可以有其他方法):
原数组定义为: int A[]={3, 5, 4, 7,3,1};
改定义 : 先定义结构体 struct Data { int value , int id}; 然后定义数据 Data A[] ={ {3,0}, {5,1}, {4,2}, {7,3}, {3,4} , {1,5} };
(2)算法改进:添加控制元素相对位置的逻辑
void ChoosingSort(Data *A ,int n)
{
for(int i=0;i<n-1;i++)
{
int min =i;
for(int j=i+1;j<n;j++)
{
if(A[j].value<A[min].value or A[j].value=A[min].value && A[j].id<a[min].id )
min=j;
}
Swap( A[min], A[i] );
}
}
总结:将不稳定算法改进为稳定算法的核心是“添加处理相同元素比较的逻辑”,算法的主干一般不需要改动。