排序算法

1 篇文章 0 订阅
1 篇文章 0 订阅

排序算法

排序算法种类

插入排序

  • 直接插入排序
  • 希尔排序

选择排序

  • 简单选择排序
  • 堆排序

交换排序

  • 冒泡排序
  • 快速排序

归并排序

基数排序

排序算法时间复杂度

排序算法时间复杂度

冒泡排序

通过对待排序序列从前向后(从下标较小元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前向后移动,像气泡一样逐渐往上冒

给定数组[5,3,9,2,4]

  • 第一趟排序后结果为:[3,5,2,4,9]
  • 第二趟排序后结果为:[3,2,4,5,9]
  • 第三趟排序后结果为:[2,3,4,5,9]
public static void bubbleSort(int[] arr) {
        // 冒泡排序,时间复杂度O(n^2)
        int temp = 0;
        boolean flag = false;// 优化冒泡排序,发现有一趟排序之后没有任何一个数据交换,则退出循环
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    flag = true;
                    temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
            if (!flag) {
                break;
            } else {
                flag = false; //如果发生交换,重置flag值
            }
        }
    }
选择排序

每次都选出数组最小值,设置一个辅助索引,与辅助索引所在位置的值进行交换进行交换

给定数组[5,3,9,2,4]

  • 第一趟排序后结果为:[2,3,9,5,4]
  • 第二趟排序后结果为:[2,3,9,5,4]
  • 第三趟排序后结果为:[2,3,4,5,9]
public static void selectSort(int[] arr) {
        //选择排序,时间复杂度O(n^2)
        for (int i = 0; i < arr.length-1; i++) {
            int minIndex = i;   // 最小值的下标
            int min = arr[i];   // 最小值
            for (int j = i+1; j < arr.length; j++) {
                if (min > arr[j]){  // 找到比假设的最小值更小元素,记录最小值以及下标
                    min = arr[j];
                    minIndex = j;
                }
            }
            if (minIndex != i){ // 将每一轮找到的最小值与数组的第i个元素进行替换
                arr[minIndex] =arr[i];
                arr[i] = min;
            }
        }
    }
插入排序

将待排序数组看作一个有序数组和无须数组,依次将无序数组的第一个元素插入到有序数组中的适当位置

给定数组[5,3,9,2,4]

  • 第一趟排序后结果为:[3,5,9,2,4]
  • 第二趟排序后结果为:[3,5,9,2,4]
  • 第三堂排序后结果为:[2,3,5,9,4]
  • 第四趟排序后结果为:[2,3,4,5,9]
public static void insertSort(int[] arr) {
    int insertVal = 0;
    int insertIdx = 0;
    for (int i = 1; i < arr.length; i++) {
        insertVal = arr[i];
        insertIdx = i - 1;

        // 保证insertIdx不会越界
        while (insertIdx >= 0 && insertVal < arr[insertIdx]) {
            arr[insertIdx + 1] = arr[insertIdx];
            insertIdx--;
        }
        if (insertIdx + 1 != i) {
            arr[insertIdx + 1] = insertVal;
        }
//            System.out.println("第"+i+"趟排序后的结果:"+ Arrays.toString(arr));
    }
}
希尔(shell)排序

先将待排序数组按步长gap分组,然后对每一组中的元素进行排序,循环此动作直到gap无限接近于0

给定数组[5,3,9,2,4]

  • 第一趟排序后结果为:[5,2,9,3,4]
  • 第二趟排序后结果为:[2,3,4,5,9]
/**
 * 交换型shell排序,效率不高,因为是用交换法对分组后的数据进行排序
 * shell排序的平均时间复杂度为nlogn,最坏时间复杂度为n^s(1<s<2)
 *
 * @param arr
 */
public static void shellSort1(int[] arr) {
    int temp = 0;
    int count = 0;
    // 根据分析进行循环处理
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < arr.length; i++) {
            for (int j = i - gap; j >= 0; j -= gap) {
                if (arr[j] > arr[j + gap]) {
                    temp = arr[j];
                    arr[j] = arr[j + gap];
                    arr[j + gap] = temp;
                }
            }
        }
    }
}

/**
 * 优化版希尔排序,使用插入法对分组的数据进行排序
 * @param arr
 */
public static void shellSort2(int[] arr) {
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
        for (int i = gap; i < arr.length; i++) {
            int j = i;
            int temp = arr[j];
            if (arr[j] < arr[j - gap]) {
                // 保证insertIdx不会越界
                while (j - gap >= 0 && temp < arr[j - gap]) {
                    arr[j] = arr[j - gap];
                    j -= gap;
                }
                arr[j] = temp;
            }
        }
    }
}
快速排序

在数组中先找到一个基准值以及位置,然后从数组的左边和右边分别开始查找,找到比基准值小的放左边,比基准值大的放右边,直到基准值左边所有元素都小于右边元素,然后对左边和右边元素进行递归

给定数组[5,3,9,2,4],以中轴值为基准

  • 第一轮交换的结果为:[5,3,4,2,9]

  • 第二轮交换的结果为:[2,3,4,5,9]

