数组各种排序算法和复杂度分析

andywu andywu     2022-10-08     405

关键词:

 

Java排序算法
1)分类:
  • 插入排序(直接插入排序、希尔排序)
  • 交换排序(冒泡排序、快速排序)
  • 选择排序(直接选择排序、堆排序)
  • 归并排序
  • 分配排序(箱排序、基数排序)
所需辅助空间最多:归并排序
所需辅助空间最少:堆排序
平均速度最快:快速排序
不稳定:快速排序,希尔排序,堆排序。
2)选择排序算法的时候要考虑
  • 数据的规模 、
  • 数据的类型 、
  • 数据已有的顺序。
  • 一般来说,当数据规模较小时,应选择直接插入排序或冒泡排序。任何排序算法在数据量小时基本体现不出来差距。 考虑数据的类型,比如如果全部是正整数,那么考虑使用桶排序为最优。  考虑数据已有顺序,快排是一种不稳定的排序(当然可以改进),对于大部分排好的数据,快排会浪费大量不必要的步骤。数据量极小,而起已经基本排好序,冒泡是最佳选择。我们说快排好,是指大量随机数据下,快排效果最理想。而不是所有情况。
3)总结:
——按平均的时间性能来分:   
  • 时间复杂度为O(nlogn)的方法有:快速排序、堆排序和归并排序,其中以快速排序为最好;    
  • 时间复杂度为O(n2)的有:直接插入排序、起泡排序和简单选择排序,其中以直接插入为最好,特别是对那些对关键字近似有序的记录序列尤为如此;     
  • 时间复杂度为O(n)的排序方法只有,基数排序。 当待排记录序列按关键字顺序有序时,直接插入排序和起泡排序能达到O(n)的时间复杂度;而对于快速排序而言,这是最不好的情况,此时的时间性能蜕化为O(n2),因此是应该尽量避免的情况。简单选择排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。
——按平均的空间性能来分(指的是排序过程中所需的辅助空间大小):     
  • 所有的简单排序方法(包括:直接插入、起泡和简单选择)和堆排序的空间复杂度为O(1);
  • 快速排序为O(logn ),为栈所需的辅助空间;
  • 归并排序所需辅助空间最多,其空间复杂度为O(n );
  • 链式基数排序需附设队列首尾指针,则空间复杂度为O(rd )。
——排序方法的稳定性能:
  • 稳定的排序方法指的是,对于两个关键字相等的记录,它们在序列中的相对位置,在排序之前和 经过排序之后,没有改变。
  • 当对多关键字的记录序列进行LSD方法排序时,必须采用稳定的排序方法。
  • 对于不稳定的排序方法,只要能举出一个实例说明即可。
  • 快速排序,希尔排序和堆排序是不稳定的排序方法。
各种排序算法Java版
 
/*
 * Copyright (c) 2005-2015 XXX Corporation. All rights reserved.
 *
 * Project Name: test
 * File Name:    SortTest.java
 * Package Name: XXX
 * date:         2016年7月28日
 *
 */
package dt;
import java.util.Arrays;
/**
 * ClassName:   SortTest <br/>
 * Description: TODO ADD DESCRIPTION. <br/>
 * date:        2016年7月28日 上午8:25:39 <br/>
 *
 * @author      danier
 */
