八大排序算法总结

Posted 今天你学Java了吗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了八大排序算法总结相关的知识,希望对你有一定的参考价值。

八大排序

文章目录

八大排序有:冒泡排序、快速排序、希尔排序、直接插入排序、简单选择排序、堆排序、归并排序、基数排序

冒泡排序

基本思想

冒泡排序(Bubble Sort)就是一种简单排序,一次比较相邻两个元素,将大数置后,如水开的气泡从小到大进行排序,重复走访排序数列,直到没有要交换为止,即数列已经排序完成。

算法描述

  1. 比较相邻的元素,如果第一个比第二个大,就交换他们两个
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

代码实现

public class BubbleSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    

    private static void sort(int[] numbers) 
        for (int i = 0; i < numbers.length - 1; i++) 
            //内层循环控制到达位置
            for (int j = 0; j < numbers.length - 1 - i; j++) 
                //比较前面的元素比后面元素大交换,同理也可以比较小
                if (numbers[j] > numbers[j + 1]) 
                    int temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                
            
        
    

代码优化

可能在最后几趟已经排序好了,还在继续排序操作,所以我们可以在交换的地方加一个标记,如果那一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去。

public class BubbleSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    

    private static void sort(int[] numbers) 
        for (int i = 0; i < numbers.length - 1; i++) 
            boolean flag = false;
            for (int j = 0; j < numbers.length - 1 - i; j++) 
                if (numbers[j] > numbers[j + 1]) 
                    int temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                    flag = true;
                
            
            if (!flag) 
                break;
            
        
    

复杂度分析

平均时间复杂度最好情况最坏情况空间复杂度
O(n²)O(n)O(n²)O(1)

冒泡排序是最容易实现的排序, 最坏的情况是每次都需要交换, 共需遍历并交换将近n²/2次, 时间复杂度为O(n²). 最佳的情况是内循环遍历一次后发现排序是对的, 因此退出循环, 时间复杂度为O(n). 平均来讲, 时间复杂度为O(n²). 由于冒泡排序中只有缓存的temp变量需要内存空间, 因此空间复杂度为常量O(1).

快速排序

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要 Ο(nlogn) 次比较。在最坏状况下则需要 Ο(n2) 次比较,但这种状况并不常见。事实上,快速排序通常明显比其他 Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

基本思想

快速排序的基本思想:挖坑填数+分治法

快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。是一种分而治之思想在排序算法上的典型应用。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法

快速排序的名字起的是简单粗暴,因为一听到这个名字你就知道它存在的意义,就是快,而且效率高!它是处理大数据最快的排序算法之一了。虽然 Worst Case 的时间复杂度达到了 O(n²),但是人家就是优秀,在大多数情况下都比平均时间复杂度为 O(n logn) 的排序算法表现要更好。

算法描述

快速排序使用分治策略来把一个序列(list)分为两个子序列(sub-lists)。步骤为:

  1. 从数列中挑出一个元素,称为“基准”
  2. 重新排序数列,所有比基准值小的元素摆在基准的前面,所有比基准大的元素摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  3. 递归(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
  4. 递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去

代码实现

递归版本

//递归版本  的快速排序,
//通过基准值来插入合适位置实现分治,递归对分治后的两个划分继续快排
public class QuickSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers,0,numbers.length-1);
        System.out.println(Arrays.toString(numbers));

    
    private static void sort(int[] a, int low, int high) 
        //这里的low 和hight 传递的是数组的起始位置0,和数组的结束位置length-1
        //已经排完
        if (low >= high) 
            return;
        
        int left = low;//起始位置0,这位置是改变的
        int right = high;//结束位置length-1,这位置是改变的

        //保存基准值
        //这里是把起始位置的数,设置为基准数,如60
        int pivot = a[left];
        while (left < right) 
            //从后向前找到比基准小的元素
            while (left < right && a[right] >= pivot)
                right--;
            a[left] = a[right];
            //从前往后找到比基准大的元素
            while (left < right && a[left] <= pivot)
                left++;
            a[right] = a[left];
        
        // 放置基准值,准备分治递归快排
        a[left] = pivot;
        QuickSort(a, low, left - 1);
        QuickSort(a, left + 1, high);
    


