插入、冒泡、归并、堆排序、快排总结

1、插入排序

这里写图片描述
这里写图片描述

    public static void insertionSort(int[] array) {
        int l = array.length;
        int j;
        for (int i = 1; i < l; i++) {
            int temp = array[i];
            //temp <= array[j - 1] 会因为等于多一次比较
            for (j = i; j > 0 && temp < array[j - 1]; j--)
                array[j] = array[j - 1];
            array[j] = temp;
        }
    }

空间消耗 O(1) (临时保存array[i])
平均时间复杂度 O(n^2)
最好情况 O(n) (已经排序,内层for循环的检测总是立即判断不成立而终止)
最坏情况 O(n^2) (需排序的为逆序)

如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。

(可以使用二分查找法进行优化)


2、冒泡排序

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

可以是将最小的冒上去,或者最大的沉下去。

这里写图片描述

//改进后的,增加了一个判断标志
public static void bubbleSort(int[] array) {
        boolean flag = true;
        //若flag 为false表明剩下的序列是有序的了
        for (int i = 0; i < array.length && flag; i++) {
            flag = false;
            for (int j = array.length - 1; j > i; j--) {
                if (array[j] < array[j - 1]) {
                    int temp = array[j];
                    array[j] = array[j - 1];
                    array[j - 1] = temp;
                    flag = true;//表明有数据交换
                }
            }
        }

空间消耗 O(1) (用于交换相邻数据)
平均时间复杂度 O(n^2)
最好情况 O(n) (改进后的,避免对已经有序的序列重复进行循环比较,未改进的为O(n^2) )
最坏情况 O(n^2) (需排序的为逆序)

当最好情况下,即需排序的数组本身是有序的,根据改进的代码。可以推断出有n-1次比较,没有数据交换。当最坏的时候,即需排序的数组本身是逆序的,此时需要比较n(n-1)/2次,并做等量数量级的记录移动。

3、归并排序

这里写图片描述

这里写图片描述

这里写图片描述

(1)递归实现
    public static void mergeSort(int[] array) {
        int[] tempArr = new int[array.length];
        mergeSort(array, tempArr, 0, array.length - 1);
    }

    private static void mergeSort(int[] array, int[] tempArr, int left, int right) {
        if (left < right) {
            int center = (left + right) / 2;
            //递归将左边的归并为有序
            mergeSort(array, tempArr, left, center);
            //递归将右边的归并为有序
            mergeSort(array, tempArr, center + 1, right);
            //将左右两个子序列归并到一起
            merge(array, tempArr, left, center + 1, right);
        }
    }

    private static void merge(int[] array, int[] tempArr, int leftPos, int rightPos, int rightEnd) {
        int leftEnd = rightPos - 1, tmpPos = leftPos, num = rightEnd - leftPos + 1;

        while (leftPos <= leftEnd && rightPos <= rightEnd) {
            if (array[leftPos] < array[rightPos]) tempArr[tmpPos++] = array[leftPos++];
            else tempArr[tmpPos++] = array[rightPos++];
//            tempArr[tmpPos++] = array[array[leftPos] < array[rightPos] ? leftPos++ : rightPos++];
        }


        while (leftPos <= leftEnd)
            tempArr[tmpPos++] = array[leftPos++];

        while (rightPos <= rightEnd)
            tempArr[tmpPos++] = array[rightPos++];

        for (int i = 0; i < num; i++, rightEnd--)
            array[rightEnd] = tempArr[rightEnd];
    }

空间消耗 O(n+log n)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n log n)
这里写图片描述

(2)非递归实现
    public static void mergeSort(int[] arr) {
        int len = arr.length;
        int k = 1;

        while(k < len)
        {
            mergePass(arr, k, len);
            k *= 2;
        }
    }

    //mergePass方法负责将数组中的相邻的有k个元素的序列进行归并
    private static void mergePass(int[] arr, int k, int n) {
        int i = 0;

        //从前往后,将2个长度为k的子序列合并为1个
        //n - 2*k + 1中加 1 的原因是数组的下表是从 0 开始的
        //且需要保证两两合并的序列(非落单的序列)长度为k
        while(i < n - 2*k + 1)
        {
            merge(arr, i, i + k-1, i + 2*k - 1);
            i += 2*k;
        }

        //这段代码保证了,将那些“落单的”长度不足两两merge的部分和前面merge起来。
        if(i < n - k )
        {
            merge(arr, i, i+k-1, n-1);
        }

    }

        //merge函数实际上是将两个有序数组合并成一个有序数组
        private static void merge(int[] arr, int low, int mid, int high) {
        //temp数组用于暂存合并的结果
        int[] temp = new int[high - low + 1];
        int i = low;
        int j = mid+1;
        int k = 0;

        //将记录由小到大地放进temp数组
        for(; i <= mid && j <= high; k++)
        {
            if(arr[i] < arr[j])
                temp[k] = arr[i++];
            else
                temp[k] = arr[j++];
        }

        //接下来两个while循环是为了将剩余的(比另一边多出来的个数)放到temp数组中
        while(i <= mid)
            temp[k++] = arr[i++];

        while(j <= high)
            temp[k++] = arr[j++];

        //将temp数组中的元素写入到待排数组中
        for(int l = 0; l < temp.length; l++)
            arr[low + l] = temp[l];
    }

