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

来源:互联网 发布:福利直播软件 编辑:程序博客网 时间:2024/05/17 06:44

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。
阅读全文
0 0
原创粉丝点击