数据结构重点:排序

我的飞书:https://rvg7rs2jk1g.feishu.cn/docx/AWfNdtY6toyOjSxWT84cTmwGnhg

概念+分类

概念

排序:将一串记录,按照其中某个或某些关键字的大小,进行递增或者递减的排列的操作,是笔试和面试中重点考的考试题型之一

同时,在提到排序的时候就不得不提到稳定性

稳定性: 当有两个相同的元素处于待排序的数据中,同时他们分别处于前后位置,在进行排序之后,如果他们进行了位置的调换,则说明是不稳定的;如果没有发生调换,则说明是稳定的 --> 两个相同数据的相对位置是否发生改变

内部排序:将数据元素全部放在内存中的排序

外部元素:不能在内外村之间移动数据的排序

 排序的代码一定要会写!!不能只会原理

常见排序算法(七种)

所有的排序都是在原数组进行排序,不需要另外创建一个空数组

  1. 插入排序

    1. 直接插入排序

    2. 希尔排序

  2. 选择排序

    1. 选择排序

    2. 堆排序

  3. 交换排序

    1. 冒泡排序

    2. 快速排序

  4. 归并排序

算法实现(暂且实现递增)

生成数组:采用随机生成数据的形式来进行排序

public static void main(String[] args) {
    Random random = new Random();
    int[] arr = new int[10];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = random.nextInt(0,9);
        System.out.print(arr[i]+" ");
    }
    System.out.println();
    //排序调用



    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i]+" ");
    }
}

插入排序

将待排序的数据,逐个按照大小插入已经排好序的序列中,知道所有数据都全部插入序列,就像斗地主整理手牌一样

直接插入排序

定义两个索引值(i j), i 代表当前需要进行排序的位置,j 为 i 之前已经排好序的末尾元素位置,再将i位置的元素与j位置进行比较,并将j不断向前移,知道找到一个位置的元素大小比 i 位置的元素小,此时这个元素的后一个位置就是 i 元素的真正位置,同时之前进行对比的元素都要进行后移;如果没有找到比 i 小的元素(都比 i 大)那么此时 j 已经遍历完排好序的序列,此时 j 为-1,i元素就要放入 j+1 的位置;同时如果原本 j 位置上的元素就已经比 i

小了,那 i 对应的元素就还是在原位

步骤

需要另外生成一个地址用来存储数据,进行对比

  1. i 从第二个数据开始进行比较,同时 i 之前的数据都是有序的

  2. j=i-1; 用来代表前面已经有序的元素

  3. j<0 则一次循环停止,代表原本处于i位置上的元素找到了位置

  4. i++;

代码
public static void sort1(int[] arr){
    for (int i = 1; i < arr.length; i++) {
        int tmp = arr[i];
        int j = i-1;//代表已经排序的最后一个元素的位置
        for (; j >=0 ; j--) {
            if(arr[j]>tmp){
                arr[j+1] = arr[j];
            }else {
                //这里代表已经找到了arr[i]的位置
                //此时的位置就应该是 j+1;
                break;
            }
        }
        //当已排序的序列完全遍历之后,就要将arr[j+1]进行赋值
        //不管是break退出循环,还是遍历完全退出,都要给j+1进行赋值
        //break: 找到位置,为 j+1
        //全部遍历退出: 找到位置为序列的开头,也就是arr[0],此时 j==-1,所以也是在 j+1 的位置
        //因为只要是循环结束就会执行语句,所以不能直接将arr[0]设定为tmp
        //有两种循环退出方式
        arr[j+1] = tmp;
    }
}
结果

结果分析:

  1. 时间复杂度:

    1. 最好情况:数据一开始就已经是有序的 --> O(N)

    2. 最坏情况:数据一开始就是逆序的 --> O(N^2)

  2. 空间复杂度: O(1)

  3. 稳定性: 稳定

  4. 特点: 数据越有序,直接插入越快(排序优化)

希尔排序(缩小增量排序)

将待排序的数组分成若干个子数组,对每个子数组进行插入排序,然后逐渐减少子数组的数量,使得整个数组变得更有序,最后再进行一次简单的插入排序

分组:

图中是第一次分组

将相同颜色的部分分为一组,也就是说,希尔排序的分组不是将相邻数据进行分组,而是选定一个特定的标记数,每隔一个标记数的数据就分为一组,图中的标记数是5,之后再不断减少标记数