非递归版本

递归的本质就是栈,那么非递归版本实现可借助栈来保存中间变量实现非递归。

中间变量也就是通过 partition函数划分区间之后分成左右两部分的首尾指针,只需要保存这两部分的首尾指针即可。

//非递归版本
public class QuickSortByStack 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    
    private static void sort(int[] a) 
        Stack<Integer> stack = new Stack<Integer>();//定义栈

        //初始状态的左右指针入栈
        stack.push(0);
        stack.push(a.length - 1);
        while (!stack.isEmpty()) 
            //出栈进行划分
            int high = stack.pop();
            int low = stack.pop();

            int pivotIndex = partition(a, low, high);

            //保存中间变量
            if (pivotIndex > low) 
                stack.push(low);
                stack.push(pivotIndex - 1);
            
            if (pivotIndex < high && pivotIndex >= 0) 
                stack.push(pivotIndex + 1);
                stack.push(high);
            
        
    
    private static int partition(int[] a, int low, int high) 
        if (low >= high) return -1;
        int left = low;
        int right = high;
        //保存基准的值
        int pivot = a[left];//最左边的数值为基准
        while (left < right) 
            //从后向前找到比基准小的元素,插入到基准位置中
            while (left < right && a[right] >= pivot) 
                right--;
            
            a[left] = a[right];
            //从前往后找到比基准大的元素
            while (left < right && a[left] <= pivot) 
                left++;
            
            a[right] = a[left];
        
        //放置基准值,准备分治递归快排
        a[left] = pivot;
        return left;
    

复杂度分析

平均时间复杂度最好情况最坏情况空间复杂度
O(nlog₂n)O(nlog₂n)O(n²)O(nlog₂n)

冒泡排序和快速排序属于交换排序

直接插入排序

基本思想

就像玩扑克一样,拿起一张牌会插入到已经有序的牌中的适当位置,组成顺子,在计算机中为了要给插入的元素腾出空间,需将其余元素向右移动一位

算法描述

插入排序采用in-place在数组上实现。具体算法描述如下:

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

代码实现

public class InsertSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    
    private static void sort(int[] numbers) 
        for (int i=0;i<numbers.length-1;i++)
            //外层循环控制比较次数
            for (int j=i+1;j>0;j--)
                //从第二个元素开始比较
                if (numbers[j]<numbers[j-1]) 
                    //例如第一次,如果第2个元素小于默认排好的第一个元素
                    int temp=numbers[j];//第二个元素赋值给临时变量
                    numbers[j]=numbers[j-1];//第一个元素赋值给第二元素
                    numbers[j-1]=temp;//临时变量赋值给第一个元素

                
            
        
    

二分查找

public class binaryInsertSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    
    private static void sort(int[] numbers) 
        if (numbers != null && numbers.length > 1) 
            for (int i = 1; i < numbers.length; i++) 
                int left = 0;
                int right = i-1;
                int mid;
                int temp = numbers[i];
                if(temp < numbers[right])   // 当前值小于有序序列的最大值时,开始查找插入位置
                    while(left <= right)
                        mid = (left + right)/2;
                        if(numbers[mid] < temp)
                            left = mid + 1;    // 缩小插入区间
                        else if(numbers[mid] > temp)
                            right = mid - 1;    // 缩小插入区间
                        else        // 待插入值与有序序列中的target[mid]相等,保证稳定性的处理
                            left = left + 1;
                        
                    

                    // left及其后面的数据顺序向后移动,并在left位置插入
                    for (int j = i; j > left; j--) 
                        numbers[j] = numbers[j-1];
                    
                    numbers[left] = temp;
                
            
        
    

复杂度分析

平均时间复杂度最好情况最坏情况空间复杂度
O(n²)O(n)O(n²)O(1)

希尔排序

希尔排序,也称 递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  • 插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率。
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一

希尔排序是先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

基本思想

将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次再将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。步长的选择是希尔排序的重要部分。只要最终步长为1任何步长序列都可以工作。一般来说最简单的步长取值是初次取数组长度的一半为增量,之后每次再减半,直到增量为1

