排序算法的稳定性是指在待排序的序列中,存在多个相同的元素,若经过排序后这些元素的相对词序保持不变,即Xm=Xn,排序前m在n前,排序后m依然在n前,则称此时的排序算法是稳定的。下面针对常见的排序算法做个简单的介绍。
1.冒泡排序
public static void bubbletSort(int[] list){
for(int k=1;k<list.length;k++){
for (int i=0;i<list.length-k;i++){
if (list[i] > list[i+1])
swap list[i] with list[i+1];
}
}
}
从上述冒泡排序的算法可以看出,其原理就是从左往右相邻的元素两两对比,将大的元素往后移直到排序完成。如果两个元素相等,是不会交换其位置的,因此冒泡排序是稳定的。注:如果将交换条件改为“list[i]>=list[i+1]”就不是稳定的了。
2.归并排序
public static void mergeSort(int[] list){
if (list.length>1){
mergeSort(list[0...list.length/2]);
mergeSort(list[list.length/2+1... list.length]);
merge list[0...list.length/2] with list[list.length/2+1... list.length];
}
}
public static int[] merge(int[] list1,int[] list2){
int[] temp = new int[list1.length+list2.length];
int current1 = 0,current2 =0,current3 = 0;
while(current1<list1.length && current2<list2.length) {
if (list1[current1] <= list2[current2])
temp[current3++] = list1[current1++];
else
temp[current3++] = list2[current2++];
}
while (current1<list1.length)
temp[current3++] = list1[current1++];
while (current2<list2.length)
temp[current3++] = list2[current2++];
return temp;
}
从归并算法可以看出,其原理是将待排序列递归地划分为短序列,指导每部分都只包含一个元素,然后再合并,合并时如果两个元素相等也会按照元素之前的顺序,把下标小的元素先放入结果列表中,依然没有破环相同元素之间原本的顺序,因此归并算法也是稳定的。注:如果在合并中,判定条件改为“if(list1[current1] < list2[current2])”则不稳定。
3.选择排序
public static void selectionSort(int[] list){
for (int i=0;i<list.length - 1;i++){
int currentMin = list[i];
int currentMinIndex = i;
for(int j=i+1;j<list.length;j++){
if (currentMin > list[j]){
currentMin = list[j];
currentMinIndex = j;
}
}
if (currentMinIndex != i){
list[currentMinIndex] = list[i];
list[i] = currentMin;
}
}
}
从选择排序算法可以看出,其原理是对待排序列从左到右,为每个位置寻找当前最小的元素,比如为list[0]选择list中最小的元素与原来list[0]出的元素交换,为list[1]从剩下元素中选择最小的与其交换,依次类推。但这个算法是不稳定的,比如“4 8 3 4 2 9 7”,为第一个位置选择最小的元素2,与第一个位置的4交换,这破环了原来两个4之间的顺序。
4.插入排序
public static void insertionSort(int[] list){
for (int i=0;i<list.length;i++){
int currentElement = list[i];
int k;
for(k=i-1; k>=0 && list[k] > currentElement; k--){
list[k+1] = list[k]; //如果当前值大于已排好序列表的k元素,则k元素后移
}
list[k+1] = currentElement;
}
}
从插入排序可以看出,其原理是在一个已经排好序的序列中依次插入一个新的元素。如果碰到相等的元素,就把新元素插入相等元素的后面,即他们原来的顺序没有变化,因此插入排序是稳定的。
5.快速排序
public static void quickSort(int[] list){
if(list.length > 1){
select a pivot;
//一般默认主元为第一个元素,同时从两边向中间比较
//前向找到一个大于主元的元素,后向找到一个小于主元的元素,交换这两个元素
//直到前向元素下标大于后向元素下标,把主元与此时后向元素交换,即主元前的元素
//都小于主元,主元后的元素都大于主元。
partition list into list1 and list2 such that
all elements in list1 <= pivot and
all elements in list2 >pivot;
quickSort(list1);
quickSort(list2);
}
}
快速排序是不稳定的,如“ 5 3 3 4 3 8 9 10 11”第一次切分,主元5要和元素3交换,即改变了3和另两个相等元素之间的顺序。
6.堆排序
堆可以存储在一个数组中,对于位置i的节点,它的左孩子在位置2i+1处,右孩子在2i+2处,它的父节点在(i-1)/2处。对堆排序分两步:添加新节点和删除跟节点。
添加新节点
Let the last node be the current node;
while(the current node is greater than its parent){
swap the current node with its parent;
Now the current node is one level up;
}
添加新节点后每个节点,堆中的元素已排好序;
删除跟节点:(即降序从堆中取出所有元素)
Remove the root node;
Move the last node to replace the root;
Let the root be the current node;
重新对堆排序;
Heap类:
public class Heap<E extends Comparable> {
private ArrayList<E> list = new ArrayList<E>();
public Heap(){
}
public Heap(E[] objects){
for (int i=0;i<objects.length;i++){
add(objects[i]);
}
}
//添加新的节点
public void add(E newObject){
list.add(newObject);
int currentIndex = list.size()-1;
while (currentIndex>0){
int parentIndex = (currentIndex-1)/2;
if (list.get(currentIndex).compareTo(list.get(parentIndex))>0){
E temp = list.get(currentIndex);
list.set(currentIndex, list.get(parentIndex));
list.set(parentIndex,temp);
}else
break;
currentIndex = parentIndex;
}
}
//删除跟节点
public E remove(){
if (list.size() ==0) return null;
E removedObject = list.get(0);
list.set(0,list.get(list.size()-1));
list.remove(list.size()-1);
int currentIndex = 0;
while(currentIndex < list.size()){
int leftChildIndex = 2*currentIndex+1;
int rightChildIndex = 2*currentIndex+2;
if (leftChildIndex >= list.size()) break;
int maxIndex = leftChildIndex;
if (rightChildIndex < list.size()){
if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0){
maxIndex = rightChildIndex;
}
}
if (list.get(currentIndex).compareTo(list.get(maxIndex))<0){
E temp = list.get(maxIndex);
list.set(maxIndex,list.get(currentIndex));
list.set(currentIndex,temp);
currentIndex = maxIndex;
}else
break;
}
return removedObject;
}
public int getSize(){
return list.size();
}
}
添加新节点不会破坏相同元素的顺序,但删除根节点获取会破坏,因此堆排序不是稳定的排序算法。
7.基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。
8.希尔排序
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。
综上所述:
稳定排序算法 | 不稳定排序算法 |
冒泡排序、插入排序、归并排序、基数排序 | 选择排序、快速排序、希尔排序、堆排序 |