排序算法之选择排序(直接选择、堆排序)

排序算法稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。 ————百度百科

排序原理

每一趟从待排序序列中找出最小的元素,顺序放在已排好序的序列最后,直到全部序列排序完毕。

直接排序原理

假设有个数组array={里面有n个数据},第一趟排序从array[1] ~ array[n]中找出比array[0]小的元素,交换顺序。第一趟排序从array[2] ~ array[n]中找出比array[1]小的元素,交换顺序,以此类推,循环直到array[n-1]结束,因为array[n]此时一定是最大的。

堆排序原理

根据堆的性质,根节点一定是最小(或最大)的元素,通过不断删除最小元(或最大元)来维持一个有序数组即可达到排序要求。由于需要使用一个数组来存储每次删除的最小元(或最大元),因此,存储需求增加了一倍,回避使用第二个数组的方法可以利用堆的结构性。每次deleteMin之后,堆缩小1,因此,位于堆中最后的单元可以用来存放刚刚删去的元素。

若是使用最小堆(即根节点是最小元),则最后的这个数组是以递减的顺序存放元素。如果我们想要这些元素排成更典型的递增顺序,那么可以使用最大堆来实现,下面的堆排序算法就是利用最大堆来实现排序。

直接选择排序


从以上的这个栗子中我们可以更好的理解直接选择排序,并且需要注意的是,在第二趟排序过程中,第一个5的顺序被交换到了第二个5的后面,因此也可以看出直接选择排序是不稳定的排序算法。

       排序过程                  宏观过程

代码如下:

public void selectSort(T[] a){
        int k; //记录目前找到的最小值的下标
        T tmp;
        for(int i=0;i<a.length;i++){
            k = i;
            for(int j=k+1;j<a.length;j++){
                if(a[j].compareTo(a[k])<0){
                    k = j;
                }
            }
            //内层循环结束,如果在本次循环找到更小的数,则交换
            if(i != k){
                tmp = a[i];
                a[i] = a[k];
                a[k] = tmp;
            }
        }
    }

时间复杂度:O(N²)
空间复杂度:O(1)

堆排序


上图展示的在构建完堆序之后的最大堆以及在进行一次deleteMax操作之后堆。我们知道堆可以用一个数组来实现,并且也看出当删除最大元后我们将该最大元置于数组刚刚元素31的位置上,再经过5次deleteMax操作之后,该堆实际上只有一个元素,而堆数组中留下的元素将是排序后的顺序。

上代码:

/** * 构建堆 */
    private <T extends Comparable<? super T>> T[] buildHeap(T[] array, int i,int n){
        T tmp;
        int child;
        for(tmp = array[i];leftChild(i) < n;i = child){
            child = leftChild(i);
            if(child != n-1 && array[child].compareTo(array[child+1])<0){
                child++;
            }
            if(tmp.compareTo(array[child])<0){
                array[i] = array[child];
            } else {
                break;
            }
        }
        array[i] = tmp;
        return array;
    }

	 /** * 删除最大元 */
    public <T extends Comparable<? super T>> T[] deleteMax(T[] array, int i){
        T item = array[0];
        array[0] = array[i];
        array[i] = item;
        return buildHeap(array, 0, i);
    }

    private int leftChild(int i){
        return i * 2 + 1;
    }


    /** * 堆排序 * 传入数组,从最后一个叶子节点的父节点处开始构建最大堆 * 最大堆序性质:任一节点的父节点大于等于该节点 * 删除最大元后,应该重新保持堆序性质 */
    public <T extends Comparable<? super T>> void heapSort(T[] array){
	
        for(int i=array.length/2 - 1;i>=0;i--){
            array = buildHeap(array, i, array.length);
        }
        for(int i=array.length-1;i>=0;i--){
            array = deleteMax(array, i);
        }
    }

上面的堆排序算法中,我们需要在一开始将数组构建成一个最大堆的形式,方式则是从最后一个叶子节点的父节点开始构建堆,直到根节点构建完毕。之后进行删除最大元操作即可。

时间复杂度:O(NlogN)
空间复杂度:O(1)

总结

直接选择排序和堆排序都是不稳定的排序算法。但是在《数据结构与算法分析 for Java》第三版的P193页中写到,堆排序是一个非常稳定的算法,这点我百度了许多堆排序,都说堆排序是不稳定的算法,这里大家可以自行验证堆排序的稳定性,哈哈哈。

更多了解,还请关注我的个人博客:www.zhyocean.cn