算法描述

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

代码实现

//希尔排序 针对有序序列在插入时采用交换法
public class ShellSort 
    public static void main(String[] args) 
        int[] numbers = 1, 2, 43, 45, 6, 27, 12, 9, 7, 2, 16;
        sort(numbers);
        System.out.println(Arrays.toString(numbers));

    
    private 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];
    


    private 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;
                
            
        
    

(Java)八大排序算法总结

目录

1. 概述

2. 插入排序

2.1 直接插入排序

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

3. 选择排序

3.1 直接选择排序

3.2 堆排序

4. 交换排序

4.1 冒泡排序

4.2 快速排序

4.2.1. 思想

4.2.2 三种分割方式

4.2.3 快速排序的优化

4.2.4 快速排序的非递归方式

4.2.5 快速排序的特性总结 

5. 归并排序

6. 计数排序(非比较类型的排序)


1. 概述

排序概念:就是将一串记录按照其中某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:通俗的将就是数据元素不发生有间隔的交换,例如:

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

外部排序:数据元素太多不能一次加载到内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

注意:以下的排序默认排升序。默认待排序列为:2,5,1,3,8,6,9,4,7,0,6 

2. 插入排序

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

2.1 直接插入排序

1. 思想:

对于一个元素,将其与前面元素进行比较,将其插入到合适的位置。

排升序:将待排元素依次与序列中元素从右往左比,若待排元素小,则继续往前比,找到合适位置插入,原来元素的位置按顺序往后搬移。

2. 画图说明:

说明:第一个元素默认它有序,所以从第二个元素开始进行比较然后插入,5比2大,继续下一个,将1依次比较插入2前面,将3依次比较插入5前面,8比5大,不用管,继续下一个,将6依次比较插在8面,9比8大,不用管,将7依次比较,插在8前面,将0依次比较插在1前面,将6依次比较插在7前面,插入完成。 

3.代码展示:

public class InsertSort 
    public static void insertSort(int[] array)
        for(int i = 1;i < array.length;i++)    //让从1下标开始,因为如果只有一个元素,它自己默认有序
            int key = array[i];          //从数组中的第二个元素开始比较,进行插入
            int end = i-1;        //end的位置是要插入元素的前一个位置
            while(end >= 0 && key < array[end])    //end不能越界,找待插入的位置,此处注意:key不能小于等于array[end],否则为不稳定
                array[end+1] = array[end];
                end--;
            
            array[end+1] = key;         //找到位置进行插入,因为上面end--了,所以这里要插入的位置为end+1
        
    

    public static void main(String[] args) 
        int[] array = 2,5,1,3,8,6,9,4,7,0,6;
        insertSort(array);
        for(int i : array)
            System.out.print(i+" ");
        
    

结果:

4.特性总结:

时间复杂度:循环嵌套,O(N^2),最优情况:当序列接近有序,插入的元素比较少,为O(N)

空间复杂度:不借助辅助空间,O(1)

稳定性:数据元素不发生有间隔的交换,稳定

应用场景:数据量小,接近有序

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

1. 思想:

选一个整数为数据元素的间隔,将间隔相同的数据元素分为一组,分别对这些组进行插入排序,间隔减小,重复上述操作,当间隔减少到1的时候,数据元素即排好序了。

2. 画图说明:

说明:gap为间隔,这里依次将gap=4,2,1,先把间隔为4的数据元素用插入排序排好序,再利用插入排序排gap=2 的数据元素,依次重复上述操作,即可。

3. 代码展示:

public class ShellSort 
    public static void shellSort(int[] array)
        int gap = array.length;
        while(gap > 1)
            gap = gap/3+1;
            for(int i = gap;i < array.length;i++)  //跟插入排序一样,都从一组数的第二个元素开始,因为第一个数默认有序
                int key = array[i];
                int end = i - gap;        //end的位置也是代排数的前一个,但这里间隔为gap,所以前一个位置为i-gap
                while(end >= 0 && key < array[end])   //与插入排序保持不变,找待插入的位置
                    array[end+gap] = array[end];    // key小于前一个数,让end位置的元素往后移一位,
                    end -= gap;    //end每次向左走的时候一次走gap的间隔
                
                array[end+gap] = key;    //找到待排位置将key插入相应位置
            
        
    

    public static void main(String[] args) 
        int[] array = 2,5,1,3,8,6,9,4,7,0,6;
        shellSort(array);
        for(int i : array)
            System.out.print(i+" ");
        
    

