排序算法介绍

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

1.内部排序->交换排序->冒泡排序

在这里插入图片描述
举一个例子
3,20,1,19,11
从小到大排序
第一轮排序,从最小索引开始
两个指针,相邻的两个如果冲突,那就直接交换
(加粗就是发生了对比)
(1) 3 ,20 , 1,19 ,11
(2) 3 **,1,20,**19,11
(3) 3,1,**19,20,**11
(4)3,1,19,11,20

第一轮排序之后,相当于最大的数据已经放到右边
只需要整理前n - 1个数据再来一次排序就行
第二次排序
(1)1,3,19,11,20
(2) 1,3,19,11,20
(3)1,3,11,19,20
第二次排序只扫描n -1个数据,排出来第二大的数据

第三次排序:
(1) 1,3,11,19,20
(2)1,3 ,11,19,20
应该是一共要进行n - 1次排序,但是,倘若有一次排序结束后,整个数据排列没有任何变化,说明数据已经很完美了,并不需要下一次排列

n个数据,每一次排序,都可以决定出第1大,第二大,第三大,第n- 1大的数据,经历了N-1 次排序之后有N - 1个数据都已经排序好了,最后一个数毫无任何意义。

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {7,2,8,10,5,3,1};
        //1.第一次排序,是两个指针,将相邻两个交换顺序{
        for(int k = 0;k < arr.length;k++){
            //使用K去控制,循环次数,以及减少下面循环的次数,毕竟每次排序结束后,第二次排序就比上次可以少拍一次
            boolean flag = true;//用于优化算法,每次进入第二次循环重新分配一个flag,默认是true
            //算法中,当出现某一次循环排序,谁都没交换的时候,已经可以直接跳出循环了
            for (int i = 0, j = i + 1; j < arr.length - k; i++, j++){

                if(arr[i] > arr[j]){
                    flag = true;//下次循环可以循环
                    int temp;
                    temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;//交换一下数据
                }
                if(flag == false){
                    break;//如果一次交换都没发生,说明可以跳出
                }
            }

        }

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

    }
}

冒泡排序时间复杂度是O(n^2)

选择排序。
冒泡排序是排着排着,把顺序排好了
选择排序是我们自己经过一系列选择,把顺序选择好
从前面中找出最小
在这里插入图片描述

图解选择排序

在这里插入图片描述

package com.atguigu.entity;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {-1,75,8,20,5,15,1};
        //一个for循环,遍历整个数组
        for (int i = 0;i < arr.length;i++){
             int min = arr[i];//用来找出最小那个值
             int pos = i;//用来记录最小值所在下标
             for(int j = i + 1;j < arr.length;j++){
                if(min > arr[j]){
                    min = arr[j];
                    pos = j;//遇到更小的就记录
                }

             }

             //一轮遍历结束后,将最小值与arr[i 交换位置
            arr[pos] = arr[i];
            arr[i] = min;//最小值放到i的位置上去

        }

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

    }
}

思想很简单,两个指针,每次都将项目中最小一个拿出来,然后和前面的遍历位置交换一下,一直到全部遍历完成
或者拿hashMap去装也可以

package com.atguigu.entity;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;

import java.util.HashMap;
import java.util.Map;

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {-1,75,78,20,5,15,1};
        //一个for循环,遍历整个数组
        for (int i = 0;i < arr.length;i++){
             Map<Integer,Integer> minMap = new HashMap<>();//用map的键值对来存储
             minMap.put(arr[i],i);//值作为key,下标作为value
             int min = arr[i];
             for(int j = i + 1;j < arr.length;j++){
                if(min > arr[j]){
                    min = arr[j];
                    minMap.put(arr[j],j);//用map去装已经锁定的值
                }

             }

             //一轮遍历结束后,将最小值与arr[i 交换位置
            arr[minMap.get(min)] = arr[i];
            arr[i] = min;//最小值放到i的位置上去

        }

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

    }
}

选择排序速度相比冒泡排序要快一些。

插入排序

在这里插入图片描述
思路
1.左边是已经排好的序列
2.右边是等待排列的
在这里插入图片描述
从排列好的后面第一个开始,都是没排列好的,和6进行比较
在这里插入图片描述
9在这里插入图片描述
倘若比正在那啥的区域要大,就自动往数组后面走,把前面空间让出来,6不需要再移动了,所以插入了。
在这里插入图片描述

