数组的排序 七大排序

七大排序

  • 交换排序
  1. 冒泡排序
  2. 快速排序
  • 插入排序
  1. 直接插入排序
  2. 希尔排序
  • 选择排序
  1. 直接选择排序
  2. 堆排序
  • 归并排序

1、冒泡排序

每次比较相邻的两个元素,将较大的元素后移,每当将数组的数比较一次,最大的数都会出现在数组的最后,再对除去最后面数的前面的数进行重复操作,操作arr.length-1次即可

代码如下:

private 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);

            }

        }
    }

}


private static void swap(int[] arr, int a, int b){
    
    int temp = arr[a];
    arr[a] = arr[b];
    arr[b] = temp;
    
}

2、快速排序

随机选择一个基准(一般选择第一个数),再设置两个辅助“指针”:i,j,i是第一个元素的index,j为最后一个元素的index。首先j开始向前移动,直到找到第一个比基准小的数index为止,然后i向后移动,直到找到第一个比基准大的数的index为止,交换i,j位置的元素;再重复上述操作,直到i>=j为止,此时将基准的值与i位置的值交换。则左边的都是比基准小的值,右边都是比基准大的值。再对左右两边进行同样的操作。

代码如下:

/**
 *
 *  快速排序:选择一个元素作为基准(一般选择第一个元素),begin指向开头,end指向末尾
 *           end负责找出比基准小的第一个元素,找出后,begin找出第一个比基准大的元素
 *           交换两个元素,以此类推,直到begin和end碰到,交换基准与该位置的元素;
 *           此时,该位置的左边是比基准小的元素,右边是比基准大的元素;以该位置作为
 *           分隔点,对左边和右边进行相同的操作;当begin大于等于end的时候,跳出递归
 *
 * @param arr   目标数组
 * @param begin 开始位置
 * @param end   结束位置
 */
private static void quickSort(int[] arr, int begin, int end){

    if (begin >= end){

        return;

    }
    
    int standard = arr[begin];
    int i = begin;
    int j = end;
    
    while (i < j){
        
        while (i < j && arr[j] >= standard){
            
            j--;            

        }

        while (i < j && arr[i] <= standard){
            
            i++;            

        }
        
        if (i < j){
            
            swap(arr, i, j);            

        }
        
    }
    
    swap(arr, begin, i);
    
    quickSort(arr, begin, i-1);
    qucikSort(arr, i+1, end);

}

3、直接插入排序

从无序序列向有序序列里面插入元素,一开始先选择数组的第一个元素作为有序序列,从第二个元素往后是无序序列,从无序序列逐个拿出元素插入到有序序列里面就是直接插入排序。

有序区间[0,i);无序区间[i,arr.length); i:1 -- > arr.length-1

代码如下:

private static void insertSort(int[] arr){

    // 有序区间是[0,i)
    // 无序区间是[i,arr,length)
    // 初始化有序区间就是[0,1),无序区间是[1,arr.length)
    
    for (int i = 1; i <= arr.length - 1; i++){
       
        int v = arr[i]; // 取无序区间的第一个数,分别和有序区间的数进行倒序比较
        int j = i - 1; // 有序区间的最后一个数的下标

        // 不写 arr[j] == v 是保证排序的稳定性
        while (j >=0 && arr[j] > v){ 

            // 如果j有效且有序区间的最后一个值大于无序的第一个值,将有序的最后一个值后移
            arr[j+1] = arr[j];
            // 有序index前移,无序的最后一个值和有序的倒数第二个值进行比较
            j--;
            
        }
        // 如果发生了移动,就将无序的第一个值放在恰当的地方
        if (j+1 != i){
            
            arr[j+1] = v;    
    
        }
        
    }

}

4、希尔排序

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成若干个组,所
有距离为gap的记录分在同一组内,并对每一组内的记录进行排序。然后取下一个gap,重复上述分组和排序的工作。当到达gap=1
时,所有记录在统一组内排好序。一般首先取gap初始值为gap = arr.length/2,然后 gap /= 2,直到gap=1停止。
1. 希尔排序是对直接插入排序的优化。
2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很
快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

