基于比较的内排序——温故而知新

引言

排序和检索是数据数据的两个关键部分,排序是想尽快有序组织数据,检索则是想尽快查找数据。
最近发现对这个知识点里的部分内容生疏,所以特此做个总结。


环境

IDE:Eclipse
语言:Java


排序分类

这里写图片描述
排序算法在实现方法上被分为两个大类,我们今天讲的是基于比较的排序算法,非比较算法我们稍后再谈。


几大排序概览

一、性能

名称 时间复杂度 空间复杂度 稳定性
选择排序 O( n 2 ) O(1) 不稳定
冒泡排序 O( n 2 ) O(1) 稳定
插入排序 O( n 2 ) O(1) 稳定
shell排序 O( n 1.5 ) O(1) 不稳定
堆排序 O(nlgn) O(1) 不稳定
归并排序 O(nlgn) O(n) 稳定
快速排序 O(nlgn) O(lgn) 不稳定

二、具体介绍

A、选择排序

简介:每次选择最大或者最小的元素放到指定位置。
每次交换必然导致元素顺序的变换,所以是不稳定的。

public int[] selectSort(int[] a) {
        int len=a.length;
        for(int i=0;i<len;i++) {
            for(int j=i+1;j<len;j++) {
                if(a[j]>a[i]) {
                    int tmp=a[j];
                    a[j]=a[i];
                    a[i]=tmp;
                }
            }
        }
        return a;
    }
    in:
        int a[]= {3,4,5,1,8,4,9,2};
    out:
        9 8 5 4 4 3 2 1 

B、冒泡排序

简介:从指定位置开始,每次比较相邻元素,按照排序规则不断交换元素位置,在动态上看起来就像是一个不断往上冒得泡。
一个元素在冒泡过程中,不会改变其余元素的相对位置,因为他是一个个进行比较和位置交换的,不想选择排序跨位置交换,所以冒泡排序是稳定的。

 public void bubbleSort(int []a) {
        for(int i=0;i<a.length;i++) {
            boolean flag=false;
            for(int j=a.length-1;j>i;j--) {
                if(a[j]>a[j-1]) {
                    int temp=a[j];
                    a[j]=a[j-1];
                    a[j-1]=temp;
                    flag=true;
                }
            }
            // 如果该轮没有交换说明数组已经有序,直接退出;
            if(!flag) break;
        }
    }
    in:
        int a[]= {4,5,1,8,4,9,2};
    out:
        9 8 5 4 4 2 1 

C、插入排序

简介:维护两个数组,一个有序,另一个无序,每次从无序数组中取一个元素,在有序数组中按固定顺序比较找到位置插入即可。麻烦点在于后面元素的挪动。
同样,插入一个元素不会改变元素的相对位置,所以也是稳定的。

//个人改进版,边找位置边挪,不用先找到位置再挪。
public void insertSort(int[] a) {
        for(int i=1;i<a.length;i++) {
            int flag=i-1;int num=a[i];
            //不断往前比较,如果前面的数比他大,该数往后挪。
            while(flag>=0 && a[flag]>num) {
                a[flag+1]=a[flag];
                flag--;
            }
            a[flag+1]=num;
        }
    }
    in:
        int a[]= {8,5,1,4,9,2};
    out:
        1 2 4 5 8 9

D、归并排序

简介:两个有序数组如何合并成一个有序数组?假设两个数组都是从小到大排序,我们只要不断比较两个元素的头元素,把较小的元素拿出来接在新开的数组里面就可以了。采用递归的方式实现。
合并时,相同元素排放规则是固定的,所以归并排序也是稳定排序。
因为需要一个数组b来做中转,所以空间代价为O(n).

public void mergeSort(int start,int end,int[] a,int []b) {
        if(start==end) return ;
        int mid=(start+end)/2;
        //递归分解
        mergeSort(start,mid,a,b);
        mergeSort(mid+1,end,a,b);
        for(int i=start;i<=end;i++) {
            b[i]=a[i];
        }
        int i1=start,i2=mid+1;
        //合并数组
        for(int j=start;j<=end;j++){
            if(i1 == mid+1)
                a[j]=b[i2++];
            else if(i2 > end)
                a[j]=b[i1++];
            else if(b[i1]<b[i2])
                a[j]=b[i1++];
            else 
                a[j]=b[i2++];
        }
        return ;
    }
    in:
        int a[]= {8,5,1,4,9,2};
    out:
        1 2 4 5 8 9 

