常见排序算法

477 阅读4分钟

常见排序算法

排序问题作为算法基础的重要组成部分,代码实现方式多种多样,但其原理是基本一致的。常见的排序算法有:

  • 冒泡排序

  • 选择排序

  • 插入排序

  • 归并排序

  • 快速排序

  • 堆排序

  • 桶排序

  • 基数排序

具体代码实现如下,均测试通过,如有错误,希指出。

冒泡排序

  • 设置外层循环次数
  • 设置内层每一轮要比较的次数
  • 两两比较,把小的数放到前面去
 public void bubbleSort(int[] a){
        int len = a.length;         //外层循环次数
        for(int i=0;i<len;i++){
            for(int j=0;j<len-i-1;j++){ //内层每一轮比较的次数
                if(a[j]>a[j+1]){        //如果前一位数比后一位数大,则交换(即向上冒泡)
                    int temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                }
            }
        }
    }

__ 优化__ :

设置标志位swapFlag = true,默认为全部已排好序。若在循环过程中,出现(排序)交换动作,则swapFlag = true,说明循环还得继续;若无交换动作,说明已经排好序的了,不满足循环条件直接跳出,减少不必要的循环次数,提高效率。

public void bubbleSort(int[] a){
        int len = a.length;
        boolean swapFlag = true;    //标志位:交换标志位,true为无交换,false为有交换
        for(int i=0;i<len && swapFlag;i++){
            swapFlag = false;      //默认不存在交换,若无交换动作干扰,可以直接跳出循环,减少不必要的循环次数
            for(int j=0;j<len-i-1;j++){
                if(a[j]>a[j+1]){
                    int temp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = temp;
                    swapFlag = true;//若存在交换情况
                }
            }
        }
    }

选择排序

  • 设定循环次数,和当前数字和当前位置
  • 将当前位置的数和后面所有的数进行对比,小的数赋值给min,并记住小数的位置
  • 对比完成后,将当前位置的数与最小的值位置交换,位置加一
  • 重复第二三步直到循环到最后一次结束
public void selectSort(int[] a){
        int len = a.length;
        for(int i=0;i<len;i++){    //循环比较的次数
            int min = a[i];        //设置当前位置的值为最小值
            int position = i;           //要交换的值的位置
            for(int j = i;j<len;j++){    //与后面的值进行比较
                if(min>a[j]){       //如果存在比min更小的值,则赋值给min
                    min = a[j];
                    position = j;
                }
            }
            a[position] = a[i];   //对比完成后,将当前位置的数与最小的值位置交换
            a[i] = min;
        }
    }

插入排序

  • 设定插入次数,即循环的次数,第一个数不用插入,即length-1次,总共
  • 设定要插入数的位数和有序序列最后一位的位数
  • 从最后一位向前循环,如果插入数小于当前数,就将当前数向后移动一位
  • 如果大于当前数,将当前数放到该位置
public void insertSort(int[] a){
        int len = a.length;
        for(int i=1;i<len;i++){  //因为第一次不用,所以从1开始
            int insertNum = a[i];    //要插入的数
            int j = i-1;        //有序序列的最后一位索引
            while(j>=0 && insertNum<a[j]){
                a[j+1] = a[j];     //元素向后移动
                j--;               //向前比较元素
            }
            a[j+1] = insertNum;     //找到位置,插入当前元素
        }
    }

归并排序

  • 递归分组,将每个分组送入merge方法进行排序
  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  • 设定两个指针(变量),最初位置分别为两个已经排序序列的起始位置
  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  • 重复步骤3直到某一指针达到序列尾
  • 将另一序列剩下的所有元素直接复制到合并序列尾
public void merge(int[] a,int low,int mid,int high){
        int[] temp = new int[high-low+1]; //新创建的临时数组
        int i = low;   //左指针
        int j = mid+1; //右指针
        int k = 0;    //新数组索引
        //把较小的数先移到新数组中
        while(i<=mid && j<=high){
            if(a[i]<a[j]){
                temp[k++] = a[i++];
            }else{
                temp[k++] = a[j++];
            }
        }
        //把左边剩余的数移入数组
        while(i<=mid){
            temp[k++] = a[i++];
        }
        //把右边剩余的数移入数组
        while(j<=high){
            temp[k++] = a[j++];
        }
        //把新数组中的数覆盖nums数组
        for(int k2 = 0;k2<temp.length;k2++){
            a[k2+low] = temp[k2];
        }
    }

 //主函数调用归并排序方法
    public void mergeSort(int[] a,int low,int high){
        int mid = (low+high)/2;
        if(low<high){
            //通过递归将数组进行分组
            //左边
            mergeSort(a,low,mid);
            //右边
            mergeSort(a,mid+1,high);
            //左右归并,将各个分组进行排序后合并
            merge(a,low,mid,high);
        }
    }

