排序算法十大经典大集合:简介-代码-动态图-时间复杂度

Posted 小兀哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法十大经典大集合:简介-代码-动态图-时间复杂度相关的知识,希望对你有一定的参考价值。

交换排序

      就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。
      包括冒泡排序,和快速排序

代码

public static void swapSort(int[] array)
   for(int i=0; i<array.length-1; i++)
   for(int j=i+1; j<array.length; j++)
      if(array[i]>array[j])
        int temp=array[i];
        array[i]=array[j];
        array[j]=temp;
      
   
   

冒泡排序(Bubble Sort)

      从尾部开始比较相邻的两个元素,如果尾部的元素比前面的大,就交换两个元素的位置。
      往前对每个相邻的元素都做这样的比较、交换操作,这样到数组头部时,第 1 个元素会成为最大的元素。
      重新从尾部开始第 1、2 步的操作,除了在这之前头部已经排好的元素。
继续对越来越少的数据进行比较、交换操作,直到没有可比较的数据为止,排序完成。

代码

	public static void bubbleSort(int arr[]) 

        for(int i =0 ; i<arr.length-1 ; i++) 
            for(int j=0 ; j<arr.length-1-i ; j++) 
                if(arr[j]>arr[j+1]) 
                    int temp = arr[j];
                    arr[j]=arr[j+1];
                    arr[j+1]=temp;
                
            
        
    

算法描述

      步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
      步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
      步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
      步骤4: 重复步骤1~3,直到排序完成。

算法分析

      最佳情况:T(n) = O(n)
      最差情况:T(n) = O(n2)
      平均情况:T(n) = O(n2)

效果

快速排序(Quick Sort)

      基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

代码

public static int[] qsort(int arr[],int start,int end)         
    int pivot = arr[start];        
    int i = start;        
    int j = end;        
    while (i<j)             
        while ((i<j)&&(arr[j]>pivot))                 
            j--;            
                    
        while ((i<j)&&(arr[i]<pivot))                 
            i++;            
                    
        if ((arr[i]==arr[j])&&(i<j))                 
            i++;            
         else                 
            int temp = arr[i];                
            arr[i] = arr[j];                
            arr[j] = temp;            
                
            
    if (i-1>start) arr=qsort(arr,start,i-1);        
    if (j+1<end) arr=qsort(arr,j+1,end);        
    return (arr);    
   

算法描述

      步骤1:从数列中挑出一个元素,称为 “基准”(pivot );
      步骤2:重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
      步骤3:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

算法分析

      最佳情况:T(n) = O(nlogn)
      最差情况:T(n) = O(n2)
      平均情况:T(n) = O(nlogn)

效果

插入排序

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

代码

public static void sort(Comparable[] a)
    
        //将a[]按升序排列
        int N=a.length;
        for (int i=1;i<N;i++)
        
        //将a[i]插入到a[i-1],a[i-2],a[i-3]……之中
            for(int j=i;j>0&&(a[j].compareTo(a[j-1])<0);j--)
            
                Comparable temp=a[j];
                a[j]=a[j-1];
                a[j-1]=temp;
            
        
    

直接插入排序(Insertion Sort)

      直接插入排序(Straight Insertion Sort)是一种最简单的排序方法,其基本操作是将一条记录插入到已排好的有序表中,从而得到一个新的、记录数量增1的有序表。

代码

public void insertSort(int[] array)
        for(int i=1;i<array.length;i++)//第0位独自作为有序数列,从第1位开始向后遍历
        
            if(array[i]<array[i-1])//0~i-1位为有序,若第i位小于i-1位,继续寻位并插入,否则认为0~i位也是有序的,忽略此次循环,相当于continue
            
                int temp=array[i];//保存第i位的值
                int j = i - 1;
                while(j>=0 && temp<array[j])//从第i-1位向前遍历并移位,直至找到小于第i位值停止
                
                    array[j+1]=array[j];
                    j--;
                
                array[j+1]=temp;//插入第i位的值
            
         
    

算法描述

      步骤1: 从第一个元素开始,该元素可以认为已经被排序;
      步骤2: 取出下一个元素,在已经排序的元素序列中从后向前扫描;
      步骤3: 如果该元素(已排序)大于新元素,将该元素移到下一位置;
      步骤4: 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
      步骤5: 将新元素插入到该位置后;
      步骤6: 重复步骤2~5。

算法分析

      最佳情况:T(n) = O(n)
      最坏情况:T(n) = O(n2)
      平均情况:T(n) = O(n2)

效果

希尔排序(Shell Sort)

      希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。

代码

public static int[] ShellSort(int[] array) 
        int len = array.length;
        int temp, gap = len / 2;
        while (gap > 0) 
            for (int i = gap; i < len; i++) 
                temp = array[i];
                int preIndex = i - gap;
                while (preIndex >= 0 && array[preIndex] > temp) 
                    array[preIndex + gap] = array[preIndex];
                    preIndex -= gap;
                
                array[preIndex + gap] = temp;
            
            gap /= 2;
        
        return array;

算法描述

      步骤1:选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
      步骤2:按增量序列个数k,对序列进行k 趟排序;
      步骤3:每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

算法分析

      最佳情况:T(n) = O(nlog2 n)
      最坏情况:T(n) = O(nlog2 n)
      平均情况:T(n) =O(nlog2n)

效果

选择排序

简单选择排序 (Selection Sort)

      选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零