结果:

4. 特性总结:

希尔排序是对直接插入排序的优化。

当gap>1时都是预排序,目的是让数组元素接近有序,当gap==1时,数组已经接近有序了,这样插入排序将会很快,整体而言,达到了优化的效果。

希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算。

gap的取法有多种,最初shell提出取gap=n/2,gap=gap/2,直到gap=1,后来,Kunth提出取gap = [gap/3]+1。在Kunth所著的《计算机程序设计技巧》第3卷中,利用大量的实验统计资料得出,当n很大时,关键码的平均比较次数和对象平均移动次数大约在n^1.25到1.6n^1.25范围内,这是利用直接插入排序作为子序列排序方法的情况下得到的。

时间复杂度暂记为:O(N^1.25)~O(1.6N^1.25)

空间复杂度:没有借助辅助空间,O(1)

稳定性:数据元素发生了有间隔的交换,不稳定

应用场景:数据比较随机

3. 选择排序

每一次从待排数据元素中选出最小(最大)的元素,存放在序列的起始(末尾)位置,直到全部待排序的数据元素全部排完。

3.1 直接选择排序

1. 思想:

思路1:

找出序列中的最大的元素,若它不在序列的末尾,将它与末尾元素交换位置,依次循环。

思路2:

每次找元素的时候,找一个最大的和一个最小的,最大的和最后一个交换位置,最小的和第一个交换位置,依次循环 

2. 画图说明:

思路1:

说明:先找到序列的最大元素与序列最后一个元素交换,序列的最后的位置朝前移一个,再找新序列的最大元素再与最后一个交换位置,依次循环。 

思路2:

说明:这种方法是一次找两个元素,跟上面那种本质上一样,只是一次交换了两个元素,将最大的与最后一个交换,将最小的与第一个交换 

3. 代码展示:

public class SelectSort 
    public static void selectSort1(int[] array)
        int size = array.length;
        for(int i = 0;i < size-1;i++)   //选择的趟数,size-1是因为当选择到序列剩一个元素时就不用选了
            int pos = 0;    //pos标记最大元素的位置,刚开始是默认为第一个位置
            for(int j = 1;j < size-i;j++)   //j=1是因为默认第一个元素的位置为最大元素的位置,所以从第二个元素的位置开始比较
                                             //size-i是因为每趟选择完后,序列都要减少一个
                if(array[j] > array[pos])
                    pos = j;    //找到最大元素位置,更新pos
                
            
            if(pos != size-i-1)   //如果最大元素的位置刚好是最后一个位置,则不需要交换,反之进行交换
                swap(array,pos,size-i-1);
            
        
    
    public static void selectSort2(int[] array)
        int left = 0;     //序列的左边界
        int right = array.length-1;   //序列的右边界
        while(left < right)   //当序列中至少存在俩元素时
            int minPos = left;   //刚开始将最小元素的位置设定为序列的第一个位置
            int maxPos = left;   //刚开始将最大元素的位置也设定为序列的第一个位置
            int index = left+1;  //从序列的第二个元素开始找最大元素和最小元素的位置
            //找最大元素和最小元素的位置
            while(index <= right)
                if(array[index] < array[minPos])
                    minPos = index;
                
                if(array[index] > array[maxPos])
                    maxPos = index;
                
                index++;
            
            if(minPos != left)  //当最小的元素不在序列的最左端时交换位置
                swap(array,minPos,left);
            
            //这里我是先交换最小的元素,但是如果最大的元素在最左侧,那么会将maxPos位置的元素更新为最小的元素,导致后面
            //交换最大元素的时候maxPos标记的元素不准确,所以下面将maxPos的位置更新了一下
            //注意:如果是先交换最大的元素,则更新minPos的位置
            if(maxPos == left)
                maxPos = minPos;
            
           // if(minPos == right)
           //     minPos = maxPos;
           // 
            if(maxPos != right)    //当最大元素不在序列的最右端时交换位置
                swap(array,maxPos,right);
            
            left++;  //左边界+1
            right--; //右边界-1
        
    
    public static void swap(int[] array,int a,int b)
        int t = array[a];
        array[a] = array[b];
        array[b] = t;
    

    public static void main(String[] args) 
        int[] array = 9,5,1,3,8,6,2,4,7,6,0;
        selectSort1(array);
        for(int i : array)
            System.out.print(i+" ");
        
        System.out.println();
        selectSort2(array);
        for(int i : array)
            System.out.print(i+" ");
        
    

