【数据结构】排序

本文详细介绍了排序算法的实现,包括插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序和归并排序。讨论了每种排序算法的实现步骤、代码示例以及时间复杂度,帮助读者理解各种排序算法的特点和适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、排序分类

在这里插入图片描述

二、算法实现

都以升序为例。

1.插入排序

插入排序舞蹈

1.1 实现步骤

  1. 遍历数组
  2. 每遍历到一个元素arry[i],将其与之前的有序区间(array[0]~array[i-1])从后向前逐一比较,array[i]<其则交换,直至array[i]>=其跳出本次循环。

1.2 代码

  1. 设定循环次数,for(int i=1;i<length;i++),第1个数不用插入;
  2. 从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位;
  3. 将当前数放置到空着的位置,即j。
public static void insertSort(int[] array){
        for(int i = 1;i <= array.length-1;i++){
            int val = array[i];
            for (int j = i-1;j >= 0 && array[j] > val;j--){
                array[j+1]=array[j];
                array[j]=val;
            }
        }
        System.out.println(Arrays.toString(array));
    }

1.3 时间复杂度

  • 最好情况下:在本身有序的情况下,共比较了n-1次,没有移动的记录,时间复杂度为O(n)。
  • 最坏情况下:在逆序情况下:比较了 n(n-1)/2次,而移动次数为(n+2)(n-2)/2次。
  • 平均比较:移动次数是n ^ 2/4,故直接插入排序的时间复杂度为O(n^2)。
  • 直接插入排序法比冒泡和简单选择排序的性能要好一些。

2.希尔排序

希尔排序舞蹈

2.1 实现步骤

  • 将所有的数每次进行分组,一组数之间的间隔我们将其称为分组增量gap(gap = size或gap = gap / 3 + 1或gap = gap / 2)
  • 对于每一组都进行插入排序
  • gap由大变小最后逐渐减小为0
    例如:
    第一次增量的取法为:gap=size/2;
    第二次增量的取法为:gap=(size/2)/2;
    最后一直到: gap=1;
    在这里插入图片描述

2.2 代码

  • 确定分的组数gap;
  • 对组中元素进行插入排序;
  • 然后将gap/2,重复1,2步,直到gap=1为止。
public static void shellSort(int[] array){
        int gap = array.length/2;
        while (gap >= 1) {
            for (int i = gap; i < array.length; i++) {
                int j = 0;
                int temp = array[i];
                for (j = i - gap; j >= 0 && array[j] > temp; j -= gap) {
                    array[j + gap] = array[j];
                }
                array[j + gap] = temp;
            }
            gap = gap/2;
        }
        System.out.println(Arrays.toString(array));
    }

2.3 时间复杂度

  • 时间复杂度为O(n^1.5)
  • 希尔排序因为是跳跃式记录,所以是一个不稳定的排序算法

3.选择排序

选择排序舞蹈

3.1 实现步骤

  1. 遍历整个序列,将最小的数放在最前面;
  2. 遍历剩下的序列,将最小的数放在最前面;
  3. 重复第二步,直到只剩下一个数。

3.2 代码

  1. 确定循环次数,记住当前数字和当前位置;
  2. 将当前位置后面所有的数与当前数字进行比较,小的数赋值给key,并记住小的数的位置;
  3. 比较完成后,将最小的值与第一个数的值交换;
  4. 重复2、3步。
public static void selectSort(int[] array){
        for (int i = 0;i < array.length;i++){
            int key = array[i];
            int index = i;
            for (int j = i+1; j < array.length;j++){
                if (array[j] < key){
                    key = array[j];
                    index = j;
                }
            }
            array[index] = array[i];
            array[i] = key;
        }
        System.out.println(Arrays.toString(array));
    }

3.3 时间复杂度

  • 最大的特点:交换移动数据次数相当少,最好情况下交换0次,最差请求交换n-1次;适用于数组个数不多,但每个数组元素较大的情况
  • 时间复杂度:无论是最好最差情况,比较次数一样多,n(n-1)/2,总的时间复杂度O(n^2)
  • 简单选择排序性能上略优于冒泡排序

4.堆排序

堆排动画

4.1 实现步骤

取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依此类推,最终得到排序的序列。

  1. 将序列构建成大根堆;
  2. 将根节点与最后一个节点交换,然后断开最后一个节点;
  3. 重复第一、二步,直到所有节点断开。

4.2 代码

public static void heapSort(int[] A){
        int n = A.length;
        for(int i=(n-1)/2 ;i>=0;i--){
            //循环从最后一个拥有子节点的节点(A[(n-1)/2])处开始,
            //这样循环可以实现从下到上,从左到右将较大数向上推,最终实现大顶堆。
            //循环到0结束因为根节点0,也有两个子节点要比较。
            heapAdjust(A,i,n-1);
        }

        for(int i=n-1;i>0;i--){
            //循环从n-1开始,因为每次要交换根节点A[0],与最后一个节点A[i]
            //循环到1结束,因为此时A[0],A[1]大小已知
            swap(A,0,i);
            heapAdjust(A,0,i-1);
        }
        System.out.println(Arrays.toString(A));
    }

    // 向下调整为大根堆
    public static void heapAdjust(int[] A, int s, int m){
        int tmp = A[s];
        for(int j=2*s+1;j<=m;j=j*2+1){
            if(j<m && A[j]<A[j+1]){ //比较左右两个子节点谁大,并记录大的坐标; j<m保证了j+1不会超界
                j++;
            }

            if(tmp > A[j]){ //将父节点A[s]与上面得到的较大子节点比较,为true说明已是大堆顶,退出for循环
                break;
            }
            else{ //否则将较大子节点的值赋给父节点,并将原父节点的下标s变为较大节点的下标j,实现了将较大数向上推,较小数向下抛
                A[s] = A[j];
                s=j;
            }
        }
        A[s] = tmp;//整个循环完成后,将最初父节点的值,复制到循环后相应的节点上。
    }
    private static void swap(int[] a, int i, int i1) {
        int temp = 0;
        temp = a[i];
        a[i] = a[i1];
        a[i1] = temp;
    }