空间消耗 O(n)

避免了递归时需要的深度为 log n 的栈空间

4、堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

在下面的篇博客中说得挺好的,具体的实现细节可以参考,但是是用JS实现代码的:
http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/

    public static void heapSort(int[] arr) {
        //构建初始最大堆
        for (int i = arr.length/2-1; i >=0; i--) {
            buildMaxHeap(arr,i,arr.length);
        }

        for (int i = arr.length-1; i > 0 ; i--) {
            //交换堆顶最大值和最后一个叶子结点
            int tmp = arr[0];
            arr[0] = arr[i];
            arr[i] = tmp;

            //重新构建剩下的序列的最大堆
            buildMaxHeap(arr,0,i);
        }
    }

    //构建最大堆
    public static void buildMaxHeap(int[] array,int index,int heapSize) {
        int iMax, iLeft, iRight;
        while (true) {
            iMax = index;
            iLeft = 2 * index + 1;
            iRight = 2 * (index + 1);

            if (iLeft < heapSize && array[index] < array[iLeft]) {
                iMax = iLeft;
            }
            if (iRight < heapSize && array[iMax] < array[iRight]) {
                iMax = iRight;
            }
            if (iMax != index) {
                int tmp = array[iMax];
                array[iMax] = array[index];
                array[index] = tmp;

                index = iMax;
            } else {
                break;
            }
        }
    }

空间消耗 O(1)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n log n)

堆排序复杂度分析:

    它的运行时间主要是消耗在初始构建堆和在重建堆时的反复筛选上。
    在构建堆的过程中,因为我们是完全二叉树从最下层最右边的非终端结点开始构建,将它与其孩子进行比较和若有必要的互换,对于每个非终端结点来说,其实最多进行两次比较和互换操作,因此整个构建堆的时间复杂度为O(n)。
    在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个结点到根结点的距离为⌊log2i⌋+1),并且需要取n-1次堆顶记录,因此,重建堆的时间复杂度为O(nlogn)。
    所以总体来说,堆排序的时间复杂度为O(nlogn)。由于堆排序对原始记录的排序状态并不敏感,因此它无论是最好、最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好过于冒泡、简单选择、直接插入的O(n2)的时间复杂度了。 
    空间复杂度上,它只有一个用来交换的暂存单元,也算是非常的不错。不过由于记录的比较与交换是跳跃式进行,因此堆排序也是一种不稳定的排序方法。
    另外,由于初始构建堆所需的比较次数较多,因此,它并不适合待排序序列个数较少的情况。 

5、快速排序

快速排序(Quicksort)是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

参考博客:http://bubkoo.com/2014/01/12/sort-algorithm/quick-sort/

    public static void quickSort(int[] arr) {
        sort(arr,0,arr.length-1);
    }

    public static void sort(int[] array,int left,int right) {
        if (left > right) {
            return;
        }
        int storeIndex = partition(array, left, right);
        sort(array, left, storeIndex - 1);
        sort(array, storeIndex + 1, right);
    }

    private static int partition(int[] array, int left, int right) {
        int storeIndex = left;
        int pivot = array[right]; // 直接选最右边的元素为基准元素
        for (int i = left; i < right; i++) {
            if (array[i] < pivot) {
                swap(array, storeIndex, i);
                storeIndex++; // 交换位置后,storeIndex 自增 1,代表下一个可能要交换的位置
            }
        }
        swap(array, right, storeIndex); // 将基准元素放置到最后的正确位置上
        //之后,以基准元素为分界点,左边是小于它的,右边是大于等于它的

        return storeIndex;
    }

    private static void swap(int[] array, int i, int k) {
        int temp = array[i];
        array[i] = array[k];
        array[k] = temp;
    }

空间消耗 O(log n)
平均时间复杂度 O(n log n)
最好情况 O(n log n)
最坏情况 O(n^2)

最坏情况发生在每次划分过程产生的两个区间分别包含n-1个元素和1个元素的时候(设输入的表有n个元素)。
最好情况为如果每次划分过程产生的区间大小都为n/2。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值