开发总结

经典排序(一)

2017-02-25  本文已影响124人  锅与盆

声明:该文章为个人学习笔记,非完全原创

总结一下各种经典排序算法,这个也算面试常考的东西了,总结出来方便大家回忆回忆😁,今天还要弄项目,先写到快排,剩下的过几天再写。

一、冒泡排序

1.介绍

基于比较的排序算法,最简单,性能差,即使最好情况时间复杂度也是O(n^2),(可以加一个附加标记改进算法,j),原地排序

2.过程
  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3.例题

对于一个int数组,请编写一个冒泡排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
测试样例:
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
代码(改良算法,增加了一个标记,当发现序列已经是有序的时直接跳出循环):
import java.util.*;

    public class BubbleSort {
        public int[] bubbleSort(int[] A, int n) {
            // write code here
            int temp;
            int tag=0;
            for(int i =n;i>1;i--){
                
                for(int j = 0;j<i-1;j++){
                    if(A[j]>A[j+1]){
                        tag=1;
                        temp=A[j];
                        A[j]=A[j+1];
                        A[j+1]=temp;
                    }  
                }
                if(tag==0)
                    break;
                tag=0;
            }
            return A;
        }
    }

二、选择排序

1.介绍

常用于数值大键值小的小文件。优点是容易实现,不需要额外空间。

2.过程

寻找序列中最小值-》交换-》对所有元素重复

最好最坏平均时间复杂度都是O(n^2)
由于在CPU中交换比比较所需时间多,选择排序比较多,但和冒泡排序比,交换次数少,所以更快。不稳定性(相同键值的数据可能顺序会变)

3.代码
public class SelectionSort {
public int[] selectionSort(int[] A, int n) {
    for(int i=0;i<n-1;i++)
        for(int j=0;j<n-i-1;j++){
        if(A[j]>A[j+1]){
            int tmp=A[j];
            A[j] = A[j+1];
            A[j+1] =tmp;
        }
    }
    return A;
}  
}

三、插入排序

1.介绍

一种简单有效的比较排序算法。
优点:实现简单,数据量少时效率高,虽然最坏情况时间复杂度也是O(n^2),但实际运行效率优于前两者(平均比较n²/2,交换n²/4次,最坏情况加倍),稳定性,原地(额外空间O(1)),即时。适合数据几乎都已经排序或输入规模较小时使用。最好情况O(n),对于部分有序的输入来说几乎就是线性排序(如果每个元素调整距离最大为k,即可令序列有序,且k相对序列长度很小,此时可认为几乎有序,此时时间复杂度O(kn))。

    public class InsertionSort {
        public int[] insertionSort(int[] A, int n) {
            int i, j, temp;          
            for(i = 1; i < n; i++){
                temp = A[i];
                for(j = i; j > 0 && A[j - 1] > temp; j-- ){
                    A[j] = A[j - 1];
                }
                A[j] = temp;
            }          
            return A;
        }
    }

上述三个算法都是O(n^2)级别算法,下面介绍O(n*logn)级别算法

四、归并排序

时间复杂度最好最坏平均都是O(nlogn),但是空间复杂度O(n),稳定。网上有文章说可以把空间复杂度优化到O(1),但是会牺牲时间效率。其实算法优化也是一种时间和空间的权衡,用空间换时间或用时间换空间。

归并排序使用分治思想,是建立在归并操作(merge)上的一种有效的排序算法。注意下面代码中归并操作的做法,有一些题可能不是归并排序,但是利用归并操作的这个思想可以很好的解决。

