排序算法总结(一)

基本概念

排序算法的稳定性

假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

分类

  • 内部排序:数据都在内存中(关注如何使得算法的时间空间复杂度更低)
  • 外部排序:数据太多,无法全部放入内存中,需要将待排序的记录存储在外部存储器(磁盘)上,无法一次性装入内存,所以需要在内存与外部存储器之间进行多次数据交换,所以还需要关注如何使得读写磁盘次数更少。

内部排序

插入排序

直接插入排序

插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。当然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳 定的。

  • 时间复杂度:主要来自于对比关键字、移动元素。若有n个元素,则需要n-1趟处理。

    1. 最好:原本就是排好序的,所以n-1趟处理就只需要对比关键字一次而不需要交换元素——O(n);
    2. 最坏:原本是逆序排放,每一次都需要比较,移动——O(n^2)
    3. 平均:O(n^2)
  • 空间复杂度:O(1)。

  • 使用场景:如:把新的数据插入到已排好的数据列中

  • java实现:

    public static void main(String[] args){
            int arr[] = {7,5,3,2,4};
            //插入排序
            for(int i = 1;i<arr.length;i++){
                //外层循环,从第二个开始比较
                for(int j = i;j>0;j--){
                    //与之前已经排好序的进行比较,如果后面的数据小于前面的则交换
                    if(arr[j]<arr[j-1]){
                        int temp = arr[j-1];
                        arr[j-1] = arr[j];
                        arr[j] = temp;
                    }else {
                        //插入完毕,退出循环
                        break;
                    }
                    System.out.println(arr[0]+"->"+arr[1]+"->"+arr[2]+"->"+arr[3]+"->"+arr[4]+"\n");
                }
            }
        }
    
    5->7->3->2->4
    5->3->7->2->4
    3->5->7->2->4
    3->5->2->7->4
    3->2->5->7->4
    2->3->5->7->4
    2->3->5->4->7
    2->3->4->5->7
    

希尔排序

是插入排序的一种,它是针对直接插入排序算法的改进。过比较相距一定间隔的元素来进行,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。

希尔排序是先追求表中元素部分有序,再逐渐逼近全局有序。

先将待排序表分割成若干形如L[i,i+d,i+2d,…,i+kd]的特殊子表,然后对各个子表分别进行直接插入排序。初始第一趟:d = length/2,然后缩小增量d,重复上述过程,直到d=1为止。

  • 空间复杂度:O(1)
  • 时间复杂度:它和增量序列的选择有关,目前无法用数学手段证明确切的时间复杂度,最坏的是O(n2),当n在某个范围内,可达到O(n1.3),所以还是比直接插入的优化一些的。
  • 稳定性:不稳定,可以自己举例子证明一下。
  • 适用性:只是适用于顺序表,而不能适用于链表(因为这里必须要有随机访问的特性才可以)。
public static void main(String[] args){
        int arr[] = {7,5,3,2,4};
        //希尔排序
        for(int i = arr.length / 2;i > 0;i = i/2){//控制步长
            for(int j = i;j < arr.length; j++){
                for (int k = j; k > 0  && k - i >= 0; k -= i) {
                    if (arr[k] < arr[k - i]) {
                        int temp = arr[k - i];
                        arr[k - i] = arr[k];
                        arr[k] = temp;
                    } else {
                        break;
                    }
                }
            }
        }
    }

交换排序

冒泡排序

重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。从前往后,大的向后,会把最大的元素渐渐“冒”出来;从后往前,小的向前,会把最小的元素渐渐“冒”出来。

  • 空间复杂度:O(1)
  • 时间复杂度:最好O(n);最坏O(n2);平均O(n2)
  • 稳定性:稳定的
  • 适用性:可以用于链表