4:特性总结:

时间复杂度:找元素,交换元素是循环嵌套,O(N^2)

空间复杂度:没有借助辅助空间,O(1)

稳定性:数据元素存在有间隔的交换,不稳定

缺陷:找最大元素或者最小元素的时候重复比较

3.2 堆排序

1. 思想:

堆排序是利用堆积树(堆)这种数据结构设计的一种算法,它是选择排序的一种,它是通过堆来进行选择数据。

注意:排升序,建大根堆,排降序,建小根堆

堆排序分为两部分建堆,利用向下调整建堆,再利用堆删除的思想排序。

向下调整:

前提:要调整的节点的两个左右子树都已满足堆的特性

方法:建大堆,将根的元素与左右孩子较大的元素交换,建小堆,将根的元素与左右孩子较小的元素交换

堆删除思想:

将堆顶元素与最后一个元素交换位置

堆中有效元素减少一个

再对堆顶元素向下调整 

2. 画图说明:

因为数据元素多,二叉树的图看着不太清晰,所以我用以下序列:0,5,3,4,1,2

建堆:

利用堆删除思想排序:

3. 代码展示:

public class HeapSort 
    //向下调整
    public static void shiftDown(int[] array,int parent,int size)
        int child = parent*2+1;  //默认将左孩子设为parent的孩子,因为不知道右边孩子是否存在
        while(child<size)
            if(child+1<size && array[child+1]>array[child])   //如果右孩子存在,比较哪个孩子值大
                child += 1;   //将child设为较大的孩子
            
            if(array[parent]<array[child])  //如果parent小,将parent与child交换
                swap(array,parent,child);
                parent = child;   //更新parent
                child = parent*2+1;   //更新child
            else    //如果已经满足堆特性则直接返回
                return;
            
        
    
    //堆排序
    public static void heapSort(int[] array)
        //建堆
        int size = array.length;
        int lastLeaf = ((size-1)-1)/2;  //找到倒数第一个非叶子节点
        for(int root=lastLeaf;root>=0;root--)    //从该结点开始,依次向下调整直到根节点
            shiftDown(array,root,size);
        
        //利用堆删除思想排序,从最后一个元素开始与堆顶元素交换,然后对堆顶元素进行向下调整
        int end = size-1;
        while(end>0)
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        
    
    public static void swap(int[] array,int a,int b)
        int t = array[a];
        array[a] = array[b];
        array[b] = t;
    

    public static void main(String[] args) 
        int[] array = 2,5,1,3,8,6,9,4,7,0,6;
        heapSort(array);
        for(int i : array)
            System.out.print(i+" ");
        
    

结果:

 

4. 特性总结:

时间复杂度:n个元素进行比较,而且太向下调整,调整的深度为完全二叉树的深度,故时间复杂度为:O(NlogN),logN是log以2为底的N

空间复杂度:没有借助辅助空间,O(1)

稳定性:元素发生了有间隔的交换,不稳定

应用场景:求前k个最小或者最大,可以和其他排序结合起来增加效率

4. 交换排序

基本思想就是根据序列中两个记录键值的比较结果来交换这两个记录在序列中的位置,交换排序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动

4.1 冒泡排序

1. 思想:

依次将序列元素进行比较,若array[i]>array[i+1],交换两个元素的位置,直到最后一个元素,从中可以得出,每躺冒泡只能确定一个元素的位置,若序列有n个元素,则需要进行n-1躺冒泡,因为序列最后一个元素的位置不用确定。