然后从8开始,继续和前面的进行对比,都不移动一直到9.

在这里插入图片描述
带着3去和前面的每一位比较,如果前面的比三大,就自动后移,把位置让给3,一直到3找到一个不比他大的位置,3才会停下来。
在这里插入图片描述
注意:
1.一开始分成左右两个,默认最左边一个是排好的数列,让右边数列走循环去插左边的数列。
2.找不如我的,然他往后走,空出来位置,遇到比不过的大神,我排大神后面。

package com.atguigu.entity;

import org.omg.Messaging.SYNC_WITH_TRANSPORT;

import java.util.HashMap;
import java.util.Map;

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = {22,11,25,20,5,15,1};
      //逐渐推导方式理解
        //第一轮结束后,我们应该是{11,22,25,20,5,15,1}
        //给11在前面找一个合适的位置
        //定义待插入数字为arr[1]
        for(int i = 1;i < arr.length;i++){
            int insertVal = arr[i];
            int insertIndex = i -1;//1 - 1,待插入数字,需要比较的索引是自身位置 - 1,一直到 0,这都是它需要去比较的位置
            while(insertIndex >= 0 && insertVal < arr[insertIndex]){//比我大,到后面去吧
                
                //说明待插入数,还没找到位置,也就是当前这个待插入索引位置的数,需要往后移动
                arr[insertIndex + 1] = arr[insertIndex];
                //arr = {22,22,25,20,5,15,1};
                insertIndex--;//当发生上述问题时候,说明,我们还没找到要插入的位置,指针继续往前移动

                //当跳出循环时候,说明插入位置找到了,我比不过前面那位大神,我就老老实实排大神后面
            }
            //当循环进不去的时候,说明我们找到了可以插入的位置
            arr[insertIndex + 1] = insertVal;

        }


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

    }
}

希尔排序
本质是对插入排序的一种优化
在这里插入图片描述插入排序会面临很多问题,比如这种情况下,插入排序效率不高,所以对插入排序进行优化就是希尔排序。
在这里插入图片描述
希尔排序
在这里插入图片描述在这里插入图片描述
希尔排序,先根据长度除以2,然后不断推导,步长相同的组成一个组,对这个组内数据进行插入排序。
在这里插入图片描述

package org.example.sort;

public class ShellSort {
    public static void main(String[] args) {
        int[] nums = new int[]{9,12,45,67,10,4};
        shell(nums);
        //左右递归界限放进去
        for (int i:nums
        ) {
            System.out.println(i);

        }

    }

    public static void shell(int[] nums){
        //

        for(int gap = nums.length / 2; gap > 0;gap /= 2){//其实就是希尔排序外面套了一层这个

            //表示分多少组

//同时希尔排序的i+ 1,变成了gap
            for(int i = gap;i < nums.length;i++){
                int insert = nums[i];//记录这个需要进行对比的值
                int j = i - gap;//记录前面那个需要对比的值
                while (j >= 0 && nums[j] > insert){
                    //进行移位
                    nums[j + gap] = nums[j];//同组移位
                    j = j -gap;//移位之后往前走
                }
                //不移动位置说明找到了插入位置
                nums[j + gap] = insert;//插在那个位置后面
            }



        }

    }
}

package sortdemo;

import java.util.Arrays;

/**
 * Created by chengxiao on 2016/11/24.
 */
public class ShellSort {
    public static void main(String []args){
        int []arr ={1,4,2,7,9,8,3,6};
        sort(arr);
        System.out.println(Arrays.toString(arr));
        int []arr1 ={1,4,2,7,9,8,3,6};
        sort1(arr1);
        System.out.println(Arrays.toString(arr1));
    }

    /**
     * 希尔排序 针对有序序列在插入时采用交换法
     * @param arr
     */
    public static void sort(int []arr){
        //增量gap,并逐步缩小增量
       for(int gap=arr.length/2;gap>0;gap/=2){
           //从第gap个元素,逐个对其所在组进行直接插入排序操作
           for(int i=gap;i<arr.length;i++){
               int j = i;
               while(j-gap>=0 && arr[j]<arr[j-gap]){
                   //插入排序采用交换法
                   swap(arr,j,j-gap);
                   j-=gap;
               }
           }
       }
    }