代码如下:

/**
 *   希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,
 *   把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,
 *   并对每一组内的记录进行排序。然后重复上述分组和排序的工作。当到达gap=1
 *   时,所有记录在统一组内排好序。
 *   1. 希尔排序是对直接插入排序的优化。
 *   2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,
 *   数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。

 * @param arr 目标数组
 */

private static void shellSort(int[] arr){

    for (int gap = arr.length / 2; gap >= 1; gap /= 2){ // gap是间隔的大小

        for (int i = 1; i < arr.length; i++){ // 从下标为1的元素开始找前面距离它为gap的元素

            int v = arr[i]; // 令v等于当前位置的元素
            int j = i - gap; // j为距离当前元素为gap的前面的第一个元素的index    
            for (; j >= 0 && arr[j] > v; j -= gap){ // 当j>=0的时候,该index有效
                                           // 在index有效的前提下,若前面的元素大于当前的元素

                arr[j+gap] = arr[j]; // 将前面的元素放到当前位置

                // 然后 j=j-gap,再找更前面的距离两个,三个,四个gap的元素的index。
                // 直到index的值小于0或者前面的小于后面的值,跳出循环
                // 因为gap的起始值为arr.length / 2,所以最开始一定只有两个元素比较
                // 所以在比它小的值开始,前面的值一定是小于它的,就是直接插入排序,
                // 前面的一定是有序数组

            }
            if (j+gap != i){// 如果前面第一个值就比该位置的值小,那就不用动,此时 j+gap = i
                            // 只有当发生一次替换的时候,才需要将当前位置的值放到前面的位置

                arr[j+gap] = v;
    
            }

        }        

    }

}

5、直接选择排序

每次从数组里面找出最大(小)的值,用minIndex或者maxIndex记录该值的下标,将该值放在数组的末尾(开头)。

代码如下:

//每次选出最大的数往后放
private static void selectMaxSort(int[] arr){

    for (int i = arr.length-1; i > 0; i--){

        int maxIndex = i;

        for (int j = i - 1; j >= 0; j--){

            if (arr[maxIndex] < arr[j]){

                maxIndex = j;
                    
            }

        }
        if (maxIndex != i){

            swap(arr, maxIndex, i);

        }

    }

}
// 每次找出最小的数往前放
private static void selectMinSort(int[] arr){

    for (int i = 0; i < arr.length - 1; i++){

        int minIndex = i;
        
        for (int j = i + 1; j <= arr.length - 1; j++){

            if (arr[minIndex] > arr[j]){

                minIndex = j;

            }

        }
        if (minIndex != i){

            swap(arr, minIndex, i);
    
        }

    }

}

6、堆排序

创建一个大顶堆(升序)或者小顶堆(降序),每次调整大顶堆都会使最大的元素的出现在堆顶,也即是arr[0],然将该元素和数组最后的结点交换,也就是数组的尾部,再对前面的结点再次进行调整。

父节点和孩子结点的关系是:父节点 i ,左孩子 2 * i + 1,右孩子 2 * i + 2。

代码如下:

// 向下调整大顶堆
private static void heapify(int[] arr, int root, int len){

    int parent = root; // 初始化时,父节点从最后一个非叶子结点开始,初始值为:(arr.length-2)/2
    int left = 2 * parent + 1; // 左孩子

    while (left < len){ // 如果存在做左孩子

        if (left + 1 < len && arr[left + 1] > arr[left]){// 如果存在右孩子且右孩子大于左孩子
            
            left++; // left自加一,为右孩子的index,就是left是两个孩子的最大值的下标

        }

        if (arr[parent] < arr[left]){ // 如果孩子的最大值大于父节点
        
            swap(arr, parent, left);  // 交换父节点与孩子结点
            parent = left; // 再令父节点为交换后的孩子结点
            left = 2 * parent + 1;//目的是为了对交换后的进行再排序,以免交换破坏下面堆的结构
        
        }else{ // 如果孩子的最大值小于父节点,直接跳出循环,不需要调整该节点

            break;

        }
    }
}

