算法,排序部分总结

1、冒泡排序

时间复杂度也是\(O(N^2)\)

排序算法:

每次节点跟他的右边相联的节点做比较,如果左边节点较大则做交换一共执行\(N^2\)

public static void sort(int[] nums) {
    for (int i = 0; i < nums.length - 1; i++) {
        for (int j = 0; j < nums.length - 1; j++) {
            int temp = nums[j];
            if (nums[j] > nums[j + 1]) {
                nums[j] = nums[j + 1];
                nums[j + 1] = temp;
            }
        }
    }
}

2、选择排序

时间复杂度为\(O(N^2)\)

第i次循环都在遍历找寻第i小的节点,最后在替换第i个节点和找到的第i小的节点的值做交换,需要执行\(N^2\)次,

function sort(int[] arr) {
    int len = arr.length;
    int minIndex、 temp;
    for(int i = 0; i < len - 1; i++) {
        minIndex = i;
        for(varj = i + 1; j < len; j++) {
            if(arr[j] < arr[minIndex]) {     // 寻找最小的数
                minIndex = j;                 // 将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    returnarr;
} 

3、插入排序

  • 第一个元素被认为是已经排序的数据,从第二个元素开始排序
  • 取下一个元素,在已经排序的元素中 从后往前扫描
  • 如果该元素大于前面的元素则扫描结束,继续下一个元素的插入
  • 如果该元素小于前面的元素则继续扫描,一直找到第一个小于该元素的位置,将此元素插入到该位置的后面
  • 继续下一个元素的排序

时间复杂度\(O(N^2)\),插入排序在数据量较少的时候有着较短的消耗时间。

1、 交换法

该方法是在排序第J个数据的时候,对比前一个数,如果第j个数小于前一个数则跟前一个数做交换,一直遍历到前一个数大于该数,这时第j个数据已经排好序了。

    /*
     *  入排序(交换)
     */
    public static void sort(int[] nums) {
        for (int i = 1; i < nums.length; i++) { // 第一个数肯定已经是有序的
            for (int j = i; j > 0; j--) {
                if (nums[j] < nums[j - 1]) { // 如果该数比之前的数要小则一直跟前一个数交换,直到大于前一个数
                    int temp = nums[j];
                    nums[j] = nums[j - 1];
                    nums[j - 1] = temp;
                } else {
                    break;
                }
            }
        }
    }

2、 优化后的复制法

上一个交换法中频繁的交换数据会带来时间的开销,所以采用复制法可以进一步的优化:

记录插入点的值x,和正在遍历的节点序号j

在选择插入点的时候,如果前一个节点的值大于当前节点,则将当前节点的值赋值为前一个节点的值

一直遍历到前一个节点的值小于当前节点的值时,跳出循环

这个时候节点序号j就是值x该插入的序号位置

    /*
     *  改进的插入排序(复制法)
    */
    public static void sort(int[] nums) {
        for (int i = 1; i < nums.length; i++) { // 第一个数肯定已经是有序的
            int sortValue = nums[i]; // 当需要排序索引对应的值
            int j ; // 最终得到的是sortValue需要放入的位置
            for (j = i; j > 0; j--) {
               if (nums[j-1] > sortValue) {
                   nums[j] = nums[j-1];
               }else {
                   break;
               }
            }
            nums[j] = sortValue;
        }
    }

4、希尔排序

1024555-20161128110416068-1421707828

时间复杂度:\(O(N^2)\)

相对于插入排序的一种改进,插入排序是每次默认前面的数据都是有序的从而找到可以被插入的地方,希尔排序将数组分割成若干个子数组,将子数组变成有序的,最终子数组数为1时,整体有序

 public static void shellSort(int[] arrays) {
        int n = arrays.length;
        n = n / 2;
        while (n >= 1) {
            for (int i = n; i < arrays.length; i += 1) {
                for (int j = i; j - n >= 0; j -= n) {
                    if (arrays[j] < arrays[j - n]) {
                        // 替换
                        int temp = arrays[j];
                        arrays[j] = arrays[j - n];
                        arrays[j - n] = temp;
                    } else {
                        break;
                    }
                }
            }
            // 继续分
            n = n / 2;
        }
    }

5、归并排序

时间复杂度:\(O(N^2)\)

1、自顶向下的方式:

image-20201129164142689

归并排序使用的就是分治思想。分治,顾名思义,就是分而治之,将一个大问题分解成小的子问题来解决。小的子问题解决了,大问题也就解决了。

可以通过递归的编程模式来完成归并排序算法:

sort(int[] nums、int l、int r) 排序nums数字中下标从l到r区域内的数据

  • 如果l的位置大于等于r的位置说明该区域内不需要排序,直接返回
  • 获取l。。。r中间的一个下标 midIndex = (l+r)/2;
  • 最后通过一个排序排序算法(partition()),来排序[l、midIndex]和[midIndex+1、r]的有序数组

partition算法:

  • 创建一个r-l+1大小的临时数组temps,将nums数组中从小标l开始到r结束的数组元素全部拷贝赋值到临时数组temps中。
  • 创建两个缩印指针,i和j 代表下一个需要比较的索引。i指向数组中l位置,j指向数组中的第二块区域的开始位置
  • 从l位置开始遍历到r位置(遍历的指针为n),判断nums[i]和nums[j]的大小,如果i索引位置的数据较小则赋值将num[n]=num[i]、且将i指针后移一位,反之将num[n] = num[j] 将j索引后移一位。
  • 需要考虑一点,如果i或者j大于他们的索引域的时候,就代表剩下所有的数组单元可以用另一个没有过域的数组的元素

image-20201129170048340

	/**
     *  自顶向下法归并排序算法
     * @param nums
     * @param l
     * @param r
     */
    public static void sort(int[] nums、 int l、 int r) {
        if (l >= r) {
            return;
        }
        int q = (l + r) / 2;
        sort(nums、 l、 q);
        sort(nums、 q + 1、 r);
        merge(nums、 l、 q、 r);
    }

	/**
	* 合并两个有序的数组
	* num 需要合并的数组
	* l 区域1的开始索引值
	* q 区域1的结束索引值
	* r 区域2的结束索引值
	*/
    public static void merge2(int[] nums、 int l、 int q、 int r) {
        //先拷贝到临时数组中去
        int[] tempArray = new int[r - l + 1]; // 【】区间
        for (int i = l; i <= r; i++) {
            tempArray[i - l] = nums[i];
        }
        int i = l; // 左边域待比较节点指针
        int j = q + 1; // 右边域待比较节点指针
        for (int n = l; n <= r; n++) {
            if (i > q) { // 如果左边域已经全部排完了,则剩下的元素都是从右边域取
                nums[n] = tempArray[j - l];
                ++j;
                continue;
            }
            if (j > r) {// 如果右边域全部拍完则剩下值从左边取值
                nums[n] = tempArray[i - l];
                ++i;
                continue;
            }
            if (tempArray[i - l] < tempArray[j - l]) { //如果序号i对应的元素值小于序号j对应元素的值、则将num[n]的值赋值为临时数组中的i-l位置的值
                nums[n] = tempArray[i - l];
                i++;
            } else {
                nums[n] = tempArray[j - l];// 跟上面类似
                j++;
            }
        }
    }

2、自底向上的方式:

首先我们把数组中的每个元素看成是长度为一的数组,然后进行两两归并,这样就可以得到一个从 0 处索引开始每相邻两个元素都有序的数组(注意并不是任意相邻)。然后我们进行四四归并(将从 0 开始的每四个相邻元素进行归并,因为四个元素的左右是有序的)。然后是八八归并。一直持续下去即可。在每一轮的归并中,可能需要归并的左右数组大小并不相等,但这不是问题,只要在实现归并的方法中改变传入的 mid 参数即可

image-20201129183300212

	/**
     * 自底向上排序
     * @param nums
     */
    public static void sort3(int[] nums) {
        for (int size = 1; size <= nums.length; size = 2 * size) { // 每次排序的组大小(2,4,8....)
            for (int i = 0; i + size < nums.length; i += 2 * size) {// i为每次排序的数组开始序号
                merge(nums、 i、 i + size - 1、 Math.min(i + 2 * size - 1、nums.length -1));// 相邻的两个区域排序
            }
        }
    }

6、快速排序

时间复杂度\(O(NLogN)\)

1、 算法描述

同样是通过分治的思想来对数据进行排序,将一个数组分成两块

  • 从数组中调一个元素,称之为基准元素
  • 对基准元素进行排序,使在定义范围内的基准元素左侧的数据都小于它,右侧的数据都大于他
  • 递归的调用排序方法,来对左领域和右领域的数据进行排序

2、 单路快排

N路快排,这个N就是取决于partition函数是如何进行分区的。

单路快排中,设置一个元素为基准元素,简单起见设置域起始元素为基准元素,再设置一个指针P,作为小于基准数据的索引,那么就从域第二个元素到最后一个元素进行遍历,如果找到某个元素小于基准元素的值时,将p指针指向的数据和该元素进行交换。

遍历完成后,将基准元素和p-1(p的位置是下一个需要交换的位置,所以可以保证p-1的元素都是小于基准数据的元素)的位置进行交换。

image-20201130090742505

    public static void sort(int[] nums、 int l、 int r) {
        if (l >= r) {	// 递归结束点
            return;
        }
        int q = partition(nums、 l、 r); // 查找基准点
        sort(nums、 l、 q - 1); // 递归左区间
        sort(nums、 q + 1、 r); // 递归右区间
    }
	/**
     * 单路快排
     *
     * @param nums 需要分区的数组
     * @param l 开始索引
     * @param r 结束索引
     * @return 基准元素的位置索引
    **/
    public static int partition(int[] nums、 int l、 int r) {
        int baseIndex = (new random()).nextInt() % (r) - l; // 随机获取一个位置的值去作为排序的基准点,防止都是已经排序完成的元素
        int p = l + 1; // 指针p指向下一个可以交换的元素,保证[l...p-1]区间的数据都小于nums[p]
        for (int i = p; i <= r; i++) { // 对整个区间进行遍历
            if (nums[i] < nums[baseIndex]) { // 如果发现元素是小于基准元素的,则和p指针指向的地址做交换
                SortHelper.swap(nums、 i、 p);
                p++;// 指针移向下一个位置
            }
        }
        SortHelper.swap(nums、 p - 1、 baseIndex);// 将基准元素移动到属于他的位置
        return p - 1;// 返回基准元素的位置
    }

3、 双路快排

将大于和小于 v 的元素放在数组的两端,引用新的索引 j 记录大于 V 的边界位置,i的值为最后一个大于基准元素值的位置,j的值为最后一个大于基准元素的位置,i和j元素同时从两边开始遍历,当i遍历到一个大于v元素的位置停下,j遍历到小于v元素的位置停下 ,两个位置进行交换。然后继续进行循环,直到i大于j

image-20201130091004638

    public static void sort(int[] nums、 int l、 int r) {
        if (l >= r) {	// 递归结束点
            return;
        }
        int q = partition(nums、 l、 r); // 查找基准点
        sort(nums、 l、 q - 1); // 递归左区间
        sort(nums、 q + 1、 r); // 递归右区间
    }	
	/**
     * 双路快排
     *
     * @param nums 需要分区的数组
     * @param l 开始索引
     * @param r 结束索引
     * @return 基准元素的位置索引
     */
    public static int partition2(int[] nums、 int l、 int r) {
        int anchor = nums[l];
        int i = l;// 最后一个小于anchor元素的位置
        int j = r+1; // 最后一个大于anchor元素的位置
        while (true) {
            while (i <= j && anchor >= nums[++i]); // 从左边开始一直遍历,直到找到一个元素大于基准值
            while (j > l && anchor <= nums[--j]); // 从右边遍历,直到找到一个元素小于基准值
            if (i > j) break;// 跳出循环条件
            SortHelper.swap(nums、 i、 j);
        }
        SortHelper.swap(nums、 l、 j);// j位置对应的元素是小于基准值的
        return j;
    }

4、 三路快排

三路快排的思想是:

  • 将整个数组域划分为三块:小于选定基准元素的,等于基准元素的,大于基准元素的区间,在递归中如果遇到了等于基准元素的值时不处理。image-20201130135317972
  • 当元素 e 等于 v 的时候直接纳入绿色区域之内,然后 i++ 处理下一个元素。如图:
  • 当元素 e 小于 v 的时候,只需要将元素 e 与等于 e 的第一个元素交换就行了,这和刚开始讲的快速排序方法类似。同理,当大于 v 的时候执行相似的操作
    public static void sort(int[] nums、 int l、 int r) {
        if (l >= r) {	// 递归结束点
            return;
        }
        int q = partition(nums、 l、 r); // 查找基准点
        sort(nums、 l、 q - 1); // 递归左区间
        sort(nums、 q + 1、 r); // 递归右区间
    }	
    /**
     * 三路快排
     *
     * @param nums 需要分区的数组
     * @param l 开始索引
     * @param r 结束索引
     * @return 基准元素的位置索引
     */
    public static void sort3(int[] nums、 int l、 int r) {
        if (l >= r) {
            return;
        }
        int anchor = nums[l];// 定义基准元素的值和位置
        // partition
        int lt = l;//[l+1.....lt] < nums[r]
        int gt = r + 1; // [gt.......、r] > nums[l]
        int i = l + 1;// 遍历数组的开始序号
        while (i < gt) {
            if (nums[i] > anchor) {
                SortHelper.swap(nums、 i、 gt - 1);
                gt--;
            } else if (nums[i] < anchor) {
                SortHelper.swap(nums、 i、 lt + 1);
                lt++;
                i++;
            } else {
                i++;
            }
        }
        SortHelper.swap(nums、 l、 lt);
       
        sort3(nums、 l、 lt - 1);
        sort3(nums、 gt、 r);
    }

7、堆排序

首先对于给定数组进行最大堆构建:

​ 将元素和其父节点进行比对,如果元素大于父节点值则进行一次交换,然后再判断父节点值是否大于父节点的父节点值,依次进行判断,直到某个节点小于等于他的父节点值或当前节点就是根节点,那么此时这个数组就是一个最大堆。

将最大堆第一个元素和末尾元素进行交换,然后将最大堆的大小减一,将接下来的元素再进行堆化。堆化成功后继续交换元素。直到数组全部有序。

如何进行堆化呢?

构建最大堆时,是向上进行比对交换,堆化是向下进行交换比对,判断元素是否有大于此元素的子节点,如果有则进行数据交换,然后再判断交换后的数据是否还有比此元素大的子元素,依次类推。直到元素到达叶子节点或子元素都小于等于他

	 /**
     * 初始化最大堆
     *
     * @param a 数组数据
     */
    private static void initialBigHeap(int[] a) {
        for (int i = 0; i < a.length; i++) {
            int currentIndex = i;
            int parentIndex = (currentIndex - 1) / 2;
            while (a[parentIndex] < a[currentIndex]) {
                SortHelper.swap(a、 parentIndex、 currentIndex);
                currentIndex = parentIndex;
                parentIndex = (currentIndex - 1) / 2;
            }
        }
    }

	/**
     * 堆化
     *
     * @param a 数组
     * @param size 堆的大小
     */
    private static void heapful(int[] a、 int size) {
        // 当前节点
        int currentIndex = 0;
        // 左节点
        int left = 1;
        // 右节点
        int right = left + 1;
        while (left < size) {
            int bigIndex = 0;
            if (a[left] < a[right] && right < size)
                bigIndex = right;
            else
                bigIndex = left;
            if (a[bigIndex] > a[currentIndex]) {
                SortHelper.swap(a、 bigIndex、 currentIndex);
                currentIndex = bigIndex;
                left = currentIndex * 2 + 1;
                right = left + 1;
            } else
                break;
        }
    }

    public static void heapSort(int[] a) {
        int size = a.length;
        initialBigHeap(a);
        SortHelper.swap(a、 0、 --size);
        while (size > 0) {
            heapful(a、 size);
            SortHelper.swap(a、 0、 --size);
        }
    }
posted @ 2021-08-27 15:10  程序木虫  阅读(56)  评论(0)    收藏  举报