    /**
     * 希尔排序 针对有序序列在插入时采用移动法。
     * @param arr
     */
    public static void sort1(int []arr){
        //增量gap,并逐步缩小增量
        for(int gap=arr.length/2;gap>0;gap/=2){
            //从第gap个元素,逐个对其所在组进行直接插入排序操作
            for(int i=gap;i<arr.length;i++){
                int j = i;
                int temp = arr[j];
                if(arr[j]<arr[j-gap]){
                    while(j-gap>=0 && temp<arr[j-gap]){
                        //移动法
                        arr[j] = arr[j-gap];
                        j-=gap;
                    }
                    arr[j] = temp;
                }
            }
        }
    }
    /**
     * 交换数组元素
     * @param arr
     * @param a
     * @param b
     */
    public static void swap(int []arr,int a,int b){
        arr[a] = arr[a]+arr[b];
        arr[b] = arr[a]-arr[b];
        arr[a] = arr[a]-arr[b];
    }
}

在这里插入图片描述

将所有的数都和前面j - gap
进行比较,通过比较,移动位置,每次遇到一个数,前面的gap都是同组得数
就是I ++ 之后,就和前面几个同组的数进行比较

快速排序算法
挑选左边第一个数为基准数,这样一会一定要先动右边的指针
在这里插入图片描述
left 交给L,让L指针移动,去寻找比key小的数字,然后停住
right交给r,让r移动,去寻找去key大的数字,然后停住
完成两边的交换
在这里插入图片描述

然后接着两个指针,让右边指针先动
继续找,一直到两个指针在某个瞬间相遇
相遇的时候说明左右两边的数据都已经排列好了
这时候将基准数换到中间来

在这里插入图片描述

最起码这个时候能保证,相遇的地方,左边都是比key小的,右边都是比key大的

让右边先动是为甚恶魔?
因为右边先动,右边先找到比较小的数据,先找到比较小的数据等着,这样交换的时候才能出现左边全是比中间那个位置小的数据,必须这样叫才行。
你要想好这个逻辑,右边先动
在这里插入图片描述再说一遍,我们的目的是让两个指针相遇之后,发生交换,然后交换之后,指针左边都是比指针小的,指针右边都是比指针大的这是我们整个程序设计的合理性所在。
随意必须是右边先动,这样右边先找到一个小的位置,右边是找小的,所以必须右边先动,右边以key为基准找到一个小的然后停住,等着左边的指针来找他才行,必须是这样,这样交换发生时候,才能保证,所有左边都小于交换后位置,右边都大于交换后位置
在这里插入图片描述交换完成后,向左递归,
然后向右边递归
在这里插入图片描述
递归终止条件是left >= right

package com.kuang.pojo;

public class QuickSort {

    public static void main(String[] args) {
        int arr[] = {1,3,7,11,6,1,15,8,10};
        quickSort(arr,0,arr.length - 1);
        for(int i = 0;i < arr.length;i++){
            System.out.println(arr[i]);
        }

    }

    public static void quickSort(int[] arr,int left,int right){

        if(left >= right ){
            return;//递归终止条件
        }
        //告诉我左右索引分别是什么
        int l = left;
        int r = right;
        int key = arr[left];//每次都取到最左边那个作为key




        while(l < r ){
            //两个指针移动终结条件

            //光靠外面那一个控制l和r不重叠不行,跑的时候l和r就有可能重叠
            while(arr[r] >=  key && r > l ){
                r--;//右边找到比key小的才会停住


            }//右边先找到自己位置,遇到和Key相等的数据不用管他,交给左边占据,交换

            while(arr[l] < key && r > l){//这里也要控制指针不能相等
                l++;//左边找到比key大或者等于key的才会停住


            }//左边先找自己的位置


            //都找完了


            if(l < r){//当两个没相遇时候,交换然后接着走
                //将两边数据交换一下
                int temp = arr[r];
                arr[r] = arr[l];
                arr[l] = temp;
            }
            else{
                break;//直接跳出去,要交换key了

            }


        }
        //先将key和两个指针相遇地方进行交换
        int temp = key;
        arr[left] = arr[l];
        arr[l] = key;
        //交换完成之后排序就完成了,然后开始左右递归




        quickSort(arr,left, l - 1);//向左递归,左边写死
        quickSort(arr,l + 1,right);//右边写死,向右边递归


    }

}

快速排序的平均时间复杂度和最坏时间复杂度分别是O(nlgn)、O(n^2)。

当排序已经成为基本有序状态时,快速排序退化为O(n^2),一般情况下,排序为指数复杂度。