E、快速排序

简介:寻找一个轴值,通过交换把数组分为两部分,轴值左边的都比它小(大),轴值右边的都比它大(小);
因为涉及到轴值的多次交换,所以这个算法是不稳定的。
因为使用了递归,而且我们是把中间值作为轴值,所以空间复杂度为O(logn)。

//交换数组元素
public void swap(int[] a,int i,int j) {
        if(i==j) return ;
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }

//不断交换,并且返回轴值的最终位置。 
public int part(int a[],int l,int r,int pivot) {
        do{
            while(a[++l]<pivot); 
            while(l<r && pivot<a[--r]);
            swap(a,l,r);
        }while(l<r);
        return l;
    }

//递归实现整体排序
public void quickSort(int[] a,int i,int j) {
        if(j<=i) return ;
        int pivot=(i+j)/2;
        swap(a,pivot,j);
        int k=part(a,i-1,j,a[j]);
        swap(a,k,j);
        quickSort(a,i,k-1);
        quickSort(a,k+1,j);
    }
    in:
        int a[]= {8,5,1,4,9,2};
    out:
        1 2 4 5 8 9 

F、堆排序

简介:了解最大(小)堆的性质,堆里的元素我们可以通过数组来存储。通过不断地堆的“下沉”操作,实现不断堆中寻找到最大(小)元素,这种本质和选择排序一样。
但由于堆是一种树结构存储,所以我们的时间代价降为了O(lgn)。

//堆类
class Heap{
    private int[] heaparr;
    private int maxsize;
    private int n;
    public Heap(int[] h,int num,int max ) {
        heaparr=h;
        n=num;
        maxsize=max;
    }
    public void swap(int[] a,int i,int j) {
        if(i==j) return ;
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    private void siftDown(int pos) {
        while(!isLeaf(pos)) {
            int j=leftChild(pos);int rc=rightChild(pos);
            if(rc<n && heaparr[rc]>heaparr[j])
                j=rc;
            if(heaparr[pos]>heaparr[j]) return ;
            swap(heaparr,pos,j);
            pos=j;
        }
    }
    public void removeFirst() {
        if(n>0) {
            swap(heaparr,0,--n);
            if(n!=0) siftDown(0);
        }
    }
    public int leftChild(int pos) {
        return 2*pos+1;
    }
    public int rightChild(int pos) {
        return 2*pos+2;
    }
    public boolean isLeaf(int pos) {
        return (pos>=n/2 && pos<n);
    }
    public void heapsort() {
        for(int i=0;i<n;i++) {
            removeFirst();
        }
    }
}
    in:
        int a[]= {8,5,1,4,9,2};
    out:
        1 2 4 9 5 8 

G、希尔排序

简介:希尔排序本质上也是插入排序,只是对插入排序做了一定的优化。因为插入排序在数组较为有序的代价是线性的,所以整体思路是将整个数组局部有序化,关于具体的复杂度迄今没有具体论证,我们不加论证的给出代价为O( n 1.5 )

private void shellSort(int[] a) {
        for (int r = a.length / 2; r >= 1; r /= 2) {
            for (int i = r; i < a.length; i += r) {
                int temp = a[i];
                int j = i - r;
                while (j >= 0 && temp < a[j]) {
                    a[j + r] = a[j];
                    j -= r;
                }
                a[j + r] = temp;
            }
        }
    }
    in:
        int a[]= {8,5,1,4,9,2};
    out:
        1 2 4 5 8 9 

总结

排序是重要的基础知识,很多地方排序也要注意到稳定性、时间代价、空间代价问题,所以我们应该要熟记几种排序的特征。我们也注意到效率高的代码在空间复杂度上存在着一定要求,可见时间复杂度和空间复杂度这两个总是在互相制衡,而现在的倾向是时间往往比空间更重要,因为存储设备容量一直在不断扩大,高效率的算法更让人青睐。

猜你喜欢

转载自blog.csdn.net/Gease_Gg/article/details/81163225
今日推荐