经典算法的性能分析与比较

一些术语和变量解释
public static long swapCount = 0; //交换次数
public static long compareCount = 0; //比较次数
public static long moveCount = 0; //数据移动次数

先简单介绍下这次要输出的算法有哪些:

排序算法

插入算法

插入算法是将未知的数按顺序取出放到另一个已排好序的队列中,将其放到合适的位置,形成新的排好序的队列。最直观的比较就是跟齐牌一样,将每一张摸好的牌插入到你前面齐好的顺序之中。

思路:
1、按顺序选取一个数
2、将选取的数与前面的数比较,比前面的数小就和前面的数交换,比前面的数大就停止比较

代码实现:

public static void insertSort(int[] arr){

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

        int j = i,temp = arr[i],k = i;

        for (; j > 0 && temp < arr[j - 1]; j--) compareCount++;

        for (; k > j && k > 0; k--) {
            arr[k] = arr[k - 1];
            moveCount++;
        };

        arr[k] = temp;
        swapCount++;
        }

    }

时间复杂度:T(n) = O(n),两次for循环
空间复杂度:S(n) = O(1),不需要额外的空间

选择排序

选择排序和插入排序正好相反,插入排序是按顺序选一个数放进排好序的队列,而选择排序是依次选一个最小或者最大的数取出来放进另一个队列。

思路:
1、从未排序的数组中选取一个最小的数
2、将选取的数直接放入前面排好序的数组

代码实现:

public static void selectSort(int[] arr) {

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

            int minIndex = i;
            int tempValue = arr[i];
            for (int j = i+1; j < arr.length; j++) {
                if (arr[j] < tempValue) {
                    minIndex = j;
                    tempValue = arr[j];
                }
                compareCount++;
            }
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
            swapCount++;
        }
    }

空间复杂度:T(n) = O(n),也是两次循环导致的。
空间复杂度:S(n) = O(1),不需要额外的空间。

归并排序

归并排序是典型的采用分治算法,降低比较的层级。将一个数组从1变为2,从2变为4,一直到数组只有一个数为止,然后再依次合并排好序的数组,虽然增加了数组的个数,但减少了比较的复杂度和数量。

思路:
1、将数组以中间拆分成两个数组,不断递归拆分,知道数组大小为1
2、将两个数组一次取出数进行比较,将小的数放入一个新的数组
3、将剩余没有放完的数组全部取出放入新的数组后面
4、将新的数组数据转移回原数组

代码实现:

public static void mergeSort(int[] arr,int left,int right){

        if (right <= left) {
            return;
        }
        int mid = (left + right) / 2;
        mergeSort(arr, left, mid);
        mergeSort(arr,mid+1,right);
        merge(arr, left, mid, right);
    }

    public static void merge(int[] arr, int low, int mid, int high) {

        int[] temp = new int[high - low + 1];

        int leftLength =  mid - low + 1;
        int rightLength = high - mid;

        int i = 0,j = 0,tempIndex = 0;
        while (i < leftLength && j < rightLength) {
            if (arr[i + low] <= arr[j + mid + 1]) {

                temp[tempIndex++] = arr[i + low];
                i++;
            } else {
                temp[tempIndex++] = arr[j + mid + 1];
                j++;
            }
            compareCount++;
            moveCount++;
        }
        while (i < leftLength) {
            temp[tempIndex++] = arr[i + low];
            i++;
            moveCount++;
        }
        while (j < rightLength) {
            temp[tempIndex++] = arr[j + mid + 1];
            j++;
            moveCount++;
        }
        for (int k = low; k <= high; k++) {
            arr[k] = temp[k - low];
            moveCount++;
        }
    }

时间复杂度:T(n) = O(nlogn),采用分治算法一共分治了logn个层级,每个层级需要n次合并操作,所以时间复杂度是O(nlogn)。
空间复杂度:S(n) = O(n),需要一个额外的数组储存归并排好序的数据。

冒泡排序

冒泡排序几乎可以算得上是一个暴力算法,选取每一个数和所有数进行比较,然后不停的交换,依次将将大的数交换到后面。

思路:
1、每一个数依次全部比较,选出最大的数放在后面
2、循环中每次比较的次数少1,后面的数逐渐拍好,直到剩下一个数位置

代码实现:

  public static void bubbleSort(int[] arr){

        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapCount++;
                }
                compareCount++;
            }
        }

    }

时间复杂度:T(n) = O(n^2),两次for循环的比较。
空间复杂度:S(n) = O(1),不需要额外的空间。

快速排序

快速排序的和分治算法的思想有点类似,只不过快速排序是找一个基准数,将其分为两部分,左边的数都比它小,右边的数都比它大。然后不断的递归实现。

思路:
1、选取第一个数为基准数。
2、选取左右两个指针,左边的数比基准数大向右走,右边的数比基准数小向左走,否则交换两个数。
3、递归实现

代码实现:

public static void quickSort(int[] a,int left, int right){

        if (left >= right) {
            return;
        }
        int v = a[left];
        int l = left + 1;
        int r = right;
        while (l < r) {

            if (a[l] <= v) {
                l++;
                compareCount++;
                continue;
            }
            if (a[r] >= v) {
                r--;
                compareCount++;
                continue;
            }
            int temp = a[l];
            a[l] = a[r];
            a[r] = temp;
            l++;
            r--;
            swapCount++;

        }
        if (a[left] > a[l]) {

            a[left] = a[l];
            a[l] = v;
        }else {
            a[left] = a[l - 1];
            a[l - 1] = v;
            l--;
        }
        compareCount++;
        swapCount++;
        quickSort(a,left,l-1);
        quickSort(a,l+1,right);
    }

时间复杂度:T(n) = O(nlogn),最快情况下是O(n^2),
空间复杂度:S(n) = O(1),没有额外的辅助存储空间。

希尔排序

希尔排序的思想是选取一个步长k,从中抽取不同的数组,他们都是以k为步长为一组进行内部排序,然后不断的缩小步长,缩短到1后结束排序。

思路:
1、选取一个步长
2、将每一步长的数组排序
3、缩小步长重复2步骤
4、直到步长为1结束

代码实现:

public static void shellSort(int[] a,int step) {


        while (step >= 1) {

            for (int i = 1; i <= step; i++) {

                for (int j = i - 1; j < a.length; j+=step) {

                    int temp = a[j];
                    int k = j;
                    while (k >= i + step - 1 && temp < a[k - step]) {

                        a[k] = a[k - step];
                        k -= step;
                        moveCount++;
                        compareCount++;
                    }
                    a[k] = temp;
                    swapCount++;

                }

            }
            step-=2;
        }



    }

时间复杂度:T(n) = O(nlogn),最坏情况O(n(logn)^2)。
空间复杂度:S(n) = O(1)。不需要额外的存储空间。

算法性能比较

这里的数据是java随机生成的10W个(0~100000)的数据。
比较次数是10W次。
算法性能比较

复杂度性能比较

这里的稳定性是说如果数组中a[i]和a[j]相等且i<j,在比较结束之后a[i]和a[j]中的数据没有进行交换,不稳定性和稳定性相反。
复杂度性能比较

更多精彩能容请听下回分解。

欢迎大家关注我的公众号"计算机基础爱好者"

谢谢

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值