// 初始化生成一个大顶堆
private static void createMaxHeap(int[] arr){

    int len = arr.length;    

    for (int i = (len - 2) / 2; i >= 0 ; i--){ // 一定要倒序生成大顶堆

        heapify(arr, i, len);
    
    }

}

// 堆排序
private static void heapSort(int[] arr){

    int end = arr.length - 1; // 记录数组尾下标
    createMaxHeap(arr); // 初始化大顶堆

    while (end > 0){ // 当end为1的时候,也就是交换了0,1的值,
                     // 这个时候的数组就是有序数组,跳出调整,
                    //也即是长度为len的数组排序的过程中需要创建len-1个大顶堆
                    //即找出len-1个最大数

        swap(arr, 0, end); // 交换大顶堆顶部的值和数组末尾的值,即将最大值后置
        heapify(arr, 0, end);// 将大数后置的大顶堆再次进行调整,就相当从root=0发生从
                            //root=0开始向下调整即可,不需要像初始化大顶堆那样倒序调整
                             // 第三个参数是数组的长度,因为最后一个数已经是最大的值,
                             // 不需要再参与排序,而且end的初始值就是len-1,
                            // 所以只需要操作原数组的len-1个值,即原数组的前end个元素即可。
        end--; // 调整后end自减1,为了使下次操作的元素个数减1

    }

}

7、归并排序

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

将数组划分为最小的块,起始有序,将融合两个有序的块,最后使整个数组有序。属于外排序。

代码如下:

// 融合两个有序的数组,大数组为[low,high)。
// 譬如{1,3,5,7,2,4,6},low = 0, mid = 4, high = 7,融合[0, 4) [4,7)
/**
 *
 * 合并两个有序的列表  [[low,mid),[mid,high)] 闭开区间
 *
 * @param arr   目标数组为从mid位置开始,前后分别为有序的两个数组合成的一个数组
 * @param low   从那一位开始进行比较融合,下标从0开始
 * @param mid   前后两个列表的分界点
 * @param high  后者数组的高位,是你想合并到的数的下标+1的值,因为是闭开区间
 */
private static void merge(int[] arr, int low, int mid, int high){

    int i = low; // 从哪里开始排序
    int j = mid; // 两个数组的分界点
    int k = 0; // 给extra添加元素的时候用到
    int[] length = high - low; // 数组的长度
    int extra = new int[length]; // 新建一个拷贝用的数组

    while (i < mid && j < high){ // i和j分别为两个数组的“指针”

        // 加入等于,保证稳定性
        if (arr[i] <= arr[j]){ // 前面的小
            
            extra[k++] = arr[i++];  // extra保存前面的,i后移,k后移

        }else{ // 后面的小

            extra[k++] = arr[j++];  // extra保存后面的,j后移,k后移

        }
       

    }

    while (i < mid){ // 前面的数组长
        
        extra[k++] = arr[i++]; // 直接将剩下的数放到extra里面

    }

    while (j < high){ // 后面的数组长
        
        extra[k++] = arr[j++]; // 直接将剩下的数放到extra里面

    }

    for (int index = 0; index < length; index++){

         // 需要搬回原位,从low开始
         arr[low + index] = extra[index];

    }
}

// 划分待排序的区间[low, high)
/**
 * 如果区间只剩下一个元素,那么会直接return,而不执行merge();
 * 当区间剩下两个元素的时候,再此划分会只剩一个元素,两个mSI都会跳出,然后执行meger()
 * 方法,执行完成两个的时候,会跳出当前的递归,执行三个或者四个元素的方法,执行merger()
 * 方法,直到将整个数组按照顺序merge
 *
 * @param arr  目标数组
 * @param low  起始位置
 * @param high 结束位置
 */
