杂文(7):排序算法总结

最后更新于2021年1月28日11:14:26

转载自:it_coders
转载自:排序算法的稳定性及其意义

小知识:

  • 算法稳定性
    假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的。

  • 堆排序、快速排序、希尔排序、直接选择排序 不稳定
    基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序 稳定

  • 稳定性的意义举例:例如要排序的内容是一组原本按照价格高低排序的对象,如今需要按照销量高低排序,使用稳定性算法,可以使得想同销量的对象依旧保持着价格高低的排序展现,只有销量不同的才会重新排序。

冒泡排序

遍历若干次要排序的数列,每次遍历时,它都会从前往后依次的比较相邻两个数的大小;如果前者比后者大,则交换它们的位置。这样,一次遍历之后,最大的元素就在数列的末尾! 采用相同的方法再次遍历时,第二大的元素就被排列在最大元素之前。重复此操作,直到整个数列都有序为止!

冒泡排序时间复杂度

冒泡排序的时间复杂度是O(N2)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1次!因此,冒泡排序的时间复杂度是O(N2)。


public class BubbleSort {

    /*
     * 冒泡排序
     *
     * 参数说明:
     *     a -- 待排序的数组
     *     n -- 数组的长度
     */
    public static void bubbleSort1(int[] a, int n) {
        int i,j;

        for (i=n-1; i>0; i--) {
            // 将a[0...i]中最大的数据放在末尾
            for (j=0; j<i; j++) {

                if (a[j] > a[j+1]) {
                    // 交换a[j]和a[j+1]
                    int tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;
                }
            }
        }
    }

    /*
     * 冒泡排序(改进版)
     *
     * 参数说明:
     *     a -- 待排序的数组
     *     n -- 数组的长度
     */
    public static void bubbleSort2(int[] a, int n) {
        int i,j;
        int flag;                 // 标记

        for (i=n-1; i>0; i--) {

            flag = 0;            // 初始化标记为0
            // 将a[0...i]中最大的数据放在末尾
            for (j=0; j<i; j++) {
                if (a[j] > a[j+1]) {
                    // 交换a[j]和a[j+1]
                    int tmp = a[j];
                    a[j] = a[j+1];
                    a[j+1] = tmp;

                    flag = 1;    // 若发生交换,则设标记为1
                }
            }

            if (flag==0)
                break;            // 若没发生交换,则说明数列已有序。
        }
    }

    public static void main(String[] args) {
        int i;
        int[] a = {20,40,30,10,60,50};

        System.out.printf("before sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");

//        bubbleSort1(a, a.length);
        bubbleSort2(a, a.length);

        System.out.printf("after  sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");
    }
}

快速排序

选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

流程

  1. 从数列中挑出一个基准值。
  2. 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
  3. 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。

快速排序时间复杂度

快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*lgN)。

这句话很好理解:假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢?至少lg(N+1)次,最多N次。

import java.util.Random;

public class QuickSort {

    public static void quickSort(int[] a, int l, int r) {
        if(l<r){
            int cur = randomPartition(a,l,r);
            quickSort(a, l, cur-1); /* 递归调用 */
            quickSort(a, cur+1, r); /* 递归调用 */
        }

	//这代码过了半个月我自己都搞不懂了,下面解释一下。
    }
    //randomPartition()方法:随机取基准值,交换最右值和基准值位置,很简单。
    private static int randomPartition(int[] a, int l, int r){
        Random random = new Random();
        int i = random.nextInt(r-l+1)+l;
        swap(a,i,r);
        return partition(a,l,r);
    }
    /*partition方法可能碰到几种情况:
    1.从头遍历的路上始终没有碰到比基准值大的值,那么执行if()语句就相当于
    swap(a,0,0),swap(a,1,1)...相当于没变化
    2.走着走着发现比基准值大的值了,那么i就不继续++了,停留在“大值”前一个索引,j继续遍历。
    i相当于记录“大值”的位置。
    3.j走着走着又发现比基准值小的值了,那么正好把这个“小值”和“大值”交换,i就能继续前进了。
    4.for循环走到倒数第二个元素就结束了,r位置上的基准值得换回去鸭,所以就把记录的大值跟基准值换一下。
    */
    private static int partition(int[] a, int l, int r){
        int x = a[r], i = l - 1;
        for (int j = l; j < r; ++j) {
            if (a[j] <= x) {
                swap(a, ++i, j);
            }
        }
        swap(a, i + 1, r);
        return i + 1;
    }
    