从比较的次数可以看出:第一躺比较的次数为n-1,第二躺的比较次数为n-2.....即每趟冒泡比较的次数在递减,即比较次数是为n-1-i,i为冒泡的趟数。

2. 画图说明:

3. 冒泡排序的优化:

从上图可以看出第10躺冒泡没有进行,因为序列已经有序,即数据元素不在发生交换。

优化:在每趟进行的开始给一个标记isChage=false,如果该躺冒泡发生了元素交换,将isChange=true,在每趟冒泡完进行判断,如果是Change==false,即没有发生交换,说明序列已经有序,即不用在继续冒泡,直接返回即可。

4. 代码展示:

public class BubbleSort 
    public static void bubbleSort(int[] array)
        int size = array.length;
        for(int i = 0;i < size-1;i++)
            boolean isChange = false;         //为了优化冒泡排序给的标记,当元素不发生交换的时候说明已经有序
            for(int j = 0;j < size-1-i;j++)
                if(array[j+1] < array[j])
                    swap(array,j,j+1);
                    isChange = true;
                
            
            if(isChange == false)     //如果元素不发生交换,直接返回
                return;
            
        
    
    public static void swap(int[] array,int a,int b)
        int t = array[a];
        array[a] = array[b];
        array[b] = t;
    

    public static void main(String[] args) 
        int[] array = 2,5,1,3,8,6,9,4,7,0,6;
        bubbleSort(array);
        for(int i : array)
            System.out.print(i+" ");
        
    

结果:

 

 

5. 特性总结: 

时间复杂度:冒泡的趟数,每趟的比较次数,O(N^2)

空间复杂度:不借助辅助空间,O(1)

稳定性:数据元素虽然发生了交换,但是没有间隔交换,稳定

4.2 快速排序

4.2.1. 思想

快速排序是Hoare提出的一种二叉树结构的交换排序方法。

基本思想为:任取待排元素序列中的某个元素作为基准值(这里我们取最右侧元素为基准值),按照该基准值将序列划分为两个区间,左侧比基准值小,右侧比基准值大,再分别用快速排序递归排左区间和右区间

参考代码:

public static void quickSort(int[] array,int left,int right)   //左闭右开
        if(right-left > 1)       //递归的返回条件,区间最少得有两个元素
            int div = partition(array,left,right);
            quickSort(array,left,div);    //递归排左侧
            quickSort(array,div+1,right);   //递归排右侧
        
    

故只要实现分割方法即可。 

4.2.2 三种分割方式

1. Hoare法

思想:在左边找比基准值大的,右边找比基准值小的,两个交换位置,最后将较大一侧的第一个元素与基准值交换位置。

画图说明:

参考代码:

 //分割区间方法1:hoare:左边找比基准值大的,右边找比基准值小的,两个交换位置,最后再将基准值放到中间的位置
    public static int partition1(int[] array,int left,int right)
        int key = array[right-1];   //找基准值,以最右侧元素为基准值
        int begin = left;    //在左侧找的下标
        int end = right-1;    //在右侧找的下标
        while(begin < end)
            while(array[begin] <= key && begin < end)  //左侧找大的,不能越界
                begin++;
            
            while(array[end] >= key && end > begin)    //右侧找小的,不能越界
                end--;
            
            if(begin != end)
                swap(array,begin,end);    //begin与end不在同一位置的时候交换,否则没有交换的必要
            
        
        if(begin != right-1)
            swap(array,begin,right-1);    //将基准值与begin位置的元素交换,begin或者end都行
        
        return end;        //将分割的位置返回,begin与end都可以
    

2. 挖坑法

思想:将基准值存入key中,基准值的位置为第一个坑位,在左侧找比基准值大的,放到第一个坑位上,此时这个元素的原始位置又形成了一个新的坑位,再从右侧找比基准值小的,放到前面的坑位上,依次循环,即将序列分割为两部分。

画图说明:

参考代码:

 //分割区间方法二:挖坑法:基准值是一个坑位,左侧找大的放到基准值的坑位上,此时形成了新的坑位,再在右侧找小的放到上一个坑位,依次循环
    public static int partition2(int[] array,int left,int right)
        int key = array[right-1];  //取最右侧元素为基准值,end的位置形成了第一个坑位
        int begin = left;
        int end = right-1;
        while(begin < end)
            while(begin < end && key >= array[begin])   //在左侧找比基准值大的
                begin++;
            
            if(begin < end)
                array[end] = array[begin];   //填end坑位,重新生成begin坑位
            
            while(begin < end && key <= array[end])    //在右侧找比基准值小的
                end--;
            
            if(begin < end)
                array[begin] = array[end];    //填begin坑位,重新生成end坑位
            
        
        array[begin] = key;     //将基准值填充在最后一个坑位
        return end;
    

3. 前后标记法

思想:给两个标记cur,pre,pre标记cur的前一个,cur开始标记第一个元素,依次往后走,cur小于区间的右边界,如果cur小于基准值并且++pre不等于cur交换cur与pre位置的元素,最后交换++pre与基准值位置的元素。

画图说明:

参考代码:

  //分割区间方法三:前后标记法
    public static int partition3(int[] array,int left,int right)
        int key = array[right-1];
        int cur = left;
        int pre = cur-1;
        while(cur < right)
            if(array[cur] < key && ++pre != cur)   //当cur位置的元素小于基准值并且++pre!=cur的时候,交换
                swap(array,cur,pre);
            
            cur++;
        
        if(++pre != right-1)        //++pre为与基准值交换的位置,所以当++pre!=right-1的时候,交换基准值的位置
            swap(array,pre,right-1);
        
        return pre;
    

4.2.3 快速排序的优化

快速排序的优化:对基准值的选取进行优化,这样做是为了选取的基准值尽可能的将区间的左右侧分的均匀一点儿。

优化方法:三数取中法取基准值,三数:区间的中间元素,最左侧元素,最右侧元素,取它们三个的中间值。

参考代码:

//快排优化:三数取中法来取基准值,左侧,右侧,中间这三个数的中间值来作为基准值,这里返回的是基准值下标
    public static int getIndexOfMid(int[] array,int left,int right)
        int mid = left+((right-left)>>1);
        if(array[left] < array[right-1])    //最右侧的下标为right-1
            if(array[mid] < array[left])
                return left;
            else if(array[mid] > array[right-1])
                return right-1;
            else
                return mid;
            
        else
            if(array[mid] < array[right-1])
                return right-1;
            else if(array[mid] > array[left])
                return left;
            else
                return mid;
            
        
    

具体与之前代码结合方式,我这里选取一种分割方法来进行优化:

参考代码:

//分割区间方法三:前后标记法
    public static int partition3(int[] array,int left,int right)
        int index = getIndexOfMid(array,left,right);   //用三数取中法获得基准值的下标
        if(index != right-1)
            swap(array,index,right-1);    //如果下表不在最右侧就交换
        
        int key = array[right-1];
        int cur = left;
        int pre = cur-1;
        while(cur < right)
            if(array[cur] < key && ++pre != cur)   //当cur位置的元素小于基准值并且++pre!=cur的时候,交换
                swap(array,cur,pre);
            
            cur++;
        
        if(++pre != right-1)        //++pre为与基准值交换的位置,所以当++pre!=right-1的时候,交换基准值的位置
            swap(array,pre,right-1);
        
        return pre;
    

4.2.4 快速排序的非递归方式

非递归的快速排序借助栈这一数据结构

参考代码:

 //非递归的快速排序,利用栈来保存分割的区间,传参只需要传数组即可
    public static void quickSort(int[] array)
        Stack<Integer> s = new Stack<>();
        s.push(array.length);
        s.push(0);
        while(!s.empty())
            int left = s.pop();
            int right = s.pop();
            if(right - left == 0)
                continue;
            
            int div = partition(array,left,right);
            s.push(right);
            s.push(div+1);
            s.push(div);
            s.push(left);
        
    

4.2.5 快速排序的特性总结 

时间复杂度:有比较,递归,O(NlogN)

空间复杂度:存在递归,递归的深度为完全二叉树的深度,O(logN)