public class SortTest {
    static int[] a = { 5,17,16, 7,10, 9, 18,4,15,14, 3, 1, 19,0, 20,6,13, 2, 12,8,11 };
    /**
     * main: ADD DESCRIPTION. <br/>
     * 执行流程: (可选). <br/>
     * 使用方法: (可选). <br/>
     * 注意事项: (可选). <br/>
     *
     * @author danier
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        long begin = System.currentTimeMillis();
        SortTest st = new SortTest();
        st.bubbleSort();
        st.selectSort();
        st.insertSort();
        st.halfInsertSort();
        st.hillsort();
        st.mergeSort(0, a.length - 1);
        st.quickSort(0, a.length - 1);
        long over = System.currentTimeMillis();
        System.out.println("使用的时间为:" + (over - begin) + "毫秒");
        System.out.print(Arrays.toString(st.a));
    }
    //    复杂度分析:一共要比较 ((n-1)+(n-2)+...+3+2+1)=n*(n-1)/2次,所以时间复杂度是O(n^2)
    //    冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,
    //    交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;
    //    如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,
    //    所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。
    public void bubbleSort() {
        for (int i = a.length - 1; i > 1; i--) {
            for (int j = 0; j < i; j++) {
                if (a[j] > a[j + 1]) swap(j, j + 1);
            }
        }
    }
 
    //    选择排序是不稳定算法,最好的情是已经排好顺序,只要比较n*(n-1)/2次即可,
    //    最坏情况是逆序排好的,那么还要移动O(n)次,由于是低阶故而不考虑不难得出选择排序的时间复杂度是O(n^2)
    //    比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序不是一个稳定的排序算法
    public void selectSort() {
        for (int i = 0; i < a.length; i++) {
            int min = i;
            for (int j = i + 1; j < a.length; j++) {
                if (a[j] < a[min]) min = j;
            }
            swap(i, min);
        }
    }
    //    插入排序的思想是这样的,第一层for循环表示要循环n次,且每次循环要操作的主体是a[i],第二层循环是对
    //    a[i]的具体操作,是从原数祖第i个位置起,向前比较,所以插入排序的平均时间复杂度也是O(n^2).
    //    比较是从有序序列的末尾开始,也就是想要插入的元素和已经有序的最大者开始比起,
    //    如果比它大则直接插入在其后面,否则一直往前找直到找到它该插入的位置。如果碰见一个和插入元素相等的,
    //    那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,
    //    从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。
    void insertSort() {
        for (int i = 1; i < a.length; i++) {
            int temp = a[i], j = i;
            while (j > 0 && a[j - 1] > temp) {
                a[j] = a[j - 1];
                j--;
            }
            a[j] = temp;
        }
    }
 
    //    1)        最好情况:序列是升序排列,在这种情况下,需要进行的比较操作需(n-1)次。后移赋值操作为0次。即O(n)
    //    2)        最坏情况:O(nlog2n)。
    //    3)        渐进时间复杂度(平均时间复杂度):O(nlog2n)
    //    希尔排序是不稳定的。因为在进行分组时,相同元素可能分到不同组中,改变相同元素的相对顺序
    public void hillsort() {
        int h = 1;
        while (h < a.length / 3) {
            h = h * 3 + 1;
        }
        while (h > 0) {
            for (int i = 1; i < a.length; i++) {
                int temp = a[i], j = i;
                while (j > h - 1 && a[j - h] > temp) {
                    a[j] = a[j - h];
                    j -= h;
                }
                a[j] = temp;
            }
            h = (h - 1) / 3;
        }
    }
//    二分查找排序是稳定的,不会改变相同元素的相对顺序, 
    //    1. 时间复杂度:O(n^2)
    //    二分查找插入位置,因为不是查找相等值,而是基于比较查插入合适的位置,所以必须查到最后一个元素才知道插入位置。
    //    二分查找最坏时间复杂度:当2^X>=n时,查询结束,所以查询的次数就为x,而x等于log2n(以2为底,n的对数)。即O(log2n)
    //    所以,二分查找排序比较次数为:x=log2n
    //    二分查找插入排序耗时的操作有:比较 + 后移赋值。时间复杂度如下:
    //    1)        最好情况:查找的位置是有序区的最后一位后面一位,则无须进行后移赋值操作,其比较次数为:log2n  。即O(log2n)
    //    2)        最坏情况:查找的位置是有序区的第一个位置,则需要的比较次数为:log2n,需要的赋值操作次数为n(n-1)/2加上 (n-1) 次。即O(n^2)
    //    3)        渐进时间复杂度(平均时间复杂度):O(n^2)
    void halfInsertSort() {
        for (int i = 1; i < a.length; i++) {
            if (a[i] > a[i - 1]) {
                continue;
            }
            int temp = a[i], left = 0, right = i - 1;
            while (left <= right) {
                int mid = (left + right) / 2;
                if (a[mid] > temp)
                    right = mid - 1;
                else
                    left = mid + 1;
            }
            for (int j = i; j > left; j--) {
                a[j] = a[j - 1];
            }
            a[left] = temp;
        }
    }
   

public void
mergeSort(int left, int right) { if (left >= right) return; int mid = (left + right) / 2; mergeSort(left, mid); mergeSort(mid + 1, right); merge(left, mid, mid + 1, right); } private void merge(int lb, int le, int rb, int re) { int[] temp = new int[a.length]; int leftbegin = lb; int index = lb; while (lb <= le && rb <= re) { if (a[lb] < a[rb]) temp[index++] = a[lb++]; else temp[index++] = a[rb++]; } while (lb <= le) { temp[index++] = a[lb++]; } while (rb <= re) { temp[index++] = a[rb++]; } while (leftbegin <= re) { a[leftbegin] = temp[leftbegin]; leftbegin++; } }

1》归并排序的步骤如下:

       Divide: 把长度为n的输入序列分成两个长度为n/2的子序列。
       Conquer: 对这两个子序列分别采用归并排序。      
       Combine: 将两个排序好的子序列合并成一个最终的排序序列。
2》时间复杂度:
这是一个递推公式(Recurrence),我们需要消去等号右侧的T(n),把T(n)写成n的函数。其实符合一定条件的Recurrence的展开有数学公式可以套,这里我们略去严格的数学证明,只是从直观上看一下这个递推公式的结果。当n=1时可以设T(1)=c1,当n>1时可以设T(n)=2T(n/2)+c2n,我们取c1和c2中较大的一个设为c,把原来的公式改为:
这样计算出的结果应该是T(n)的上界。下面我们把T(n/2)展开成2T(n/4)+cn/2(下图中的(c)),然后再把T(n/4)进一步展开,直到最后全部变成T(1)=c(下图中的(d)):

 

 
       把图(d)中所有的项加起来就是总的执行时间。这是一个树状结构,每一层的和都是cn,共有lgn+1层,因此总的执行时间是cnlgn+cn,相比nlgn来说,cn项可以忽略,因此T(n)的上界是Θ(nlgn)。
       如果先前取c1和c2中较小的一个设为c,计算出的结果应该是T(n)的下界,然而推导过程一样,结果也是Θ(nlgn)。既然T(n)的上下界都是Θ(nlgn),显然T(n)就是Θ(nlgn)。
 
 

4)快速排序算法思想

基于分治的思想,是冒泡排序的改进型。首先在数组中选择一个基准点(该基准点的选取可能影响快速排序的效率,后面讲解选取的方法),然后分别从数组的两端扫描数组,设两个指示标志(lo指向起始位置,hi指向末尾),首先从后半部分开始,如果发现有元素比该基准点的值小,就交换lo和hi位置的值,然后从前半部分开始扫秒,发现有元素大于基准点的值,就交换lo和hi位置的值,如此往复循环,直到lo>=hi,然后把基准点的值放到hi这个位置。一次排序就完成了。以后采用递归的方式分别对前半部分和后半部分排序,当前半部分和后半部分均有序时该数组就自然有序了。

 

 
 
//    平均时间复杂度O(nlogn),最坏时间复杂度O(n*n),辅助空间O(logn)<每次都要分给一个额外空间,而总共有logn次>
//    每次分成两段,那么分的次数就是logn了,每一次处理需要n次计算,那么时间复杂度就是nlogn了!
//    根据平均情况来说是O(nlogn),因为在数据分布等概率的情况下对于单个数据来说在logn次移动后就会被放到正确的位置上了。
//    最坏是O(n^2).这种情况就是数组刚好的倒序,然后每次去中间元的时候都是取最大或者最小。
//    稳定性:不稳定。
 public static int partition(int []array,int lo,int hi){
//左右中抽取3个点,按照213的顺序排序,以左节点2作为pivot
int mid=lo+(hi-lo)/2;
if(array[mid]>array[hi]){
swap(array[mid],array[hi]);
}
if(array[lo]>array[hi]){
swap(array[lo],array[hi]);
}
if(array[mid]>array[lo]){
swap(array[mid],array[lo]);
}
int key=array[lo]; //此时左节点lo值为key。后续准备放置到lo和hi重合的位置

while(lo<hi){
//从右开始找到比key值小的数据,写入lo
while(array[hi]>=key&&hi>lo){
hi--;
}
array[lo]=array[hi];

//从左开始找到比key值大的数据,写入hi
while(array[lo]<=key&&hi>lo){
lo++;
}
array[hi]=array[lo];
}

// lo和hi重合时候,将key放入。此时hi左面的数都小于key,右面数大于key
array[hi]=key;
return hi;
}

public static void swap(int a,int b){
int temp=a;
a=b;
b=temp;
}
public static void sort(int[] array,int lo ,int hi){
if(lo>=hi){
return ;
}
int index=partition(array,lo,hi);
sort(array,lo,index-1);
sort(array,index+1,hi);
}


 

 

排序和数据结构(代码片段)

目录各种排序算法时间复杂度及空间复杂度对比各种排序算法1.python内置方法sorted()和list.sort()排序2.冒泡排序3.选择排序4.插入排序5.快速排序6.希尔排序7.计数排序8.归并排序列表查找数据结构malloc()到底如何申请内存空间?数组... 查看详情

各种排序算法的时间复杂度和空间复杂度

   其中冒泡排序加个标志,所以最好情况下是o(n)  查看详情

各种排序算法的比较

各种排序算法的比较排序算法的比较从时间复杂度上来看从空间复杂度来看从稳定性看其他特点后续排序算法的比较从时间复杂度上来看简单选择排序、直接插入排序和冒泡排序平均情况下的时间复杂度都为O(n^2),... 查看详情

各种排序算法汇总小结

...要求很高。而一般我们所谓的算法的性能主要是指算法的复杂度,一般用O方法来表示。在后面我将给出详细的说明。对于排序的算法我想先做一点简单的介绍,也是给这篇文章理一个提纲。我将按照算法的复杂度,从简单到难... 查看详情

转:各种排序算法的稳定性和时间复杂度小结

...法了。他的名字的由来因为它的工作看来象是冒泡: 复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。直接插入排序:O(n*n)选择排序:O(n*n)快速排序:平均时间复杂度log2(n 查看详情

快速排序算法分析解析(代码片段)

快速排序算法的时间复杂度和各次标准数据元素的值关系很大。如果每次选取的标准元素都能均分两个子数组的长度,这样的快速排序过程是一个完全二叉树结构。(即每个结点都把当前数组分成两个大小相等的数组结点,n个元素... 查看详情

十大经典排序算法的算法描述和代码实现(代码片段)

...排序和基数排序的非比较类排序,分析了各种排序算法的复杂度和稳定性,还有JAVA代码的详细实现。对冒泡排序、插入排序、选择排序和堆排序等十种算法进行了详细的思想总结。一、算法概述1、算法分类十种常见排序算法可... 查看详情

各种排序算法的利弊

对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。时间复杂度来说:(1)平方阶(O(n2))排序  各类简单排序:直接插入、直接选择和冒泡排序;(2)线性对数阶(O(nlog2n))排序  快速排序、堆排序和归并排序;(3)O... 查看详情

数组中的排序分析及奇偶排序-算法数据结构面试分享(代码片段)

...一般的排序方法,如冒泡,快排,插入,归并。其中时间复杂度有O(N),和O(Nlogn),以及O(N2)的。今天我们在这里看一些特定情况下的排序,并否所有的排序都是基于大小的,有时待排序的数大小范围是已知的,我们分别看两个典型... 查看详情

算法之数组和问题

...素的和。这个就是经典的握手问题,不难得出其最坏时间复杂度为:(Theta)((n^2))这种指数级别的时间复杂度必然不是我们想要的,直接PASS先做排序然后再进行查找:假设 查看详情

各种排序算法的的时间复杂度空间复杂度和稳定性

为了方便记忆,借用马士兵的词儿:选炮插,快归堆希统计姬,n方n老n一三,对n加kn乘k,不稳稳稳不稳稳,不稳不稳稳稳稳。 查看详情

数据结构(java)——no4.常见的排序算法(代码片段)

常见的七种排序算法引言1.冒泡排序思路代码复杂度分析2.插入排序思路代码复杂度分析3.希尔排序思路代码复杂度分析4.选择排序思路代码复杂度分析5.堆排序思路代码复杂度分析6.快速插入排序思路代码复杂度分析7.归并排序思... 查看详情

排序算法-冒泡排序的时间复杂度分析

冒泡排序算法是一种基于比较的排序算法,每次冒泡过程,都会有一个数据确定位置。经过n次冒泡后,就有n个数据确定了位置。如图所示,对数组[4,5,6,3,2,1]进行冒泡排序。 起初,按照最原始的想法,代... 查看详情

排序(上):冒泡排序插入排序和选择排序(代码片段)

...下三个方面来衡量:最好情况、最坏情况、平均情况时间复杂度时间复杂度的系数、常数、低阶比较次数和交换(或移动)次数第1、2点在之前的复杂度分析中我们已经讲过了,第3点会在这一节以及接下来的章节中详细讲解。这... 查看详情

归并排序的细节讲解与复杂度分析

1.归并排序时间复杂度为O(N*logN),额外的空间复杂度O(N)。2.递归行为:一个数组的排序,先将左侧部分排好序,然后将右侧部分排好序,最后整体利用外排序的方式整体排好。3.归并排序:将两个(或者两个以上)有序表合并成一个... 查看详情

各种排序算法(代码片段)

...键字比较次数[n-1]和记录移动次数[0]均达到最小值,时间复杂度O(n)如果全部反序,所需的关键字比较次数[n(n-1)/2]和记录移动次数[3*n*(n-1)/2]均达到最大值,时间复杂度O(n^2)基本思想:1、第一个数和第二个数比较,如果第一个比第... 查看详情

八大排序算法——堆排序(动图演示思路分析实例代码java复杂度分析)

一、动图演示  二、思路分析   先来了解下堆的相关概念:堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称... 查看详情

排序算法比较与分析

...要从排序算法的基本概念、原理出发,分别从算法的时间复杂度、空间复杂度、算法的稳定性和速度等方面进行分析比较。依据待排序的问 查看详情