4.3 时间复杂度

  • 构建大堆顶的时间复杂度为O(n)
  • 第i次取堆顶记录重建堆需要O(logi)的时间,取n-1次,故重建堆的时间复杂度为O(nlogn)
  • 无论最好,最坏,平均时间复杂度均为O(nlogn)
  • 空间复杂度,只需要一个暂存单元
  • 因为记录的比较与交换是跳跃式进行,所以是一种不稳定的排序方法
  • 由于初始构建堆所需要的比较次数较多,不适合待排序序列个数较少的情况

5.冒泡排序

冒泡排序舞蹈

5.1 实现步骤

对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。
注意:若某一趟没有进行数组元素交换,则代表数组已经有序。

5.2 代码

public static void bubbleSort(int[] array){
        for (int i = 0;i < array.length - 1;i++){
            boolean flag = true;
            // 减i是因为排序i趟后,后面i个元素就是有序的。
            for (int j = 0;j < array.length - 1 - i;j++){
                if (array[j] > array[j+1]){
                    swap(array,j,j+1);
                    flag = false;
                }
            }
            if (flag){
                break;
            }
        }
        System.out.println(Arrays.toString(array));
    }

    private static void swap(int[] array, int j, int i) {
        int temp = array[j];
        array[j] = array[j+1];
        array[j+1] = temp;
    }

5.3 时间复杂度

  • 最好情况下 :数组本身有序,只需要进行n-1次比较,不需要交换,时间复杂度为O(n)
  • 最坏情况下 :数组为逆序,此时需要比较n*(n-1)/2,并作等数量级的记录移动,时间复杂度为O(n^2)

6.快排

快排舞蹈

6.1 实现步骤

  1. 在待排序的元素任取一个元素作为基准(通常选第一个或最后一个元素),称为基准元素;
  2. 将待排序的元素进行分区,比基准元素大的元素放在它的右边,比其小的放在它的左边;
  3. 对左右两个分区重复以上步骤直到所有元素都是有序的。

6.2 代码

public static void quickSort(int[] array,int l,int r){
        if (l < r){
            int i = l;
            int j = r;
            int x = array[l];
            while (i < j){
                // 1.从右向左找第一个小于x的数
                while (i < j && array[j] >= x) j--;
                // 找到后将array[j]放在array[i]处,并将i++
                if (i < j) array[i++] = array[j];
                // 2.从左向右找第一个大于x的数
                while (i < j && array[i] < x) i++;
                // 找到后将array[i]放在array[j]处,并将j--
                if (i < j) array[j--] = array[i];
            }
            // 将提前保存的x放在空缺处(此时i == j)
            array[i] = x;
            //对基准数左半边和右半边进行递归
            quickSort(array,l,i-1);
            quickSort(array,i+1,r);
        }
    }

6.3 时间复杂度

  • 最优与平均时间复杂度O(nlogn)
  • 最坏时间复杂度O(n^2)
  • 空间复杂度O(logn)
  • 快速排序是一种不稳定的排序方法

7.归并排序

归并排序舞蹈

7.1 实现步骤

  • 分解:分解待排序的n个元素的序列成n/2个元素的两个子序列
  • 解决:使用归并排序递归地排序两个子序列
  • 合并:合并两个已经排序好地子序列为一个序列,排序完成

7.2 代码

public static int[] mergeSort(int[] A ,int left,int right){
        if(left < right){
            int mid = left + (right - left)/2;

            //分
            mergeSort(A,left,mid);
            mergeSort(A,mid+1,right);

            //合并
            mergeGetArray(A,left,mid,right);
        }
        return A;
    }

    public static void mergeGetArray(int[] A,int left,int mid,int right){
        int[] TempRes = new int[right-left+1];
        int i = left,j = mid +1;
        int k = 0;

        while(i <= mid && j <= right){
            if(A[i]<A[j]){
                TempRes[k++] = A[i++];

            }else{
                TempRes[k++] = A[j++];
            }
        }

        while(i <= mid){
            TempRes[k++] = A[i++];
        }

        while(j <= right){
            TempRes[k++] = A[j++];
        }

        for(i = 0;i < TempRes.length;i++){
            A[left+i] = TempRes[i];
        }
    }

7.3 时间复杂度

  • 无论最好、最坏、平均来讲总的时间复杂度为O(nlogn)
  • 对于递归归并:由于需要与原始记录序列同样数量的存储空间存放归并结果及递归时深度为logn的栈空间,空间复杂度O(n+logn)
  • 非递归方式的归并排序:空间复杂度为O(n)
  • 归并排序为稳定的排序算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值