稳定性:数据元素发生有间隔的交换,不稳定

应用场景:数据凌乱且随机

5. 归并排序

1. 思想:

归并排序是将两个有序序列进行合并,但是我们拿到是一个数组,所以得先将数组递归均分为两部分,再将两部分进行合并。

2. 画图说明:

递归均分:

从下往上合并:

3. 代码展示:

public class MergeSort 
    public static void mergeSort(int[] array,int left,int right,int[] temp)
        if(right - left > 1)
            int mid = left+((right-left)>>1);
            mergeSort(array,left,mid,temp);      //递归均分左侧
            mergeSort(array,mid,right,temp);      //递归均分右侧
            mergeData(array,left,mid,right,temp);  //合并两个序列,[left,mid) , [mid,right)
            System.arraycopy(temp,left,array,left,right-left); //拷贝到原数组,源,起始位置,新,起始位置,长度
        
    
    public static void mergeData(int[] array,int left,int mid,int right,int[] temp)
        //[begin1,end1),[begin2,end2),为两个合并的区间
        int begin1 = left;
        int end1 = mid;
        int begin2 = mid;
        int end2 = right;
        int index = left;   //辅助数组的下标
        while(begin1 < end1 && begin2 < end2)
            if(array[begin1] <= array[begin2])
                temp[index++] = array[begin1++];
            else
                temp[index++] = array[begin2++];
            
        
        while(begin1 < end1)
            temp[index++] = array[begin1++];
        
        while(begin2 < end2)
            temp[index++] = array[begin2++];
        
    
    //重新包装一下,便于用户传参
    public static void mergeSort(int[] array)
        int[] temp = new int[array.length];
        mergeSort(array,0,array.length,temp);
    

4. 非递归的归并排序

给一个间隔,用间隔来表示区间

参考代码:

 //非递归的归并排序
    public static void mergeSortNor(int[] array)
        int size = array.length;
        int[] temp = new int[size];
        int gap = 1;       //先每两个元素归并
        while(gap < size)
            for(int i = 0;i < size;i+=gap*2)
                int left = i;
                int mid = i+gap;
                int right = mid+gap;
                if(mid > size)    //防止mid越界
                    mid = size;
                
                if(right > size)   //防止right越界
                    right = size;
                
                mergeData(array,left,mid,right,temp);
            
            System.arraycopy(temp,0,array,0,size);
            gap <<= 1;
        
    

5. 特性总结:

时间复杂度:O(NlogN)

空间复杂度:借助了辅助空间,为辅助数组的大小,O(N)

稳定性:数据元素不发生有间隔的交换,稳定

应用场景:适合外部排序

6. 计数排序(非比较类型的排序)

1. 思想:

计数排序又称鸽巢原理,是对哈希直接定址法的变形应用

步骤:

统计相同元素出现次数

根据统计的结果将序列回收到原来的序列中

2. 画图说明:

  

3. 代码展示:

public class CountSort 
    public static void countSort(int[] array)
        //找出数组的最大值与最小值
        int maxNum = array[0];
        int minNum = array[0];
        for(int i = 1;i < array.length;i++)
            if(array[i] > maxNum)
                maxNum = array[i];
            
            if(array[i] < minNum)
                minNum = array[i];
            
        
        int range = maxNum - minNum + 1;   //序列元素的范围大小
        int[] count = new int[range];      //new一个范围大小的数组
        for(int i = 0;i < array.length;i++)
            count[array[i]-minNum]++;      //读取
        
        int index = 0;     //存入原数组的下标
        for(int i = 0;i < range;i++)
            while(count[i] > 0)
                array[index] = i + minNum;
                index++;
                count[i]--;
            
        
    

4. 性能总结:

时间复杂度:O(N)

空间复杂度:O(M),M为数据范围的大小

稳定性:数据元素没有进行有间隔的交换,稳定

应用场景:数据元素集中在某个范围

以上是关于八大排序算法总结的主要内容,如果未能解决你的问题,请参考以下文章

Java实现八大排序算法

八大排序算法Java实现

八大排序算法Java(转)

八大排序算法总结

八大排序算法总结与Java实现

八大排序算法总结