private static void mergeSortInternal(int[] arr, int low, int high){

    //System.out.println("进入区间方法");
    if (low == high-1){ // 当区间里面的元素划分到只剩下一个元素的时候,跳出递归
        //System.out.println("跳出递归");
        return; // 区间剩下一个元素会在此处跳出
    }

    int mid = (low + high) / 2; // low + high 才能让后面序列的起始值从数组的头开始算起
    
    mergeSortInternal(arr, low, mid); // 递归前半区间
    mergeSortInternal(arr, mid, high); // 递归后半区间

    //System.out.println("区间是:[" + low + ", " + high + ")");
    merge(arr, low, mid, high); // 区间剩下俩个以上元素,会执行完成merge()之后跳出
    //System.out.println("执行完毕merge");

}
// 二路归并算法
private static void mergeSort(int[] arr){

    mergeSortInternal(arr, 0, arr.length);

}

分析划分待排序区间递归的过程

与方法的实际输出顺序相同:

非递归的版本:

public static void mergeSort(int[] array) {

    for (int i = 1; i < array.length; i = i * 2) {

        for (int j = 0; j < array.length; j = j + 2 * i) {

            int low = j;
            int mid = j + i;

            if (mid >= array.length) {
                continue;
            }

            int high = mid + i;

            if (high > array.length) {
                high = array.length;
            }

            merge(array, low, mid, high);
        }
    }
}

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 使用冒泡排序算法,可以将10个整数按照从小到大的顺序排列。排序前的数组为:[5, 7, 4, 8, 10, 1, 3, 2, 6, 9];排序后的数组为:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。 ### 回答2: 冒泡排序算法是一种简单的排序算法,它重复地遍历要排序数组,比较每对相邻的元素,并按照从小到大的顺序进行交换,直到整个数组都被排序。下面是将10个整数按照从小到大的顺序进行冒泡排序的过程: 初始数组为:[10, 7, 8, 3, 1, 6, 5, 4, 9, 2] 第一轮冒泡排序: 比较相邻的两个元素,若前者大于后者则交换两个元素的位置。 [7, 8, 3, 1, 6, 5, 4, 9, 2, 10] [7, 3, 1, 6, 5, 4, 8, 2, 9, 10] [3, 1, 6, 5, 4, 7, 2, 8, 9, 10] [1, 3, 5, 4, 6, 2, 7, 8, 9, 10] [1, 3, 4, 5, 2, 6, 7, 8, 9, 10] [1, 3, 4, 2, 5, 6, 7, 8, 9, 10] [1, 3, 2, 4, 5, 6, 7, 8, 9, 10] [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第二轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第三轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第四轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第五轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第六轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第七轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第八轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第九轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 第十轮冒泡排序: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 排序后的数组为:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 排序前的数组为:[10, 7, 8, 3, 1, 6, 5, 4, 9, 2] ### 回答3: 冒泡排序算法是一个简单且基础的排序算法。它通过相邻元素的比较并交换位置,每一轮都将最大或最小的元素“冒泡”到最后的位置,因此被称为冒泡排序。 我们假设需要排序的10个整数为:4, 8, 2, 6, 5, 1, 9, 3, 7, 10。 首先,我们从第一个元素开始,比较相邻的两个元素大小,如果第一个元素大于第二个元素,则交换它们的位置。依次比较每一对相邻的元素,完成一轮后,最大的元素将“冒泡”到数组的最后一个位置。 经过第一轮比较,变化后的数组为:4, 2, 6, 5, 1, 8, 3, 7, 9, 10。 然后进行第二轮比较,结果为:2, 4, 5, 1, 6, 3, 7, 8, 9, 10。 接下来进行第三轮比较,结果为:2, 4, 1, 5, 3, 6, 7, 8, 9, 10。 依次类推,最终进行第九轮比较后,结果为:1, 2, 3, 4, 5, 6, 7, 8, 9, 10。 最终得到的有序数组为:1, 2, 3, 4, 5, 6, 7, 8, 9, 10。 最后,我们遍历输出排序前后的数组排序前的数组为:4, 8, 2, 6, 5, 1, 9, 3, 7, 10。 排序后的数组为:1, 2, 3, 4, 5, 6, 7, 8, 9, 10。 冒泡排序算法的时间复杂度为O(n^2),在处理大量数据时效率较低。但是冒泡排序算法思路简单,易于理解和实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值