资讯专栏INFORMATION COLUMN

数据结构与简单算法-排序算法

AlphaWatch / 2474人阅读

摘要:堆排序其实类似简单选择排序,每次找出最大最小元素,移到特定位置完成排序。排序算法平均情况最好情况最坏情况辅助空间稳定性冒泡排序稳定简单选择排序稳定直接插入排序稳定希尔排序不稳定堆排序不稳定归并排序稳定快速排序不稳定参考大话数据结构算法。

排序算法 时间复杂度O(n^2)的排序 1.冒泡排序

冒泡排序通过两两比较相邻记录的关键字,反序则交换,从而达到排序的效果

for(int i=0; io[j+1]){
            swap(o,j,j+1);
        }
    }
}

上面的代码有个小问题,设想存在待排序序列{1,3,5,7,11,9},经过第一遍遍历交换11和9之后已经达到排序效果,接下来的遍历已无意义。考虑增加一个标志位,若上一轮遍历没有引起序列任何变化,则退出整个遍历,不再做无意义的比较。

boolean flag = true;
for(int i=0; io[j+1]){
            swap(o,j,j+1);
            flag = true;
        }
    }
}

冒泡排序还有几种变形,或是向上冒泡、向下冒泡,但都差别不大。分析时间复杂度:最好情况下无需交换,只要进行n-1次比较,时间复杂度O(n);最坏情况下,即待排序序列为逆序,需要比较n*(n-1)/2次,并作等数量级的交换,时间复杂度O(n^2)。

2.简单选择排序

与冒泡排序区别的是,冒泡排序只要发现反序就会交换相邻的两个数,而简单选择排序是先找出最小或最大的数,与特定位置交换,可以看出其优点是交换移动次数相当少。

int min;
for(int i=0; io[j]){
            min = j;
        }
    }
    if(min != i){
        swap(o,min,i);
    }
}

分析简单选择排序的复杂度,无论最好最坏情况下,比较次数都是n*(n-1)/2,但最好情况下交换0次,最坏情况也只有n-1次,总的复杂度仍是O(n^2)。

3.直接插入排序

直接插入排序的思想是向一个有序序列逐步插入记录,得到记录数不断增加的序列,若初始为乱序序列,则选取第一个元素作为一个有序序列,其他元素依次插入这个有序序列,从而达到排序效果。

int j,temp;
for(int i=1; i=0&&o[j]>temp; j--){
            o[j+1] = o[j];
        }
        o[j+1] = temp;
    }
}

直接插入与理扑克牌有点相似,最好情况下需要比较n-1次,无需交换;最坏情况下比较(n+2)(n-1)/2次,移动(n+4)(n-1)/2次。

时间复杂度O(n^3/2)的排序 1.希尔排序

希尔排序是基于直接插入排序的,直接插入排序在元素较少和元素基本有序时效率是不错的,但随着元素增多和有序性破坏,效率会下降的很明显。希尔排序通过分组实现跳跃式移动,保证待排序序列基本有序后再排序,就能提高效率。

int j,temp;
int increment = o.length;
do{
    increment = increment/3+1;
    for(int i=increment; i=0&&o[j]>temp; j-=increment){
            o[j+increment] = o[j];
        }
        o[j+increment] = temp;
    }
    
}while(increment>1);

希尔排序的关键在于增量increment的选择,最坏情况下,可以取得时间复杂度O(n^3/2)的算法。

时间复杂度O(nlgn)的排序 1.堆排序

堆是特殊的完全二叉树,每个节点的值都大于等于(小于等于)其左右孩子节点的值。堆排序其实类似简单选择排序,每次找出最大最小元素,移到特定位置完成排序。堆排序相对于简单选择排序的优点是,之前排序的结果得以保存,能被后面的排序利用。

//堆排序
for(int i=o.length/2-1; i>=0; i--){
    //从最后一个有子节点的节点开始依次往前调整对应节点来生成大顶堆
    heapAdjust(o,i,o.length-1);
}

for(int i=0; io[i]){
            swap(o,m,i);
        }
        i = m;
    }
}

堆排序的时间复杂度主要取决于构建堆和重建堆两部分,构建堆的时间复杂度O(n^2),重建堆的时间复杂度为O(nlgn),总的时间复杂度为O(nlgn)。

2.归并排序

归并排序的思想是逐层将序列子元素两两归并成有序序列,每个子序列长度经历1、2、4、...、n,最后得到完整有序序列。

int[] sort(int[] o,int m,int n){
    int mid;
    int[] result = new int[o.length];
    if(o.length == 1|| m==n){
        result[0] = o[0];
    }else{
        mid = (m+n)/2;
        int[] temp1 = new int[mid-m+1];
        int[] temp2 = new int[o.length-mid+m-1];
        System.arraycopy(o,0,temp1,0,mid-m+1);
        System.arraycopy(o,mid-m+1,temp2,0,o.length-mid+m-1);
        int[] result1 = sort(temp1,m,mid);
        int[] result2 = sort(temp2,mid+1,n);
        result = Merge(result1,result2);
    }
    return result;
}

