七个排序算法

排序算法

1. 冒泡排序

升序: 从头开始两两比较,把较大的元素与较小的元素进行交换;每轮把当前最大的一个元素存入到数组当前轮次的末尾,可以理解为 每轮次都是在确定目前的最大元素。

以第一次轮为例:

  • 从第一个元素开始,一一比较相邻元素,最终最大的元素在当前轮次的末尾
  • 第一轮即arr[length - 1],第二轮即arr[length - 2] --> 即arr[ length - i - 1]
//冒泡排序
void bubbleSort(int arr[], int length) {
    for (int i = 0; i < length; i++) {
        for (int j = 0; j < length - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp;
                temp = arr[j + 1];
                arr[j + 1] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

2. 选择排序

选择排序的思想从某种程度来说,跟冒泡排序是类似的:以升序为例

  • 选择排序:选出最小的元素,从前往后一一确定最小
  • 冒泡排序:选出最大的元素,从后往前一一确定最大;

以第一轮为例:

  • 第一个元素与其他所有数组元素比较,使得 arr[0] 存储的是最小的元素
//选择排序
void selectSort(int arr[], int length) {
    for (int i = 0; i < length - 1; i++) {
        for (int j = i + 1; j < length; j++) {
            if (arr[i] > arr[j]) {//
                int temp;
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}

3.插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。

基本思想:将一个记录插入到已经排好序的有序表中,从而形成一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

void insertSort(int arr[], int length) {
    for (int i = 1; i < length; i++) {// arr[i] 为当前要插入的元素
        int j;
        //该层循环是为找到比arr[i]小的下标j,那么j + 1 就是 arr[i] 要插入的下标位置
        for (j = i - 1; j >= 0 && arr[i] < arr[j]; j--) { //arr[0, i - 1]是已经排好序的有序表
                arr[j + 1] = arr[j];//实现比 arr[i] 大的数往后一位
        }
        arr[j + 1] = arr[i];
    }
}

4.希尔排序

希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”,是插入排序算法的一种更效的改进版本,实质上是一种分组插入方法

希尔排序是把数组按下标的一定增量分组,对每组使用插入排序算法排序;

随着增量逐渐减少,每组包含的元素越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

以第一轮为例:假设 n 为6

  • 增量为 n / 2 = 3 ;分为 3 组
  • 以数组下标代表分组 : [ 0, 3] ; [1, 4] ; [2, 5]
void incrementSort(int arr[], int length) {
    int n = length;
    while (true) {
        n /= 2;   //增量每次减半    
        for (int i = 0; i < n; i++) {
            for (int j = i + n; j < length; j += n) {//这个循环里其实就是一个插入排序         
                int k = j - n;
                while (k >= 0 && arr[k] > arr[k + n]) {
                    int temp = arr[k];
                    arr[k] = arr[k + n];
                    arr[k + n] = temp;
                    k -= n;
                }
            }
        }
        if (n == 1)
            break;
    }
}

5.快速排序

快速排序算法通过多次比较和交换来实现排序,其排序流程如下:

  1. 首先选定一个记录(通常选取第一个记录),通过该记录的值将数组分成左右两部分

  2. 将大于或等于记录值的数据集中到数组右边,小于记录值的数据集中到数组的左边。

  3. 左边和右边的数据继续进行排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。

这可以说是一个递归过程。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了

// 快速排序
void QuickSort(int arr[], int start, int end)
{
    if (start >= end)
        return;

    int i = start;
    int j = end;
    
    // 以第一个记录为基准
    int base = arr[start];
    while (i < j)
    {
        // 从右向左找比基准数小的数
        while (j > i && arr[j] >= base)
            j--;

        if (i < j)//说明右边有arr[j] < base
        {
            arr[i] = arr[j];//将该元素放于左边
            i++;//i下标已确定比 base 小;从左向右扫描的下标开始值 + 1
        }
        // 从左向右找比基准数大的数
        while (i < j && arr[i] < base)
            i++;

        if (i < j)//说明有arr[i] >= base
        {
            arr[j] = arr[i];//将该元素放于右边
            j--;//j下标已确定不小于 base ;下次 从右向左扫描的下标结尾 - 1
        }
    }
    // 把基准数放到i的位置
    arr[i] = base;
    // 递归
    QuickSort(arr, start, i - 1);
    QuickSort(arr, i + 1, end);
}

6. 归并排序

归并排序即是将两个本就有序的序列合并成一个有序的序列。

// 归并排序
void MergeSort(int[] arr, int[] nums)
{
    int len1 = arr.length;
    int len2 = nums.length;
    int[] newArr = new int[len1 + len2];//新的有序序列
    int i = 0, j = 0,k = 0;
    while(i < len1 && j < len2){
        if(arr[i] <= nums[j]){
            newArr[k] = arr[i];
            i++;//使 i 指向下一个将与nums[j]的元素
        }else if(arr[i] > nums[j]){
            newArr[k] = nums[j];
            j++;//使 j 指向下一个将与arr[i]的元素
        }
        k++;
    }
    while (i < len1){
        newArr[k] = arr[i];
        k++;
        i++;
    }
    while (j < len2){
        newArr[k] = nums[j];
        k++;
        j++;
    }
}

7. 堆排序

堆是一个近似的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点可以将堆看做是一个完全二叉树。

  • 每个结点的值都大于等于其左右孩子结点的值,称为大顶堆
  • 每个结点的值都小于等于其左右孩子结点的值,称为小顶堆

基本思想为:将待排序列构造成一个大顶堆(小顶堆),整个序列的最大值(最小值)就是堆顶的根结点,将根节点的值和堆数组的末尾元素交换,此时末尾元素就是最大值(最小值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值(次小值),如此反复执行,最终得到一个有序序列。

以大顶堆为例:如何将二叉树调整为堆

  • 从二叉树最后一个非叶子结点开始,将结点上的值依次和左右子树的值作比较,若子树上的值大于结点值,则将其交换。
  • 对二叉树上所有的非叶子结点执行上述操作
public static int[] heapSort(int[] array) {
    //索引从0开始的,最后一个非叶子结点array.length/2 - 1
    for (int i = array.length / 2 - 1; i >= 0; i--) {
        adjustHeap(array, i, array.length);  //调整堆
    }

    
    // 开始排序
    for (int j = array.length - 1; j > 0; j--) {
        // 把大顶堆的根元素,放到数组的最后;即每一次的堆调整之后,都会有一个元素到达自己的最终位置
        swap(array, 0, j); 
        
        // 最后一个元素无需再考虑排序问题了。

        adjustHeap(array, 0, j);// 重新调整堆,获取当前最大值
    }
    return array;
}

public static void adjustHeap(int[] array, int i, int length) {
    int temp = array[i];//非叶子节点
    
    for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {  
        //2*i+1 为 i 的左子树 ,2*k+1为 k 的左子树
        
        // 让k先指向子节点中最大的节点
        if (k + 1 < length && array[k] < array[k + 1]) {  //如果有右子树,并且右子树大于左子树
            k++;
        }
        //如果发现结点(左右子结点)大于根结点,则进行值的交换
        if (array[k] > temp) {
            swap(array, i, k);
            // 如果子节点更换了,以子节点为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
            i  =  k;
        } else {  //不用交换,直接终止循环
            break;
        }
    }
}

public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
}
为根的子树会受到影响,所以,循环对子节点所在的树继续进行判断
            i  =  k;
        } else {  //不用交换,直接终止循环
            break;
        }
    }
}

public static void swap(int[] arr, int a, int b) {
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值