//从左往右冒泡
    public static void bubbleSort1(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);
                }
            }
        }
    }

    //改进
    public static void bubbleSort2(int[] arr){
        int flag;//标记
        for(int i=arr.length-1; i>0; i--){
            flag = 0;//初始化标记为0
            for(int j = 0;j < i; j++){
                if(arr[j] > arr[j+1]){
                    swap(arr,j,j+1);
                    flag = 1;//发生交换,变为1
                }
            }
            // 若没发生交换,则说明数列已有序
            if (flag == 0){
                break;
            }
        }
    }
    //交换
    public static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

运行结果:

请输入要排序的数组长度:
20
请输入要排序的数组:
9 99 10 48 29 89 45 3 7 88 29 34 19 48 29 65 96 20 58 11
1排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99 
2排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99 

冒泡排序应该是最容易理解和编写代码的排序了吧!!!

快速排序

参考笔记:https://www.cnblogs.com/skywang12345/p/3596746.html

重点来了朋友们!!!!

快排是所有排序算法里面考察频率最高的一个!!!

好好学啊友友们!!!理解不了咱就记死它!!!

算法思想:

​ 在待排序表中任意选取一个元素pivot作为枢轴(基准),通常取得是首元素,通过一趟排序将待排序表划分为独立的两部分L[1,…,k-1]和L[k+1,…,n],使得L[1,…,k-1]中的所有元素小于pivot,L[k+1,…,n]中的所有元素大于等于pivot,则pivot就放在了其最终位置L(k)上,这个过程称作一次划分。然后分别递归地对两个子表重复上述过程,直到每部分只有一个元素或空为止,即达到了所有元素均放在了其最终位置上。

  • 稳定性:不稳定
  • 时间复杂度:平均O(NlogN),最坏O(N^2)

算法流程(超详细版):

  • 从数列中挑出一个基准值,为了方便选择第一个;
  • 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置k。
  • 如何将小于、大于基准数的值分别放在两边呢?采用双指针法,从数组的两端分别进行对比。
  • 先从最右边的位置向左找直到找到一个小于基准数的值,记录下该值的位置(记为i);
  • 再从最左位置往右直到找到一个大于基准数的值,记录下该值的位置(记为j);
  • 如果位置i<j,则交换这两个位置上的值,然后继续从j-1的位置往前,i+1的位置往后重复上面比对基准数然后交换的步骤。
  • 如果执行到了i=j,表示本次比对已经结束,将最后i的位置的值与基准数做交换,此时基准数就找到了临界点的位置k,位置k两边的数组都比当前位置k上的基准值或更小或更大。
  • 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。
public static void quickSort(int[] arr){
        if(arr == null || arr.length == 0 || arr.length == 1){
            return;
        }
        partition(arr,0,arr.length-1);
    }
    //核心代码部分
    public static void partition(int[] arr , int low , int high){
        if (low > high){
            return;
        }
        //设定基准值pivot
        int pivot = arr[low];
        int i = low,j = high;
        while (i != j){
            //从右往左直到找到比pivot小的数
            while (arr[j] >= pivot && i < j){
                j--;
            }
            //从左往右直到找到比pivot大的数
            while (arr[i] <= pivot && i < j){
                i++;
            }
            //已经找到或者已经i>=j了,就交换位置
            if (i < j){
                swap(arr,i,j);
            }
        }
        //基准数位置(中间)
        arr[low] = arr[i];
        arr[i] = pivot;
        //递归,继续向基准的左右两边执行和上面同样的操作
        //i的索引处为上面已确定好的基准值的位置,无需再处理
        partition(arr,low,i-1);
        partition(arr,i+1,high);
    }

    //交换
    public static void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
//运行结果
请输入要排序的数组长度:
20
请输入要排序的数组:
9 99 10 48 29 89 45 3 7 88 29 34 19 48 29 65 96 20 58 11
排序后的数组为:
3 7 9 10 11 19 20 29 29 29 34 45 48 48 58 65 88 89 96 99 

今日推歌

----《手掌心》 丁当

一干而尽
爱恨嗔痴的幻影
我敬你一杯一干二净的黎明
我在南极
憧憬你的北极星
我等你
不信心心不相印

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星回昭以烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值