跳跃式分组:尽可能将最小的元素往前拿,最大的元素往后(第一次的标记数就是数组长度的一半) --> 提升效率

只有当标记数为1的时候,才是排序,在此之前都是预排序

代码
public static void shellSort(int[] arr){
    int gap = arr.length;
    while (gap>1){
        gap = gap/2;
        shell(arr,gap);
    }
}
public static void shell(int[] arr,int gap){
    int tmp;
    for (int i = gap; i <arr.length ; i++) {
        tmp = arr[i];
        int j = i-gap;
        for (; j >=0 ; j+=gap) {
            if(arr[j]>tmp){
                arr[j+gap] = arr[j];
            }else {
                break;
            }
        }
        arr[j+gap] = tmp;
    }
}
结果

结果分析:

  1. 时间复杂度: O(N^1.25) ~ O(N^1.5)

  2. 空间复杂度: O(1)

  3. 稳定性: 不稳定

选择排序

每一次从待排序的数据元素中选出最小(最大)的元素,存放到序列的其实位置,直到全部数据排序完毕

直接选择排序

代码

第一种

public static void selectSort(int[] arr){
    for (int i = 0; i < arr.length ; i++) {
        int flag = i;//记录下标
        for (int j = i+1; j < arr.length ; j++) {
            if (arr[j]<arr[flag]){
                flag=j;//让下标始终为当前找到的最小值的下标
            }
        }
        int tmp = arr[i];
        arr[i] = arr[flag];
        arr[flag] = tmp;
    }
}

双指针:

public static void selectSort2(int[] arr){
    int left = 0;
    int right = arr.length-1;
    while (left<right){
        int i = left+1;
        int minIndex = left;
        int maxIndex = left;
        while (i<=right){
            if(arr[i]<arr[minIndex]){
                minIndex = i;
            }
            if(arr[i]>arr[maxIndex]){
                maxIndex = i;
            }
            i++;
        }
        int tmp = arr[right];
        arr[right] = arr[maxIndex];
        arr[maxIndex] = tmp;
        tmp = arr[left];
        arr[left] = arr[minIndex];
        arr[minIndex] = tmp;

        left++;
        right--;
    }
}
结果

结果分析

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

  2. 空间复杂度: O(1)

  3. 稳定性: 不稳定

堆排序

代码
public static void heapSort(int[] arr){
    //创建大根堆
    createHeap(arr);
    int end = arr.length-1;
    while (end>0){
        int tmp = arr[0];
        arr[0] = arr[end];
        arr[end] = tmp;
        siftDown(arr,0,end);
        end--;
    }
}
private static void createHeap(int[] arr){
     //从最后一个子树的父亲节点开始
    for (int parent = (arr.length-1-1)/2; parent >=0 ; parent--) {
        siftDown(arr,parent,arr.length);
    }
}
private static void siftDown(int[] arr,int parent,int len){
    //向下调整
    int child = (2*parent)+1;
    while (child<len){
        if(child+1<len&&arr[child]<arr[child+1]){
            child = child+1;
        }
        if (arr[child]>arr[parent]){
            int tmp = arr[child];
            arr[child] = arr[parent];
            arr[parent] = tmp;
            parent = child;
            child = 2*parent+1;
        }else{
            break;
        }
    }
}
结果

结果分析:

  1. 时间复杂度: O(N*logN)

  2. 空间复杂度: O(1)

  3. 稳定性: 不稳定

交换排序

冒泡排序

代码
public static void bubbleSort(int[] array){
    //i用来表示趟数
    for (int i = 0; i < array.length-1; i++) {
        boolean flag = false;
        for (int j = 0; j < array.length-1; j++) {
            if(array[j]>array[j+1]){
                swap(array,j,j+1);
                flag = true;
            }
        }
        //优化
        if(flag==false){
            //有序
            break;
        }
    }
}
private static void swap(int[] arr,int i,int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}
结果

结果分析:

  1. 时间复杂度:

    1. 优化前: 最好最坏都是 O(N^2)

    2. 优化后: 最好是 O(N) 最坏是 O(N^2)

  2. 空间复杂度: O(1)

  3. 稳定性: 稳定

快速排序

