各大排序算法的分析与实现以及时间复杂度

时间复杂度:

时间复杂度是一个算法流程中,常数操作数量的指标。常用O表示。在常数操作数量的表达式中,只要高阶项,不要低阶项,也不要高阶项系数,剩下的部分如果记为f(n),那么时间复杂度就是O(f(n))。

 

一、冒泡排序


思想:n个数一一对比之后找出最大的,再在剩下的n-1个数中一一对比找出第二大的,以此类推。

时间复杂度:O(n^2)

实现代码:

 

    public static void bubbleSort(int[] arr) {
        for (int i = arr.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                //如果前一个数比后一个数大就交换
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

 

 

二、选择排序

思想:n个数中第一个数与后面的数字比较,若有更小的就交换,从而让第一个数字为最小值,接着再处理剩下的n-1个数,以此类推。

时间复杂度:O(n^2)

实现代码:

 

    public static void insertionSort(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                //如果有小于min的值,就改变min的下标
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            swap(arr, i, minIndex);
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

 

 

三、插入排序

思想:第二个数先与第一个数比较,形成有序序列。接着第三个数和前两个形成有序序列的数字比较,找到合适的位置插入。以此类推。

最好的时间复杂度:O(n)

最坏的时间复杂度:O(n^2)

实现代码:

 

    public static void insertionSort(int arr[]) {
        //从第二个数开始插入
        for (int i = 1; i < arr.length; i++) {
            //该数与前面形成的有序序列进行比较,该数较小的话就交换,直到插入合适位置,较大的话就不交换
            for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

 

 

四、归并排序

思想:采用分治的思想,将n个元素的数组切成一半,每一半再分别排序,最后将两半已经排序的数组合并。

时间复杂度:O(n*logn)

空间复杂度:O(n)

实现代码:

 

    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) return;
        mergeSort(arr, 0, arr.length - 1);
    }

    public static void mergeSort(int[] arr, int index, int end) {
        if (index == end) return;
        int mid = index + (end - index) / 2;
        mergeSort(arr, index, mid);
        mergeSort(arr, mid + 1, end);
        //合并两个数组
        merge(arr, index, mid, end);
    }

    public static void merge(int[] arr, int index, int mid, int end) {
        //构造辅助数组
        int[] help = new int[end - index + 1];
        int i = 0;
        int p1 = index;
        int p2 = mid + 1;
        //哪个比较小就放入辅助数组,并且移动指针
        while (p1 <= mid && p2 <= end) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        //说明p2指针移动完毕,只剩p1
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        //说明p1指针移动完毕,只剩p2
        while (p2 <= end) {
            help[i++] = arr[p2++];
        }
        //将辅助数组的元素复制回原来数组
        for (int j = 0; j < help.length; j++) {
            arr[index + j] = help[j];
        }
    }

 

 

 

五、快速排序(改进后的)

思想:把数组中的最后一个元素作为分界量,也就是基准点,排成左边比它小的,中间等于它的,右边比它大的。再递归调用分别排左边的和右边的。

最好的时间复杂度:O(n*logn)

最坏的时间复杂度:O(n^2)

长期期望的时间复杂度:O(n*logn)

长期期望的额外空间复杂度:O(logn)

实现代码:

    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) return;
        quickSort(arr, 0, arr.length - 1);
    }

    public static void quickSort(int[] arr, int index, int end) {
        if (index < end) {
            //swap(arr, l + (int) (Math.random() * (r - l + 1)), r);(这一句加上就是随机快速排序,暂时不加)
            //p数组代表的是等于划分基准点的元素的初始和结束位置
            int[] p = partition(arr, index, end);
            quickSort(arr, index, p[0] - 1);
            quickSort(arr, p[1] + 1, end);
        }
    }

    //下面是以数组的最后一个元素作为基准点的,这个方法完成的结果就是小于基准点放左边,等于基准点放中间,大于基准点放右边
    public static int[] partition(int[] arr, int index, int end) {
        //less指针用来划分小于基准点的范围
        int less = index - 1;
        //more指针用来划分大于基准点的范围
        int more = end;
        while (index < more) {
            //如果当前元素小于基准点的元素,就让它与less指针的前一个数字交换,并移动less指针,移动index指针操作下一个数
            //如果当前元素大于基准点的元素,就让它与more指针的前一个数字交换,并移动more指针
            //如果当前元素等于基准点的元素,移动index指针操作下一个数
            if (arr[index] < arr[end]) {
                swap(arr, index++, ++less);
            } else if (arr[index] > arr[end]) {
                swap(arr, index, --more);
            } else {
                index++;
            }
        }
        //由于基准点的元素一直没有动过,最后要让它和大于基准点的元素交换
        swap(arr, more, end);
        //返回等于基准点元素的下标
        return new int[]{less + 1, more};
    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

六、堆排序

思想:先让数组的n个元素依次进堆,构建出大顶堆。然后交换顶堆的元素和最末尾的元素(最末尾的元素不一定最小,但是一定小于等于堆顶的元素),然后让剩下的n-1元素进行调整,重新调整为大顶堆。然后在进行交换,再调整,以此类推。

时间复杂度:O(n*logn)

实现代码:

 

    public static void heapSort(int[] arr) {
        if (arr == null || arr.length < 2) return;
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr, i);
        }
        int size = arr.length;
        swap(arr, 0, --size);
        while (size > 0) {
            heapify(arr, 0, size);
            swap(arr, 0, --size);
        }
    }

    //建立大顶堆的过程
    public static void heapInsert(int[] arr, int index) {
        //如果当前值大于父节点的话,与父节点交换
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //重新调整成为大顶堆的过程
    public static void heapify(int[] arr, int index, int size) {
        //取该结点的左结点
        int left = index * 2 + 1;
        //如果左结点在size范围内
        while (left < size) {
            //从左右结点中选出最大的,赋给largest
            int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
            //判断当前结点和左右结点的最大值比较,如果当前结点大就维持原样(因为调整到index的结点可能比左结点的值要大的)
            if (arr[largest]<arr[index]) {
                break;
            }
            //交换index和largest结点的值
            swap(arr, largest, index);
            //让index指针移动到largest结点位置
            index = largest;
            //取index的左结点
            left = index * 2 + 1;
        }

    }

    public static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

 

排序算法的稳定性

排完顺序之后不会改变相同的原数字在原来数组中的相对次序,称为稳定。反之为不稳定

冒泡排序:稳定

选择排序:不稳定

插入排序:稳定

归并排序:稳定

快速排序:不稳定

堆排序:不稳定

posted @ 2018-11-28 09:50  professorxin  阅读(728)  评论(0编辑  收藏  举报