public static void quickSort(int[] arr, int left, int right) {
    int l = left;
    int r = right;
    // 中轴值
    int pivot = arr[(right + left) / 2];
    int temp = 0;
    while (l < r) {
        //从数组左边开始一直找,直到找到一个大于等于pivot的值
        while (arr[l] < pivot) {
            l += 1;
        }
        //从数组右边一直找,直到找到一个小于等于pivot的值
        while (arr[r] > pivot) {
            r -= 1;
        }
        // 如果l>=r,说明左边的数全部小于右边的数,退出循环
        if (l >= r) {
            break;
        }
        // 交换
        temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
        //如果交换完之后,发现这个arr[l] == pivot,r-- 前移
        if (arr[l] == pivot) {
            r -= 1;
        }
        //如果交换完之后,发现这个arr[r] == pivot,l++ 后移
        if (arr[r] == pivot) {
            l += 1;
        }
    }
    // 如果l==r,必须l++,r--,否则会出现栈溢出
    if (l == r) {
        l += 1;
        r -= 1;
    }
    // 如果中轴值左边有数据,递归
    if (right > l) {
        quickSort(arr, l, right);
    }
    // 如果中轴值右边有数据,递归
    if (left < r) {
        quickSort(arr, left, r);
    }
}
归并排序

将数组从中间拆分成两部分,一直分拆到数组元素为1,然后合并,合并时按顺序合并,一直递归直到合并完成

给定数组[5,3,9,2,4]

  • 第一次合并:[3,5,9,2,4](以9为中轴拆分成左右两部分)
  • 第二次合并:[2,3,4,5,9](合并)
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
 if (left < right) {
        int mid = (right + left) / 2;
        // 向左递归分解
        mergeSort(arr, left, mid, temp);
        // 向右递归分解
        mergeSort(arr, mid + 1, right, temp);
        // 合并
        merge(arr, left, mid, right, temp);
    }
}

/**
 * arr数组分解之后合并步骤
 *
 * @param arr   原始数组
 * @param left  左边有序序列的初始索引
 * @param mid   中间索引
 * @param right 右边索引
 * @param temp  辅助数组,用于存放中间数据
 */
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    int foot = 0;// 记录temp数组当前插入位置索引
    int i = left;// 初始化i,左边有序序列的初始索引
    int j = mid + 1;// 初始化j,右边有序序列的初始索引
    // 第一步:
    // 先把左右两边(有序)的数据按照规则填充到temp数组
    // 直到有一边的数据处理完毕
    while (i <= mid && j <= right) {
        // 如果左边数组的当前元素小于等于右边数组当前元素,则将左边数组元素拷贝到temp,反之亦然
        if (arr[i] <= arr[j]) {
            temp[foot] = arr[i];
            i += 1;
            foot += 1;
        } else {
            temp[foot] = arr[j];
            j += 1;
            foot += 1;
        }
    }
    // 第二步
    // 将有剩余数据的数组数据全部填充到temp
    while (i <= mid) {
        temp[foot] = arr[i];
        i += 1;
        foot += 1;
    }
    while (j <= right) {
        temp[foot] = arr[j];
        j += 1;
        foot += 1;
    }
    // 第三步
    // 将temp数组的元素拷贝到arr中
    int t = 0;
    int tempLeft = left;// 因为每一次分拆后的数组不一定是是合并所有元素,每一次分拆后的元素合并个数不一样
//        System.out.println("tempLeft:" + tempLeft + "right:" + right);
    while (tempLeft <= right) {
        arr[tempLeft] = temp[t];
        t += 1;
        tempLeft += 1;
    }
}
基数排序(桶排序)

准备一个length为10的二维数组bucket,然后循环遍历需要排序的数组,依次按数组中元素的个,十,百…位数字放入到bucket对应的数组中,若元素高位为空,则补0,然后遍历bucket,按顺序将bucket中元素取出来,重复上述操作直至数组中元素的所有位数都比较完成。

给定数组[5,3,9,2,4]

  • 第一次放入桶中:[2,3,4,5,9]
// 由于基数排序是典型的用空间换时间的算法
// 所以基数排序会耗费额外的内存空间,所以当数组长度过大的时候,可能会出现OutOfMemoryError错误
public static void radixSort(int[] arr) {
    // 获取数组中最大元素
    int max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
    // 得到最大元素位数
    int maxLength = (max + "").length();

    // 定义一个二维数组,表示有10个桶,每个桶的就是一个一位数组
    // 说明
    // 1.二维数组包含10个一位数组
    // 2.因为无法确定元素中对应位数相等的有多少,所以初始化bucket中桶的大小为arr.length
    // 3.很明确,基数排序是用空间换时间的经典算法
    int[][] bucket = new int[10][arr.length];
    // 定义一个一位数组记录每个桶中每次放入的元素个数
    int[] bucketElementCounts = new int[10];
    // 通过循环来处理每一轮排序
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        for (int j = 0; j < arr.length; j++) {
            // 取出每个元素个位数的值
            int digitOfElement = arr[j] / n % 10;
            // 放入到对应的桶中
            bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
            // 更新记录元素个数的数组
            bucketElementCounts[digitOfElement]++;
        }

        int index = 0;
        // 按顺序取出桶中所有数据
        for (int k = 0; k < bucket.length; k++) {
            if (bucketElementCounts[k] > 0) {
                for (int l = 0; l < bucketElementCounts[k]; l++) {
                    arr[index++] = bucket[k][l];
                }
            }
            // 每一个桶中数据取出之后必须bucketElementCounts[k] = 0,否则会影响下一轮排序
            bucketElementCounts[k] = 0;
        }
    }
}

特别注意:基数排序是典型的使用空间换时间的算法,如果数组过大,会导致OutOfMemoryError错误

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值