zoukankan      html  css  js  c++  java
  • 各大排序算法的分析与实现以及时间复杂度

    时间复杂度:

    时间复杂度是一个算法流程中,常数操作数量的指标。常用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;
        }

    排序算法的稳定性

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

    冒泡排序:稳定

    选择排序:不稳定

    插入排序:稳定

    归并排序:稳定

    快速排序:不稳定

    堆排序:不稳定

  • 相关阅读:
    关于service相关知识的认识
    如何在service实现弹出对话框
    NDK编程jni学习入门,声明native方法,使其作为java与c的交互接口
    js事件
    es6箭头函数
    es6展开运算符
    es6 解构赋值
    js 函数的this指向
    js函数作用域
    js 预解析以及变量的提升
  • 原文地址:https://www.cnblogs.com/lzxin/p/10030433.html
Copyright © 2011-2022 走看看