是一种二叉树结构的排序算法,找一个基准值,将数据分为两部分,使基准值左边都比他小,右边都比他大,同时使用的是双指针,左右两指针相遇的位置就是基准值按照排序应该在的位置

代码
        1.Hoare
public static void fastSort(int[] arr){
    sort3(arr,0,arr.length-1);
}
private static void sort3(int[] arr,int start,int end){
    if(start>=end){
        return;
    }
    int flag = partitionHoare(arr,start,end);
    sort3(arr,start,flag-1);
    sort3(arr,flag+1,end);
}
private static int partitionHoare(int[] arr,int left,int right){
    int temp = arr[left];
    int flag = left;
    while (left<right){
        while (left<right&&arr[right]>=temp)
            right--;
        while (left<right&&arr[left]<=temp)
            left++;
        swap(arr,left,right);
    }
    swap(arr,left,flag);
    return left;
}
private static void swap(int[] arr,int i,int j){
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}
        2.挖坑法

Hoare的一种优化

public static void fastSort(int[] arr){
    sort3(arr,0,arr.length-1);
}
private static void sort3(int[] arr,int start,int end){
    if(start>=end){
        return;
    }
    int flag = partition(arr,start,end);
    sort3(arr,start,flag-1);
    sort3(arr,flag+1,end);
}
private static int partition(int[] arr,int left,int right){
    int temp = arr[left];
    int flag = left;
    while (left<right){
        while (left<right&&arr[right]>=temp)
            right--;
        while (left<right&&arr[left]<=temp)
            left++;
        if (left>=right){
            break;
        }
        arr[flag] = arr[right];
        arr[right] = arr[left];
    }
    arr[left] = temp;
    return left;
}
结果

结果分析:

  1. 时间复杂度:

    1. 最好情况: O(N*logN)

    2. 最坏情况: O(N^2)

  2. 空间复杂度:

    1. 最好情况: O(logN)

    2. 最坏情况: O(N)

  3. 稳定性: 不稳定

归并排序

分治法: 将已经有序的子序列进行合并(二路归并)

代码
public static void mergeSort(int[] arr){
    mergeFunc(arr,0,arr.length-1);
}
private static void mergeFunc(int[] arr,int left,int right){
    if (left>=right){
        return;
    }
    int mid = left+((right-left)>>1);
    //分组
    mergeFunc(arr,left,mid);
    mergeFunc(arr,mid+1,right);
    merge(arr,left,mid,right);
}
private static void merge(int[] arr,int left,int mid,int right){
    int s1 = left;
    int e1 = mid;
    int s2 = mid+1;
    int e2 = right;
    int[] tmp = new int[right-left+1];
    int flag = 0;
    //1.保证两个表都有数据
    while (s1<=e1&&s2<=e2){
        if(arr[s1]<=arr[s2]){
            tmp[flag] = arr[s1];
            s1++;
        }else {
            tmp[flag] = arr[s2];
            s2++;
        }
        flag++;
    }
    //剩余数据
    while (s1<=e1){
        tmp[flag] = arr[s1];
        flag++;
        s1++;
    }
    while (s2<=e2){
        tmp[flag] = arr[s2];
        flag++;
        s2++;
    }

    //返回到原数组
    for (int i = 0; i <flag ; i++) {
        arr[i+left] = tmp[i];
    }
}
结果

结果分析:

  1. 时间复杂度: O(N*logN)

  2. 空间复杂度: O(N)

  3. 稳定性: 稳定

总结

名称

时间复杂度

空间复杂度

稳定性

直接插入排序

最好情况: O(N)

最坏情况: O(N^2)

平均:O(N^2)

O(1)

稳定

希尔排序

O(N^1.25) ~ O(N^1.5)

平均:O(N^1.3)

O(1)

不稳定

选择排序

O(N^2)

O(1)

不稳定

堆排序

O(N*logN)

O(1)

不稳定

冒泡排序

优化前: 最好最坏都是 O(N^2)

优化后: 最好是 O(N) 最坏是 O(N^2)

平均:O(N^2)

O(1)

稳定

快速排序

最好情况: O(N*logN)

最坏情况: O(N^2)

最好情况: O(logN)

最坏情况: O(N)

不稳定

归并排序

O(N*logN)

O(N)

稳定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值