大致过程如下:
将待排序序列R[0...n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。即先把数组分解成n个小序列,再两两组合(二路归并),要求组合后的新序列在序列内有序。

与快排相似,也是通过递归实现。不过是先递归(分解),再归并(组合)。归并过程为:
我们每次从两个列表开头元素选取较小的一个,直到某一个列表到达底部,再将另一个剩下部分顺序取出。看代码更好理解。下面上代码:

import java.util.*;
 
public class MergeSort {
    public int[] mergeSort(int[] A, int n) {
        sort(A,0,n-1);
        return A;
    }
    //分割数组
    public void sort(int[] A,int l,int r){
        if(l<r){
            int mid;
            mid=(l+r)/2;
            sort(A,l,mid);
            sort(A,mid+1,r);
            merge(A,l,mid,r);
        }
    }
    //归并
    public void merge(int[] A,int l, int mid, int r){
        int i=l;
        int j=mid+1;
        int k=l;
        int t;
        int[] temp = new int[A.length];
        while(i<=mid&&j<=r){
            if(A[i]<=A[j])
                temp[k++]=A[i++];
            else
                temp[k++]=A[j++];
        }
        while(i<=mid){
            temp[k++]=A[i++];
        }
        while(j<=r)
            temp[k++]=A[j++];
        for(t=l;t<=r;t++)
            A[t]=temp[t];
         
    }
}

五、快排

基于比较的著名排序算法,是分治的一个实例,计算复杂度需要用分治法主定理。

1.算法:
    a,如果数组中仅有一个元素或没有元素需要排序,则返回(递归返回条件)
    b,选择一个元素作枢轴(pivot),把数组分为,小于pivot的元素,大于的两部分(划分)
    c,对两部分递归调用该算法。(递归)
2.性能

时间复杂度与上面的相比最好,平均O(nlogn);最坏O(n²)(发生在数组已排序且选最后一个元素作枢轴的情况)。空间复杂度O(logn)~O(n²),因为是递归算法,需要利用栈空间保存。不稳定(可举例证明)。

3.枢轴的选择和优化

a、若选择数组最左边或最右边的元素作枢轴,可能由于非均衡划分导致快排最坏情况发生,所以不好。
b、随机选择枢轴元素可以保证每个元素被选中概率相等,确保划分在平均情况下均衡,防止最坏情况发生。
c、随机选枢轴只是令划分在平均情况下均衡。换句话说就是减小了最坏情况发生的概率,但实际上最坏情况时间复杂度还是O(n²)。我们可以想到如果能每次选所有元素的中位数作为枢轴,就能保证每次划分都是均衡的。但显然这样做是不可能的,因为寻找数组所有元素的中位数的时间开销太大了。一个常用的方法是从数组中随机选择3个元素,取其中位数作为枢轴。

4.代码
    import java.util.*;
    
    public class QuickSort {
        public int[] quickSort(int[] A, int n) {
            // write code here
            sort(A,0,n-1);
            return A;
        }
        void sort(int[] A,int l,int r){
            int pivot;
            if(l<r){
                pivot=partition(A,l,r);
                sort(A,l,pivot-1);
                sort(A,pivot+1,r);
            }
        }
        int partition(int[] A,int l,int r){
            int i=l;
            int j=r;
            int t=l-1;
            Random rand= new Random();
            int p=l+rand.nextInt(r-l+1);
            swap(A,r,p);
            for(;i<r;i++){
                if(A[i]<A[p]){
                    swap(A,i,++t);
                }
            }
            swap(A,r,++t);
            return t;
        }
        void swap(int[] A,int a,int b){
            int temp = A[a];
            A[a] = A[b];
            A[b] = temp;
        }
    }

六、希尔排序

希尔排序,又叫缩小增量排序(看到后面你就知道为什么这么叫了),由Donnald Shell提出而得名。是个不稳定算法(不理解的话可以举个例子)。
该算法本质上就是一个泛化的插入排序,可以视作直接插入排序的改良。因为插入排序在序列几乎有序时效率很高,希尔排序通过先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的。

希尔排序最好情况时间复杂度为O(n),平均和最坏情况复杂度取决于步长序列(增量序列)的选择。希尔排序最重要的也是步长选择这一步。

Donald Shell 最初建议步长选择为n/2,并且对步长取半直到步长达到 1。虽然这样取可以比O(n^2)类的算法(插入排序)更好,但这样仍然有减少平均时间和最差时间的余地。 可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的。

| 步长序列 | 最坏情况时间复杂度 |
| ------------- |:-------------:| -----:|
| n/(2^i) | O(n²) |
| 2i*3i | O(nlog²n) |
已知的最好步长序列由Marcin Ciura设计(1,4,10,23,57,132,301,701,1750,…) 这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。

另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713, …)

代码(步长选择n/(2^i)):

import java.util.*;

public class ShellSort {
    public int[] shellSort(int[] A, int n) {
        int h,i,j,temp;
        for(h=n/2;h>=1;h=h/2){
            for(i=h;i<n;i++){
                temp=A[i];
                j=i;
                for(;j>=h&&A[j-h]>temp;){
                    A[j]=A[j-h];
                    j=j-h;
                }
                A[j]=temp;
            }
        }
        return A;
    }
}
上一篇下一篇

猜你喜欢

热点阅读