快速排序

  • 选定第一个为基准值,定义交换临时值
  • 当基准值左边的值小于基准值,基准值右边的值大于基准值时,i++,j--(前提条件:i<=j)
  • 当基准值左边的值大于基准值,基准值右边的值小于基准值时,借用临时值进行交换
  • 当一趟快速排序结束时,即i>j
  • 当满足j>start, i<end时,递归该方法,分别将i值和j值作为end值和start值参数
public void quickSort(int[] a,int start,int end){
        if(start<end){
            int baseNum = a[start];   //选定基准值
            int temp;     //交换临时值
            int i = start;
            int j = end;
            do{
                while(a[i]<baseNum && i<end){   //左边的数小于基准值时(合法)
                    i++;
                }
                while(a[j]>baseNum && j>start){ //右边的数大于基准值时(合法)
                    j--;
                }
                if(i<=j){        //当出现不满足上述条件的值时,将左右两个不符合条件的值交换
                    temp = a[i];
                    a[i] = a[j];
                    a[j] = temp;
                    i++;        //继续处理下一个索引值
                    j--;
                }
            }while(i<=j);

            if(start<j){        //当第一轮查找结束时,进行递归,处理基准值左边部分数组
                quickSort(a,start,j);
            }
            if(end>i){          //处理基准值右边部分数组
                quickSort(a,i,end);
            }
        }
    }

堆排序

  • 选定第一个为基准值,定义交换临时值
  • 对简单选择排序的优化
  • 将序列构建成大顶堆
  • 将根节点与最后一个节点交换,然后断开最后一个节点
  • 重复第一、二步,直到所有节点断开
public void heapSort(int[] a){
        //循环建堆
        int len = a.length;
        for(int i=0;i<len-1;i++){
            //建堆
            buildMaxHeap(a,len-1-i);
            //交换堆顶和最后一个元素
            swap(a,0,len-1-i);
        }
    }

    //交换方法
    private void swap(int[] data,int i,int j){
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

    //对data数组从0到lastIndex建大顶堆
    private void buildMaxHeap(int[] data,int lastIndex){
        //从lastIndex结点(最后一个结点的父节点开始)
        for(int i = (lastIndex-1)/2;i>=0;i--){
            //k保存正在判断的点
            int k =i;
            //如果当前k结点的子结点存在
            while(k*2+1 <= lastIndex){
                //k结点的左子结点的索引
                int biggerIndex = 2*k+1;
                //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k结点的右子结点存在
                if(biggerIndex < lastIndex){
                    //如果右子结点的值较大
                    if(data[biggerIndex]<data[biggerIndex+1]){
                        //biggerIndex总是记录较大子结点的索引
                        biggerIndex++;
                    }
                }
                //如果k结点的值小于其较大的子结点的值
                if(data[k]<data[biggerIndex]){
                    //交换它们
                    swap(data,k,biggerIndex);
                    //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k结点的值大于其左右子结点的值
                    k = biggerIndex;
                }else{
                    break;
                }
            }
        }
    }

时间因素,后续补充~

桶排序 基数排序

资料参考:www.cnblogs.com/10158wsj/p/…

总结

稳定性

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

平均时间复杂度

  • O(n^2):直接插入排序,简单选择排序,冒泡排序
  • 在数据规模较小时(9W内),直接插入排序,简单选择排序差不多。当数据较大时,冒泡排序算法的时间代价最高。性能为O(n^2)的算法基本上是相邻元素进行比较,基本上都是稳定的
  • O(nlogn):快速排序,归并排序,希尔排序,堆排序

其中,快排是最好的, 其次是归并和希尔,堆排序在数据量很大时效果明显。

排序算法的选择

1.数据规模较小

  • 待排序列基本有序的情况下,可以选择直接插入排序;
  • 对稳定性不作要求宜用简单选择排序,对稳定性有要求宜用插入或冒泡

2.数据规模不是很大

  • 完全可以用内存空间,序列杂乱无序,对稳定性没有要求,快速排序,此时要付出log(N)的额外空间。
  • 序列本身可能有序,对稳定性有要求,空间允许下,宜用归并排序

3.数据规模很大

  • 对稳定性有求,则可考虑归并排序。
  • 对稳定性没要求,宜用堆排序
  • 序列初始基本有序(正序),宜用直接插入,冒泡

各算法复杂度