代码

public static void selectionSort(int[] arr) 
        int min, temp;
        for (int i = 0; i < arr.length; i++) 
            // 初始化未排序序列中最小数据数组下标
            min = i;
            for (int j = i+1; j < arr.length; j++) 
                // 在未排序元素中继续寻找最小元素,并保存其下标
                if (arr[j] < arr[min]) 
                    min = j;
                
            
            // 将未排序列中最小元素放到已排序列末尾
            if (min != i) 
                temp = arr[min];
                arr[min] = arr[i];
                arr[i] = temp;
            
        
    

算法描述

      步骤1:初始状态:无序区为R[1…n],有序区为空;
      步骤2:第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
步骤3:n-1趟结束,数组有序化了。

算法分析

      最佳情况:T(n) = O(n2)
      最差情况:T(n) = O(n2)
      平均情况:T(n) = O(n2)

效果

堆排序(Heap Sort)

      堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。

代码

 * 选择排序-堆排序
    * @param array 待排序数组
    * @return 已排序数组
    */
    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;
    
  
    /**
    * 整个堆排序最关键的地方
    * @param array 待组堆
    * @param i 起始结点
    * @param length 堆的长度
    */
    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的左子树(因为i是从0开始的),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;
            
        
    
  
    /**
    * 交换元素
    * @param arr
    * @param a 元素的下标
    * @param b 元素的下标
    */
    public static void swap(int[] arr, int a, int b) 
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    

算法描述

      步骤1:将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
      步骤2:将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
      步骤3:由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

算法分析

      最佳情况:T(n) = O(nlogn)
      最差情况:T(n) = O(nlogn)
      平均情况:T(n) = O(nlogn)

效果

归并

归并排序(Merge Sort)

      归并排序(Merge Sort)是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

代码

public class MergeSort 
    public static void main(String[] args) 
    
        int arr[] = 8, 4, 5, 7, 1, 3, 6, 2;
        int temp[] = new int[arr.length]; //归并排序需要一个额外空间
        
        mergeSort(arr, 0, arr.length - 1, temp);
        System.out.println("排序后的结果为:" + Arrays.toString(arr));
	


    //分 + 合方法:先递归分,再合
    public static void mergeSort(int[] arr, int left, int right, int[] temp) 
        if(left < right) 
            int mid = (left + right) / 2; //中间索引
            //向左递归进行分解
            mergeSort(arr, left, mid, temp);
            //向右递归进行分解
            mergeSort(arr, mid + 1, right, temp);
            //合并
            merge(arr, left, mid, right, temp);
        
    

    /**
     * 合并的方法
     * @param arr 待排序的原始数组
     * @param left 左边有序序列的初始索引
     * @param mid 中间索引
     * @param right 右边索引
     * @param temp 做中转的数组
     */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) 

        int i = left; // 初始化i, 左边有序序列的初始索引
        int j = mid + 1; //初始化j, 右边有序序列的初始索引
        int t = 0; // 指向temp数组的当前索引

        //(一)
        //先把左右两边(有序)的数据按照规则填充到temp数组
        //直到左右两边的有序序列,有一边处理完毕为止
        while (i <= mid && j <= right)  //继续
            //如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
            //即将左边的当前元素,填充到temp数组
            //然后 t++, i++
            if(arr[i] <= arr[j]) 
                temp[t] = arr[i];
                t = t + 1;
                i = i + 1;
             else  //反之,将右边有序序列的当前元素,填充到temp数组
                temp[t] = arr[j];
                t += 1;
                j += 1;
            
        

        //(二)
        //把有剩余数据的一边的数据依次全部填充到temp
        while( i <= mid)  //左边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[i];
            t += 1;
            i += 1;
        
        while( j <= right)  //右边的有序序列还有剩余的元素,就全部填充到temp
            temp[t] = arr[j];
            t += 1;
            j += 1;
        

        //(三)
        //将temp数组的元素拷贝到arr
        //注意,并不是每次都拷贝所有,这句话和递归有关,要理解有一定的难度
        //因为这里并不是全部分完之后再合,而是分一点合一点
        t = 0;
        int tempLeft = left; //
        //第一次合并 tempLeft = 0 , right = 1 //  tempLeft = 2  right = 3 // tL=0 ri=3
        //最后一次 tempLeft = 0  right = 7
        while(tempLeft <= right) 
            arr[tempLeft] = temp[t];
            t += 1;
            tempLeft += 1;
        
    

算法描述

      步骤1:把长度为n的输入序列分成两个长度为n/2的子序列;
      步骤2:对这两个子序列分别采用归并排序;
      步骤3:将两个排序好的子序列合并成一个最终的排序序列。

算法分析

      最佳情况:T(n) = O(n)
      最差情况:T(n) = O(nlogn)
      平均情况:T(n) = O(nlogn)

效果

非比较排序-牺牲空间

计数排序(Counting Sort)

      计数排序是一个非基于比较的排序算法。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

以上是关于排序算法十大经典大集合:简介-代码-动态图-时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章

买什么数据结构与算法,这里有:动态图解十大经典排序算法(含JAVA代码实现)

花一个晚上时间整理,十大经典排序算法(Python版本),拿起就用

(附代码)动图图解 | 十大经典排序算法Python版实现

干货收藏:一文掌握十大经典排序算法(动态演示+代码)

十大经典排序算法(上)

十大经典排序算法(上)