int[] Merge(int[] i,int[] j){
    int m=0,n=0,k=0;
    int[] result = new int[i.length+j.length];
    
    for(; m

上面的算法用了大量临时数组,是一种不好的设计,其实只需要一个与原数组大小相同的临时数组(必须有,如果只有数组本身,那么排序时可能覆盖原有元素),就能完成归并排序。

void sort(int[] o, int lo, int high){
    if(hi <= lo) return;
    int mid = lo+(high-lo)/2;
    sort(o,lo,mid);
    sort(o,mid+1,hi);
    merge(o,lo,mid,hi);
}

void merge(int[] o,int lo,int mid,int high){
    int i = lo,j = mid+1;
    int[] aux = new int[hi-lo+1];
    for(int k=lo; k<=hi, k++)
        if(i>mid) a[k] = aux[j++];
        else if(j>hi) a[k] = aux[i++];
        else if(aux[j]

归并排序需要扫描序列中所有记录,耗费O(n)时间,整个归并排序需要logn,因此,总的时间复杂度为O(nlgn),空间复杂度为O(n+logn)。

归并排序可以从几个方面优化:对小规模子数组使用插入排序;测试数组是否已经有序,有序就可以跳过merge方法;不将元素复制到辅助数组(复制改为排序)。

3.快速排序

快速排序的思想是分割,确保一个元素左边的元素都小于这个元素,右边的元素都大于这个元素,然后对这两部分分别继续进行分割,从而达到排序的效果。

void sort(int[] i,int low,int high){
    int temp;
    if(low=privotkey)
            high --;
        swap(i,low,high);
        while(low

快速排序的时间复杂度最优和平均都是O(nlgn),最坏情况下为O(n^2),空间复杂度为O(lgn),快速排序很脆弱,有一些优化手段,比如转插入等;希尔排序效率高,代码简单,不需要额外空间,较为常用;

总结

直接插入排序在序列基本有序时效率较高,对于随机排序序列,插入排序和选择排序的运行时间是平方级别的,两者之比应该是一个较小的常数。

排序算法 平均情况 最好情况 最坏情况 辅助空间 稳定性
冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定
简单选择排序 O(n^2) O(n^2) O(n^2) O(1) 稳定
直接插入排序 O(n^2) O(n) O(n^2) O(1) 稳定
希尔排序 O(nlogn)~O(n^2) O(n^1.3) O(n^2) O(1) 不稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn)~O(n) 不稳定

参考:《大话数据结构》、《算法》。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68617.html

相关文章

  • Github标星2w+,热榜第一,如何用Python实现所有算法

    摘要:归并排序归并排序,或,是创建在归并操作上的一种有效的排序算法,效率为大符号。以此类推,直到所有元素均排序完毕。与快速排序一样都由托尼霍尔提出的,因而也被称为霍尔选择算法。 showImg(https://segmentfault.com/img/remote/1460000019096360);编译:周素云、蒋宝尚 学会了Python基础知识,想进阶一下,那就来点算法吧!毕竟编程语言只...

    zxhaaa 评论0 收藏0
  • 数据结构算法:二分查找

    摘要:为检查长度为的列表,二分查找需要执行次操作。最后需要指出的一点是高水平的读者可研究一下二叉树关于二叉树,戳这里数据结构与算法二叉树算法常见练习在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 常见数据结构 简单数据结构(必须理解和掌握) 有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小) 无序数据结构:集合、字典、散列表,无序...

    zsirfs 评论0 收藏0
  • 数据结构算法:二分查找

    摘要:为检查长度为的列表,二分查找需要执行次操作。最后需要指出的一点是高水平的读者可研究一下二叉树关于二叉树,戳这里数据结构与算法二叉树算法常见练习在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 常见数据结构 简单数据结构(必须理解和掌握) 有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小) 无序数据结构:集合、字典、散列表,无序...

    you_De 评论0 收藏0
  • 数据结构算法:二分查找

    摘要:为检查长度为的列表,二分查找需要执行次操作。最后需要指出的一点是高水平的读者可研究一下二叉树关于二叉树,戳这里数据结构与算法二叉树算法常见练习在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 常见数据结构 简单数据结构(必须理解和掌握) 有序数据结构:栈、队列、链表。有序数据结构省空间(储存空间小) 无序数据结构:集合、字典、散列表,无序...

    gotham 评论0 收藏0
  • 排序算法(Java)——那些年面试常见的排序算法

    摘要:下面会介绍的一种排序算法快速排序甚至被誉为世纪科学和工程领域的十大算法之一。我们将讨论比较排序算法的理论基础并中借若干排序算法和优先队列的应用。为了展示初级排序算法性质的价值,我们来看一下基于插入排序的快速的排序算法希尔排序。 前言   排序就是将一组对象按照某种逻辑顺序重新排列的过程。比如信用卡账单中的交易是按照日期排序的——这种排序很可能使用了某种排序算法。在计算时代早期,大家普遍...

    Harpsichord1207 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<