zoukankan      html  css  js  c++  java
  • 数据结构(二):排序算法

     

    一、 排序算法概述

      日常的程序设计中,排序是很常见的需求,把数据元素按照一定的规则进行排序,比如淘宝上的货品按照上架日期排序,百度的搜索按照最新的内容排序。

    二、 冒泡排序

      2.1排序原理

    • 比较相邻的元素,如果前一个元素比后一个元素大,就交换两个元素的位置
    •  第一次冒泡,最大的元素会被交换到最后的位置,第二次冒泡,第二大的元素会被交换到倒数第二的位置,以此类推,每次冒泡后参与冒泡的数量都会减少一个,直到最后参与冒泡的个数为1,则此时排序完成

      2.2排序演示图

       

      2.3代码实现

    import java.util.Arrays;

     

    public class Bubble {

          

           public static void main(String[] args) {

                  Integer[] list = new Integer[] {5,1,3,2,4};

                  Bubble.sort(list);

                  System.out.println(Arrays.toString(list));

           }

          

           /**

            * 冒泡核心排序算法

            * @param col

            */

           private static void sort(Comparable[] col) {

                  for(int i=col.length-1;i>0;i--) {//每次冒泡完都会减少一个数参与下一轮冒泡,直到剩最后一个数为止

                         for(int j=0;j<i;j++) {//前后元素的比较,由于每次冒泡完减少一个数参与比较,故j跟着i的数量走

                                if(bigger(col[j], col[j+1])) {

                                       switchPos(col,j,j+1);

                                }

                         }

                  }

           }

          

           /**

            * 前后元素的比较

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a,Comparable b) {

                  return a.compareTo(b)>0;

           }

     

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col,int a,int b) {

                  Comparable t = col[a];

                  col[a] = col[b];

                  col[b] = t;

           }

    }

          

      2.4时间复杂度分析

      冒泡排序中,元素交换的执行次数为

      (n-1)+(n-2)+…+2+1 = n^2/2-n/2

      元素参与比较的执行次数为

      (n-1)+(n-2)+…+2+1 = n^2/2-n/2

      故冒泡的算法时间复杂度相加为n^2-n,根据大O计数法为O(n^2)

    三、 选择排序

      3.1排序原理

    • 对于需要排序的数据对象中,假设第一个索引处的值为最小值,遍历过程中,当前索引处的值大于其他索引处的值时,认为其他索引处的值为最小值,并互相交换位置,直到遍历至数据对象的导数第二个数为止

      3.2排序演示图

       

      3.3代码实现

    import java.util.Arrays;

     

    public class Select {

     

           public static void main(String[] args) {

                  Integer[] list = new Integer[] {5,1,3,2,4};

                  Select.sort(list);

                  System.out.println(Arrays.toString(list));

           }

          

           /**

            * 选择核心排序算法

            * @param col

            */

           private static void sort(Comparable[] col) {

                  for(int i=0;i<col.length-1;i++) {

                         int minIndex = i;

                         for(int j=i+1;j<col.length;j++) {

                                if(bigger(col[minIndex],col[j])) {

                                       minIndex = j;

                                }

                         }

                         switchPos(col, i, minIndex);

                  }

           }

          

           /**

            * 前后元素的比较

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a,Comparable b) {

                  return a.compareTo(b)>0;

           }

     

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col,int a,int b) {

                  Comparable temp = col[a];

                  col[a] = col[b];

                  col[b] = temp;

           }

    }

     

      3.4时间复杂度分析

      选择排序使用双层for循环,外层是数据交换,里层是数据比较

      数据比较的次数是:(n-1)+(n-2)+…+2+1 = n^2/2-n/2

      数据交换的次数是:n-1

      故冒泡的算法时间复杂度相加为n^2/2+n/2-1,根据大O计数法为O(n^2)

    四、 插入排序

      4.1排序原理

    • 把待排序的数据元素分成已排序和待排序的两组
    • 找到未排序的第一个元素,向已排序的数据元素中插入
    • 倒叙遍历已经排序的元素,直到找到比待插入元素小的元素,将待插入元素放在此位置,其余元素向后移动一位

      4.2排序演示图

       

      4.3代码实现

    import java.util.Arrays;

     

    public class Insert {

     

           public static void main(String[] args) {

                  Integer[] list = new Integer[] {5,1,3,2,4};

                  Insert.sort(list);

                  System.out.println(Arrays.toString(list));

           }

          

           /**

            * 插入核心排序算法

            * @param col

            */

           private static void sort(Comparable[] col) {

                  //外层为未排序对象

                  for(int i=1;i<col.length;i++) {

                         //里层为已排序对象的倒序

                         for(int j=i;j>0;j--) {

                                if(bigger(col[j-1], col[j])) {

                                       switchPos(col, j-1, j);

                                }else {

                                       break;

                                }

                         }

                  }

           }

          

           /**

            * 前后元素的比较

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a,Comparable b) {

                  return a.compareTo(b)>0;

           }

     

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col,int a,int b) {

                  Comparable temp = col[a];

                  col[a] = col[b];

                  col[b] = temp;

           }

    }

          

      4.4时间复杂度分析

      插入排序使用双层for循环,外层是未排序数据元素,里层是已排序数据元素

      数据比较的次数是:(n-1)+(n-2)+…+2+1 = n^2/2-n/2

      数据交换的次数是:(n-1)+(n-2)+…+2+1 = n^2/2-n/2

      故冒泡的算法时间复杂度相加为n^2-n,根据大O计数法为O(n^2)

    五、 希尔排序

      5.1排序原理

    • 选定一个增长量h,对数据对象按h的间隔进行分组,并对分组后的数据进行插入排序
    • 减小增长量h直到剩下1,即排序成功
    • 增长量h的选定无固定规则,经过科学论证h的规则存在多种可行的可能,没有绝对最优的规则,一般选择数组的长度n/2作为初始值,随后也是按n=n/2做递减
    • 假设在插入排序中,有序的数组为2,3,5,6,无序数组为1,8,那么1和8的插入每次都得遍历2,3,5,6后才能做插入,而设定了增长量后,可以有效的减少遍历的次数,故希尔排序是插入排序的优化

      5.2排序演示图

       

      5.3代码实现

    import java.util.Arrays;

     

    public class Hill {

     

           public static void main(String[] args) {

                  Integer[] list = new Integer[] {5,1,3,2,4};

                  Hill.sort(list);

                  System.out.println(Arrays.toString(list));

           }

          

           /**

            * 希尔核心排序算法

            * @param col

            */

           private static void sort(Comparable[] col) {

                  int n = col.length;

                  int h = 1; //初始增量

                  while(h<n/2) {

                         h = h*2+1;

                  }

                 

                  while(h>=1) {

                         //外层先找到待插入的元素,待插入的元素以h为起始索引

                         for(int i=h;i<n;i++) {

                                //里层对待插入的元素,往前按h间隔找到待插入排序的数进行插入排序

                                for(int j=i;j>=h;j=j-h) {

                                       if(bigger(col[j-h],col[j])) {

                                              switchPos(col, j-h, j);

                                       }

                                }

                                h = h/2;

                         }

                  }

           }

          

           /**

            * 前后元素的比较

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a,Comparable b) {

                  return a.compareTo(b)>0;

           }

     

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col,int a,int b) {

                  Comparable temp = col[a];

                  col[a] = col[b];

                  col[b] = temp;

           }

    }

     

      5.4时间复杂度分析

      希尔排序的时间复杂度无法做清晰的计算,有人根据大量数据得出平均时间复杂度为O(n^1.3),我们可以做个实验如下,对于10w的大数据量情况下的排序,是不是希尔排序要优于插入排序

       

       

      通过实际的测验证明,希尔排序的性能确实是优于插入排序的

    六、 归并排序

      6.1排序原理

      

       如图,归并算法采用分治的思想,主要通过如下三步实现数据有序

    • 将数据元素进行对半拆分,直到拆成只有1个数据元素的n组数据
    • 对相邻的两组数进行排序,使之有序并合并
    • 不断重复上一步,直到合成了一组有序的数据
    • 归并排序是一种空间换时间的算法

      6.2排序演示图

       

       

       

       

      6.3代码实现

    import java.util.Arrays;

     

    public class Merge {

          

           //辅助数组声明

           private static Comparable[] assist;

     

           public static void main(String[] args) {

                  Integer[] list = new Integer[] {5,1,3,2,4};

                  Merge.sort(list);

                  System.out.println(Arrays.toString(list));

           }

          

           /**

            * 归并核心排序算法

            * @param col

            */

           public static void sort(Comparable[] col) {

                  int low = 0;

                  int high = col.length-1;

                  assist = new Comparable[col.length];

                  sort(col,low,high);

           }

          

           private static void sort(Comparable[] col,int low,int high) {

                  if(high<=low) {

                         return;

                  }

                 

                  int mid = low + (high-low)/2;

                 

                  //对左子组进行递归排序

                  sort(col,low,mid);

                 

                  //对右子组进行递归排序

                  sort(col,mid+1,high);

                 

                  //归并排序

                  merge(col,low,mid,high);

           }

          

           /**

            * 对已排序的几组数据进行归并排序

            * @param col

            * @param low

            * @param mid

            * @param high

            */

           private static void merge(Comparable[] col, int low, int mid, int high) {

                  int index = low;

                  int p1 = low;    //左右子组比对的左指针

                  int p2 = mid+1;  //左右子组比对的右指针

                 

                  //左右指针分别走,对比两个指针下的数据,小的往辅助数组里放

                  while(p1<=mid && p2<=high) {

                         int leftNum = (int) col[p1];

                         int rightNum = (int) col[p2];

                         System.out.println("左数:"+leftNum+" 右数:"+rightNum);

                         if(bigger(leftNum, rightNum)) {

                                assist[index++] = col[p2++];

                         }else {

                                assist[index++] = col[p1++];

                         }

                  }

                 

                  //右指针的数据比较完了,把左指针剩余的数填充到辅助数组中

                  while(p1<=mid) {

                         assist[index++] = col[p1++];

                  }

                 

                  //左指针的数据比较完了,把右指针剩余的数填充到辅助数组中

                  while(p2<=high) {

                         assist[index++] = col[p2++];

                  }

                 

                  for(int i=low;i<=high;i++) {

                         col[i]=assist[i];

                  }

           }

     

           /**

            * 前后元素的比较

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a,Comparable b) {

                  return a.compareTo(b)>0;

           }

     

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col,int a,int b) {

                  Comparable temp = col[a];

                  col[a] = col[b];

                  col[b] = temp;

           }

    }

     

      6.4时间复杂度分析

       

       假设数据元素的个数为N,那么拆分有logN层,每层的最坏情况都是比较N次,所以算法复杂度为logN*N+logN,根据大O计数法为O(N*logN)

    七、 快速排序

      7.1排序原理

       

    • 从数据元素中取一个值作为分界值,选取小于该分界值的元素作为左子组,选取大于该分解值的元素作为右子组
    • 左右子组一直重复上述操作,直到子组的个数为1

      7.2排序演示图

       

       

      

       

      

       

      7.3代码实现

    package com.data.struct.common.sort.quick;

     

    import java.util.Arrays;

    import com.data.struct.common.sort.merge.Merge;

     

    public class Quick {

     

           public static void main(String[] args) {

                  //Integer[] list = new Integer[] { 6,7,8,9,10,2,3,1};

                  Integer[] list = new Integer[] {3,1,2};

                  Quick.sort(list);

                  System.out.println(Arrays.toString(list));

           }

     

           /**

            * 归并核心排序算法

            *

            * @param col

            */

           public static void sort(Comparable[] col) {

                  int low = 0;

                  int high = col.length - 1;

                  sort(col, low, high);

           }

     

           private static void sort(Comparable[] col, int low, int high) {

                  if (high <= low) {

                         return;

                  }

                 

                  System.out.println(Arrays.toString(col));

                 

                  int quickIndex = quick(col, low, high);

     

                  // 对左子组进行递归排序

                  sort(col, low, quickIndex-1);

     

                  // 对右子组进行递归排序

                  sort(col,  quickIndex+1, high);

           }

     

           /**

            * 对已排序的几组数据进行归并排序

            *

            * @param col

            * @param low

            * @param mid

            * @param high

            */

           private static int quick(Comparable[] col, int low,  int high) {

                  //设定一个基准值,一般以第一个数为准

                  Comparable value = col[low];

                  //左指针起始位置

                  int leftPointer = low;

                  //右指针起始位置

                  int rightPointer = high + 1;

                 

                  while(true) {

                         boolean flag = bigger(col[--rightPointer],value);

                         while(flag) {     //从右往左扫指针,发现比基准值小的值则停下

                                if(rightPointer<=low) {

                                       break;

                                }

                         }

                        

                         while(bigger(value,col[++leftPointer])) {     //从左往右扫指针,发现比基准值大的值则停下

                                if(leftPointer>=high) {

                                       break;

                                }

                         }

                        

                         if(leftPointer>=rightPointer) {                          //左指针和右指针相遇,说明全部元素已扫描完成,跳出while循环

                                break;

                         }else {

                                switchPos(col, leftPointer, rightPointer); //左指针和右指针没相遇前,说明元素还没扫描完成,此时符合条件的元素交换位置

                         }                                                                              

                  }

                 

                  //此时左边的元素小于基准值,右边的元素大于基准值,此时交换基准值和左右指针重叠处的值,基准值所在的索引,为新分组数据的分界线

                  switchPos(col, low, rightPointer);           

                  return rightPointer;

           }

     

           /**

            * 前后元素的比较

            *

            * @param a

            * @param b

            * @return

            */

           private static boolean bigger(Comparable a, Comparable b) {

                  return a.compareTo(b) > 0;

           }

     

           /**

            * 元素位置的交换

            *

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] col, int a, int b) {

                  Comparable temp = col[a];

                  col[a] = col[b];

                  col[b] = temp;

           }

    }

     

      7.4时间复杂度分析

       

      假设每次选中的基准值都是中位数,则会出现如图的情况,切分次数为logN次,每次交换元素的操作为N/2,所以最优情况下的算法复杂度为N/2*logN次,根据大O计算法推算出为O(N*logN)

       

      假设每次选中的基准值都是最大值或最小值,则每次左或右指针都得全量扫描,即N次,切分次数因此也需要N次,如果有数字为4,1,2,3,那么每次快排的结果如下

      4123

      3124

      2134

      1234

      所以最坏情况下的快排结果为O(N^2)

    7.5快速排序和归并排序的区别

    • 归并排序是将若干数据元素切割成了最细粒度的左右子组,并对相邻子组进行排序,并合并。快速排序是将若干数据元素切割成了有序的左右子组,这样当所有左右子组都有序时,整个数据元素对象也就有序了。
    • 在归并排序中,一个数组被等分为两半,递归调用发生在排序之前,在快速排序中,切分数组的位置取决于数组的内容,递归调用发生在排序之后

    八、 排序的稳定性

      在一组数据元素中,A和B相等,并且A元素排在B元素后面,经过一轮排序后,值相等的A和B的位置依然不变,此时排序是稳定的。

       

      稳定性的意义:

      如果一组数据元素只需要根据一种排序方式进行排序,此时排序稳定性是没有意义的,多种方式的排序,如几件商品同时进行价格,销量,库存等方式的排序,则此时排序稳定性是有意义的,如下:

      按价格从高到低排序

       

      按销量从高到低排序

       

      排序算法的稳定性:

      冒泡排序(稳定算法):冒泡排序中,通过相邻元素的大小比较,只有当前一个元素比后一个元素大时才交换位置,所以相等元素的位置保持不变,所以冒泡排序是稳定的算法

      选择排序(不稳定算法):选择排序中,是假设第一个数为最小的,往后遍历发现比该数小的则交换位置,在5,6,5,2这一组数中,当第一个数遍历至元素2时,会发生元素交换,此时变成了2,6,5,5

      由此判断选择排序是不稳定的

      希尔排序(不稳定算法):希尔排序是按照一定增长量进行排序的算法,假设有一组数3,3,1,5,希尔的增长量为2,则在第一次插入排序后变成了1,3,3,5,由此判断希尔排序是不稳定的

      归并排序(稳定算法):归并算法是虽然将数据元素拆分成n对,并且对相邻组的元素进行排序,但是只有在发生左指针扫到的元素,大于右指针扫的元素时,才将小的元素填充到辅助数组中

      相等元素不会发生操作,所以归并排序是稳定的

      快速排序(不稳定算法):快速排序是找一个基准值,左指针扫到比基准值小的放左边,右指针扫到比基准值大的放右边,若基准值找的数据元素中的中位数,如 4,3,2,2,1

      第一轮快排后的位置是1,2,2,3,4,由此判断快速排序是不稳定的

     

  • 相关阅读:
    Android(java)学习笔记68:使用proguard混淆android代码
    SGU 194 Reactor Cooling
    关于流量有上下界的网络流问题的求解
    关于最小割的求解方法
    HDU 5311 Hidden String
    POJ 3548 Restoring the digits
    POJ 2062 HDU 1528 ZOJ 2223 Card Game Cheater
    ZOJ 1967 POJ 2570 Fiber Network
    HDU 1969 Pie
    HDU 1956 POJ 1637 Sightseeing tour
  • 原文地址:https://www.cnblogs.com/jiyukai/p/13874189.html
Copyright © 2011-2022 走看看