排序算法 及其稳定性解释

排序算法的稳定性是指在待排序的序列中,存在多个相同的元素,若经过排序后这些元素的相对词序保持不变,即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排序是不稳定的。


综上所述:

稳定排序算法不稳定排序算法
冒泡排序、插入排序、归并排序、基数排序选择排序、快速排序、希尔排序、堆排序


    原文作者:排序算法
    原文地址: https://blog.csdn.net/u010161379/article/details/51050954
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