    private static void swap(int[] a, int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int i;
        int a[] = {30,40,60,10,20,50};

        System.out.printf("before sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");

        quickSort(a, 0, a.length-1);

        System.out.printf("after  sort:");
        for (i=0; i<a.length; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");
    }
}

直接插入排序

把n个待排序的元素看成为一个有序表和一个无序表。开始时有序表中只包含1个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,将它插入到有序表中的适当位置,使之成为新的有序表,重复n-1次可完成排序过程。

如图
Java笔记虾

直接插入排序时间复杂度

直接插入排序的时间复杂度是O(N2)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢?N-1!因此,直接插入排序的时间复杂度是O(N*N)。

/**
* 直接插入排序:Java
*
* @author skywang
* @date 2014/03/11
*/

public class InsertSort {

   /*
    * 直接插入排序
    *
    * 参数说明:
    *     a -- 待排序的数组
    *     n -- 数组的长度
    */
   public static void insertSort(int[] a, int n) {
       int i, j, k;

       for (i = 1; i < n; i++) {

           //为a[i]在前面的a[0...i-1]有序区间中找一个合适的位置
           for (j = i - 1; j >= 0; j--)
               if (a[j] < a[i])
                   break;

           //如找到了一个合适的位置
           if (j != i - 1) {
               //将比a[i]大的数据向后移
               int temp = a[i];
               for (k = i - 1; k > j; k--)
                   a[k + 1] = a[k];
               //将a[i]放到正确位置上
               a[k + 1] = temp;
           }
       }
   }

   public static void main(String[] args) {
       int i;
       int[] a = {20,40,30,10,60,50};

       System.out.printf("before sort:");
       for (i=0; i<a.length; i++)
           System.out.printf("%d ", a[i]);
       System.out.printf("\n");

       insertSort(a, a.length);

       System.out.printf("after  sort:");
       for (i=0; i<a.length; i++)
           System.out.printf("%d ", a[i]);
       System.out.printf("\n");
   }
}

希尔排序

希尔(Shell)排序又称为缩小增量排序,它是一种插入排序。它是直接插入排序算法的一种威力加强版。

流程

把记录按步长 gap 分组,对每组记录采用直接插入排序方法进行排序。
随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到 1 时,整个数据合成为一组,构成一组有序记录,则完成排序。

package notes.javase.algorithm.sort;

public class ShellSort {
   public void shellSort(int[] list) {
       int gap = list.length / 2;

       while (1 <= gap) {
           // 把距离为 gap 的元素编为一个组,扫描所有组
           for (int i = gap; i < list.length; i++) {
               int j = 0;
               int temp = list[i];

               // 对距离为 gap 的元素组进行排序
               for (j = i - gap; j >= 0 && temp < list[j]; j = j - gap) {
                   list[j + gap] = list[j];
               }
               list[j + gap] = temp;
           }

           System.out.format("gap = %d:\t", gap);
           printAll(list);
           gap = gap / 2; // 减小增量
       }
   }

   // 打印完整序列
   public void printAll(int[] list) {
       for (int value : list) {
           System.out.print(value + "\t");
       }
       System.out.println();
   }

   public static void main(String[] args) {
       int[] array = {
               9, 1, 2, 5, 7, 4, 8, 6, 3, 5
       };

       // 调用希尔排序方法
       ShellSort shell = new ShellSort();
       System.out.print("排序前:\t\t");
       shell.printAll(array);
       shell.shellSort(array);
       System.out.print("排序后:\t\t");
       shell.printAll(array);
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值