常见排序算法以及复杂度

来源:互联网 发布:lcd1602数据口反了 编辑:程序博客网 时间:2024/05/20 19:15

前言:

今天在网上看到一条面试题,是关于常见排序算法和各自负杂度的,考虑到如果自己碰到这道题也未必能回答好,在这里对这方面知识巩固学习、整理了一下。

概述:

排序算法很多种,今天这里就几种常见的排序算法进行学习了解。


原理及实现:

一、插入排序

(1)直接插入排序:

基本思想:有一个已经有序的数据序列,要在这个数据序列里面插入一个数据,并且要求插入后的数据序列增1且任然有序。即:我们把数据序列的第一个数看成是一个有序的数据序列,从第二个数开始,逐个添加到前面有序序列的合适位置,直到整个变成有序的序列为止。

要点:每次比较要取一个参照,作为临时存储判断数组的边界。

如下图,展示了插入排序的简易过程。


优点:稳定,快。如果碰到和待插入元素相等的,待插入元素插在后面,所以序列中相等的元素在排序前后的顺序没有改变,所以直接插入排序是稳定的。

缺点:比较次数不一定,比较次数越少,插入点后的数据移动越多,特别是当数据量庞大的时候。用链表可以解决这一问题。

具体实现:

     static void directSort(int []array){  //打印初始数据System.out.print("初始数据序列:");for(int a:array){System.out.print(a+" ");}System.out.println("");//直接插入排序实现for(int i = 1;i < array.length;i++){int temp = array[i];//参照,也是我们的待插入元素int j;for(j = i - 1;j >= 0 && array[j] > temp;j--){//将前面大的值向后移array[j+1] = array[j];}array[j+1] = temp;//插入元素//打印当前排序结果System.out.print("第"+i+"次排序"+"(参照:"+temp+")"+":");for(int a:array){System.out.print(a+" ");}System.out.println("");}//打印最终数据序列System.out.print("最终数据序列:");                for(int a:array){               System.out.print("  "+a);                }       }

输出结果:


效率:

时间复杂度:

最坏情况:数据序列完全逆序,插入新的元素的时候,需要判断已有有序数据序列的全部数据。插入第二个元素的时候需要考察前一个元素,插入第三个元素的时候需要考察前两个元素,.....,插入第n个元素的时候,需要考察前n-1个元素,比较次数是1+2+3+.....+(n-1),等差数列求和,结果为n^2/2,所以最坏情况下的复杂度为O(n^2)。

最好情况:数据序列已经有序,每插入一个新的元素的时候,只要考察前面一个元素,所以复杂度为O(n)。

平均情况:O(n^2)。

空间复杂度:

需要的辅助空间大小是O(1)。

(2)二分法插入排序:

基本思路:二分法插入排序的基本思路和直接插入排序的基本思路是相同的,不同点在于寻找插入的位置,直接插入排序是用插入元素和已有有序序列的元素从后向前,一个个比较,找到合适位置。而二分法插入排序是用二分查找法来寻找插入的位置,用待插入元素和已有有序数据序列的中间元素进行比较,以有序序列的中间元素作为分界,确定待插入元素是在查找序列的左边还是右边,如果在左边,就以左边的序列作为查找序列,右边类似。递归处理新序列,直到当前查找序列的长度小于1结束。

如下图,取最后一个数据的判断来示例一下过程:


优点:稳定。

缺点:比较次数是固定的,和记录的初始顺序无关,在最坏情况下比直接插入排序快,最好情况下比直接插入排序慢。

具体实现:

        static void Sort(int []array){  //二分法插入排序实现int left = 0;//左边界int right = array.length - 1;//右边界int temp;//临时值int low,high;//区间边界值int middle;//中间值for(int i = left + 1;i <= right;i++){temp = array[i];low = left;high = i - 1;//二分法判断插入位置while(low <= high){middle = (low + high)/2;if(array[i] >= array[middle]){low = middle + 1;}else{high = middle - 1;}}//移动数据,插入元素for(int j = i - 1;j >= low;j--){array[j+1] = array[j];}array[low] = temp;}}

效率:

时间复杂度:

适合用于数据较多的场景,与直接插入排序相比,在寻找插入位置上花的时间减少,但是数据移动的次数一样;比较次数 和初始数据序列顺序无关,查找插入位置的次数是一定的,所以比较的次数也是一定的,时间复杂度为O(n^2)。

空间复杂度:

需要的辅助空间大小是O(1)。

(3)shell(希尔)排序:

基本思路:在直接排序的基础上做了很大的改进,又被称作缩小增量排序。将待排序的数据序列按特定增量划分成若干个子序列,对各个子序列进行插入排序;然后再选择一个更小的增量,再将数据序列划分成若干个子序列进行排序........最后增量为1,即使用直接插入排序,使最终数据序列有序。

要点:取增量,n是数据序列里面数据的个数,增量的序列{n/2,n/4,n/8 ...... ,1}。

如下图,展示取增量、划分子序列判断的过程:


优点:快、数据移动少。

缺点:不稳定、增量d的取值是多少,应该取多少个不同的值都无法确定。

具体实现:

       static void sort(int[] array) {// 打印初始数据System.out.print("初始数据序列:");for (int a : array) {System.out.print(a + " ");}System.out.println("");// 希尔排序int d = array.length;//初始增量while(true){d = d / 2;for(int i = 0;i < d;i ++){//按增量划分子序列for(int j = i + d;j < array.length;j = j + d){//遍历子序列int temp = array[j];//直接插入排序的参照值int k;for(k = j - d;k >= 0 && array[k] > temp;k = k - d){//直接插入排序,后移数据array[k + d] = array[k];}//插入数据,后移一位游标到最后一次比较的位置插入array[k+d] = temp;}}//打印logSystem.out.print("增量:"+d);System.out.print("。结果:");for (int a : array) {System.out.print(a + " ");}System.out.println("");//跳出循环if(d == 1){break;}}// 打印最终数据序列System.out.print("最终数据序列:");for (int a : array) {System.out.print("  " + a);}}
效率:

时间复杂度:

最坏情况:O(n^2)。

最好情况:O(nlogn)。

平均:O(nlogn)。

空间复杂度:
需要的辅助空间大小是O(1)。

既然说到了希尔排序是直接排序的升级版,那么说一下相对于直接排序的优点在哪:

①待排序数组初始状态基本有序的时候,直接插入排序的比较和插入的次数相对比较少;

②当待排序数组的长度n不大的时候,n和n^2相差不大,即直接插入排序的时间复杂度最好情况O(n)和最坏情况O(n^2)差别不大;

③增量排序初始的时候,增量的值比较大,分的组比较少,每组的数据也比较少,所以每组直接插入排序较快;随着增量的逐渐减小,分的组越来越多,每组的数据也越多,但是由于之前的各个分组的排序,使现有的各组顺序比较接近有序的状态,所以,新的排序也会比较快。


二、选择排序

(1)简单选择排序:

基本思路:在待排序的数组中,遍历找出最小的一个数和第一个位置的数交换,然后再在剩下的数中找出最小的和第二个位置的交换,依次类推,直到对比玩倒数第二个和最后一个。

如下图所示,比较过程:

优点:移动数据的次数已知,n-1次。

缺点:不稳定,比较的次数多。

具体实现:

       static void sort(int[] array) {// 打印初始数据System.out.print("初始数据序列:");for (int a : array) {System.out.print(a + " ");}System.out.println("");//简单选择排序int position = 0;for(int i = 0;i < array.length;i++){int temp = array[i];position = i;int j;for(j = i + 1;j < array.length;j++){if(array[j] < temp){temp = array[j];position = j;}}array[position] = array[i];array[i] = temp;//打印日志System.out.print("第"+i+"位置排序:");for (int a : array) {System.out.print("  " + a);}System.out.println("");}// 打印最终数据序列System.out.print("最终数据序列:");for (int a : array) {System.out.print("  " + a);}}
效率:

时间复杂度:

最好情况:O(n^2)。

最坏情况:O(n^2)。

平均情况:O(n^2)。

空间复杂度:

需要的辅助空间大小是O(1)。


(2)堆排序:

基本思想:

堆排序是树形选择排序,是对简单选择排序的改进。

关于堆型结构的理解可以查看点击打开链接,点击打开链接,点击打开链接。

三、交换排序

(1)冒泡排序:

基本思路:依次比较待排序数组的相邻两个数,依据排序要求,如果他们的顺序错误,就交换位置;重复遍历数组,直到有序。

如下图所示,比较过程:


优点:稳定。

缺点:慢,每次只移动相邻数据。

具体实现:

       static void sort(int[] array) {// 打印初始数据System.out.print("初始数据序列:");for (int a : array) {System.out.print(a + " ");}System.out.println("");//冒泡排序int temp;for(int i = array.length - 1;i > 0;i--){for(int j = 0;j < i;j++){if(array[j+1] < array[j]){temp = array[j+1];array[j+1] = array[j];array[j] = temp;}}}// 打印最终数据序列System.out.print("最终数据序列:");for (int a : array) {System.out.print("  " + a);}}
效率:

时间复杂度:

最好情况:O(n)。

最坏情况:O(n^2)。

平均情况:O(n^2)。

空间复杂度:
辅助空间O(1)。


(2)快速排序:

基本思路:通过一趟排序将要排序的数组分割成两个独立部分,其中一部分的所有数据都要比另一部分小,然后再按此方法分别对两部分进行快速排序,依次类推,整个过程递归进行,以达到整个数组有序。

如下图,下面以一轮比较的过程来展示:

优点:极快,数据移动少。

缺点:不稳定。

具体实现:

       static void sort(int arr[], int left, int right) {int low = left;int high = right;int temp = arr[left];while(low < high){while(low < high && arr[high] >= temp){high --;}if(low < high){int tem = arr[low];arr[low] = arr[high];arr[high] = tem;}while(low < high && arr[low] <= temp){low ++;}if(low < high){int tem = arr[low];arr[low] = arr[high];arr[high] = tem;}}if(left < low){sort(arr,left,low-1);}if(high < right){sort(arr,high+1,right);}}

效率:

时间复杂度:

最好情况:O(nlogn)。

最坏情况:O(n^2)。

平均情况:O(nlogn)。

空间复杂度:O(nlogn)~O(n^2)。


四、归并排序

基本思路:归并排序是建立在归并操作基础上的有效排序算法。该算法是采用分治法,将两个已有有序的子序列合并成一个大的有序的序列,通过递归,层层合并。

五、基数排序

原创粉丝点击