快速排序最差情况递归调用栈高度O(n),平均情况递归调用栈高度O(logn),而不管哪种情况栈的每一层处理时间都是O(n),所以,平均情况(最佳情况也是平均情况)的时间复杂度O(nlogn),最差情况的时间复杂度为O(n^2)。

排序算法优缺点
1.冒泡排序
在这里插入图片描述

  首先,排序算法的稳定性大家应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同。在简单形式化一下,如果Ai = Aj, Ai原来在位置前,排序后Ai还是要在Aj位置前。

 其次,说一下稳定性的好处。排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,对基于比较的排序算法而言,元素交换的次数可能会少一些(个人感觉,没有证实)。

 回到主题,现在分析一下常见的排序算法的稳定性,每个都给出简单的理由。

(1)冒泡排序

    冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。

(2)选择排序

  选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法。

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

(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,一般取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。如果i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,很有可能把前面的元素的稳定性打乱,比如序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。

(5)归并排序
归并排序是把序列递归地分成短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),然后把各个有序的段序列合并成一个有序的长序列,不断合并直到原序列全部排好序。可以发现,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也没有人故意交换,这不会破坏稳定性。那么,在短的有序序列合并的过程中,稳定是是否受到破坏?没有,合并过程中我们可以保证如果两个当前元素相等时,我们把处在前面的序列的元素保存在结果序列的前面,这样就保证了稳定性。所以,归并排序也是稳定的排序算法。

(6)基数排序
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。

(7)希尔排序(shell)
希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。由于多次插入排序,我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

(8)堆排序
我们知道堆的结构是节点i的孩子为2i和2i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择当然不会破坏稳定性。但当为n/2-1, n/2-2, …1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没有交换,那么这2个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法

发生大范围交换的排序方法都不稳定
比如
选择排序,大范围交换
希尔排序
基数排序
选择排序
这都是隔着很远就发生了交换的
所谓的大范围传球容易失误是也。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
十种排序算法介绍 转自:matrix67 今天我正式开始挄照我癿目彔写我癿 oi 心得了。我要把我所有学到癿 oi 知识传给以后千千万万癿 oier。以前写过 癿一些东西丌重复写了,但我最后将会重新整理,使乊成为一个完整癿教程。 ???? 挄照我癿目彔,讲仸何东西乊前我都会先介绍旪间复杂度癿相关知识,以后劢丌劢就会扯到这个东西。这个 已经写过了,你可以在这里看到那篇又臭又长癿文章。在讲排序算法癿过程中,我们将始终围绕旪间复杂度癿内容 迚行说明。 ???? 我把这篇文章称乊为“仍零开始学算法”,因为排序算法是最基础癿算法,介绍算法旪仍各种排序算法入手是 最好丌过癿了。 ???? 给出 n 个数,怎样将它们仍小到大排序?下面一口气讲三种常用癿算法,它们是最简单癿、最显然癿、最容 易想到癿。选择排序(selection sort)是说,每次仍数列中找出一个最小癿数放到最前面来,再仍剩下癿 n-1个数 中选择一个最小癿,丌断做下去。揑入排序(insertion sort)是,每次仍数列中取一个还没有取出过癿数,幵挄照 大小关系揑入到已经取出癿数中使得已经取出癿数仌然有序。冒泡排序(bubble sort)分为若干趟迚行,每一趟排 序仍前往后比较每两个相邻癿元素癿大小(因此一趟排序要比较 n-1对位置相邻癿数)幵在每次发现前面癿那个数 比紧接它 后癿数大旪交换位置;迚行足够多趟直到某一趟跑完后发现这一趟没有迚行仸何交换操作(最坏情况下 要跑 n-1趟,这种情况在最小癿数位亍给定数列癿最后面旪 发生) 。事实上,在第一趟冒泡结束后,最后面那个数 肯定是最大癿了,亍是第二次叧需要对前面 n-1个数排序,这又将把这 n-1个数中最小癿数放到整个数列 癿倒数 第二个位置。这样下去,冒泡排序第 i 趟结束后后面 i 个数都已经到位了,第 i+1 趟实际上叧考虑前 n-i 个数(需 要癿比较次数比前面所说癿 n-1要 小) 。这相当亍用数学归纳法证明了冒泡排序癿正确性:实质不选择排序相同。 上面癿三个算法描述可能有点模糊了,没明白癿话网上找资料,代码和劢画演示遍地 都是。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值