常用的十大排序算法

  在计算机领域,算法是永恒的主题。

  在各位同学平时的工作中和面试中一定会涉及到很多算法方面的需求,算法是什么呢?

  算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。

  接下来我们就常用的一些算法来做一些讨论,文笔拙劣,若有不当之处,还请各位看官多多指教!

  正文之中对一些博客的引用都给出了原文链接。侵删!

排序相关算法

常用的排序算法有以下几种:

  1. 选择排序
  2. 插入排序
  3. 冒泡排序
  4. 希尔排序
  5. 归并排序
  6. 快速排序
  7. 堆排序
  8. 计数排序
  9. 桶排序
  10. 基数排序

以下我们就上市的每种算法一一讨论。

选择排序

  首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

OC实现
- (NSMutableArray *)selectionSortByArray:(NSMutableArray *)array{
    for (int index = 0; index < array.count - 1; index++) {
        int pos = index;
        for (int tmpIndex = index + 1; tmpIndex < array.count; tmpIndex++) {
            int posValue = [array[pos] intValue];
            int tmpIndexValue = [array[tmpIndex] intValue];
            if (posValue > tmpIndexValue) {
                pos = tmpIndex;
            }
        }
        
        //将index位置的元素和pos位置的元素互换
        int indexValue = [array[index] intValue];
        int posValue = [array[pos] intValue];
        
        indexValue = indexValue + posValue;
        posValue = indexValue - posValue;
        indexValue = indexValue - posValue;
        
        array[index] = [NSNumber numberWithInt:indexValue];
        array[pos] = [NSNumber numberWithInt:posValue];
    }
    return array;
}
常用的十大排序算法_第1张图片
image

  插入排序的时间复杂度最好情况下为O(n2),最坏情况下为O(n2),平均时间复杂度为 O(n^2)

  空间复杂度为O(1)


插入排序

  从第一个元素开始,该元素可以认为已经排好序,取下一个,在已经排好序的序列中向前扫描,有元素大于这个新元素,将已经在排好序中的元素移到下一个位置,依次执行。

OC实现
- (NSMutableArray *)insertionSortByArray:(NSMutableArray *)array{
    for (int index = 1; index < array.count; index ++) {
        int tmpValue = [array[index] intValue];
        
        for (int tmpIndex = index - 1; tmpIndex >= 0 && [array[tmpIndex] intValue] > tmpValue ; tmpIndex --) {
            array[tmpIndex + 1] = array[tmpIndex];
            array[tmpIndex] = [NSNumber numberWithInt:tmpValue];
        }
    }
    return array;
}
常用的十大排序算法_第2张图片
image

  插入排序的时间复杂度最好情况下为O(n),最坏情况下为O(n^2),平均时间复杂度为 O(n^2)

  空间复杂度为O(1)


冒泡排序
  1. 比较相邻的元素。如果第一个比第二个大,就交换它们两个
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数
  3. 针对所有的元素重复以上的步骤,除了最后一个
  4. 重复步骤1~3,直到排序完成
OC实现
- (NSMutableArray *)bubbleSortByArray:(NSMutableArray  *)array{
    for (int index = 0; index < array.count - 1; index ++) {
        for (int tmpIndex = 0; tmpIndex < array.count - 1; tmpIndex ++) {
            if ([array[tmpIndex] intValue] > [array[tmpIndex + 1] intValue]) {
                NSNumber * tmp = array[tmpIndex];
                array[tmpIndex] = array[tmpIndex + 1];
                array[tmpIndex + 1] = tmp;
            }
        }
    }
    return array;
}
常用的十大排序算法_第3张图片
image

  冒泡排序的时间复杂度最好情况下为O(n),最坏情况下为O(n^2),平均时间复杂度为 O(n^2)

  空间复杂度为O(1)


希尔排序

  希尔排序是先将整个待排序的记录序列分割成为若干子序列,然后分别进行直接插入排序。即希尔排序是插入排序的变种或优化方案。
  希尔排序的思想是使数组中任意间隔为h的元素都是有序的,这样的数组被称为h有序数组,换言之,一个h有序数组就是h和互相独立的有序数组编织在一起组成的一个数组。如下图:

        h = 4
        L     E     E     A     M     H     L     E     P     S     O     L     T     S     X     R
        L--------------- M----------------p----------------T
               E----------------H---------------S----------------S
                      E----------------L----------------O----------------X
                              A----------------E----------------L-----------------R


OC实现
- (NSMutableArray *)shellSortByArray:(NSMutableArray *)array{
    int h = 1;
    
    //定义递增序列
    while (h < array.count /2) {
        h = h * 2 + 1;
    }
    
    for (h ; h > 0; h = h/2) {
        for (int index  = h; index < array.count; index++) {
            NSNumber * tmp = array[index];
            for (int j = index - h; j >= 0 && [array[j] intValue] > tmp.intValue; j -= h) {
                array[j + h] = array[j];
                array[j] = tmp;
            }
        }
    }
    return array;
}


希尔排序过程

初始数组  : 10, 4, 9, 6, 25, 33, 7, 8, 12, 1, 15, 20

第一趟,增量为7,结果数组中,每间隔 7-1 个元素的元素组成的数组是有序的

            8, 4, 1, 6, 20, 33, 7, 10, 12, 9, 15, 25  

第二趟,增量为3,结果数组中,每间隔 3-1 个元素的元素组成的数组是有序的
    
            6, 4, 1, 7, 10, 12, 8, 15, 25, 9, 20, 33

第三趟,增量为1,结果数组中,每间隔 1-1 个元素的元素组成的数组是有序的
    
            1, 4, 6, 7, 8, 9, 10, 12, 15, 20, 25, 33

  希尔排序的时间复杂度最好情况下为O(nlog2n),最坏情况下为O(n2),平均时间复杂度为 O(nlogn)

  空间复杂度为O(1)

拓展

  希尔排序中如何选择递增序列呢?有没有最优的递增序列?

  事实上,由于算法的性能不仅仅取决于h,还取决于h之间的数学性质,比如他们的公因子等。就目前而言很多论文都研究了各不相同的序列,但都无法证明某一个序列是最优的。但可以证明的是,复杂的序列在最坏的情况下对算法带来的提升是要优于我们所使用的简单递增序列的。
                             《算法(第4版)》


归并排序

  归并排序的思想是,当我们需要将一个数组进行排序的时候,可以先(递归的)将它分成两半分别排序,然后将结果归并起来。

  即使一个数组变成若干个元素有序的数组,然后在将这些数组排序合并起来

  归并排序优势在于它能够保证将任意长度为N的数组排序所需要的时间和NlogN成正比,而它的主要缺点则是它所需要的额外空间和N成正比。

  归并排序是适用分治思想的典型案例

- (NSMutableArray *)mergeSortByArray:(NSMutableArray *)array{
    
    NSInteger len = array.count;
    if (len < 2) {
        return array;
    }
    
    NSInteger middle = len/2;
    NSMutableArray * leftArr = [NSMutableArray arrayWithArray:[array subarrayWithRange:NSMakeRange(0, middle)]];
    NSMutableArray * rightArr = [NSMutableArray arrayWithArray:[array subarrayWithRange:NSMakeRange(middle, array.count - middle)]];
    
    NSMutableArray * returnArr = [self mergeWithLeft:[self mergeSortByArray:leftArr] right:[self mergeSortByArray:rightArr]];
    return returnArr;
}

- (NSMutableArray *)mergeWithLeft:(NSMutableArray *)leftArr right:(NSMutableArray *)rightArr{
    
    NSMutableArray * auxArray = [NSMutableArray array];
    while (leftArr.count && rightArr.count ) {
        if ([leftArr[0] integerValue] <= [rightArr[0] integerValue]) {
            [auxArray addObject:leftArr.firstObject];
            [leftArr removeObjectAtIndex:0];
        }else{
            [auxArray addObject:rightArr.firstObject];
            [rightArr removeObjectAtIndex:0];
        }
    }
    
    while (leftArr.count) {
        [auxArray addObject:leftArr.firstObject];
        [leftArr removeObjectAtIndex:0];
    }
    while (rightArr.count) {
        [auxArray addObject:rightArr.firstObject];
        [rightArr removeObjectAtIndex:0];
    }
    
    return auxArray;
}
归并排序的过程
常用的十大排序算法_第4张图片
image

  归并排序的时间复杂度最好情况下为O(nlogn),最坏情况下为O(nlogn),平均时间复杂度为 O(nlogn)

  空间复杂度为O(n)


快速排序

  快速排序是对冒泡排序的一种优化,利用分治和递归的思想。它的基本思想是:

  通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后按此方法对这两部分数据进行快速排序,整个过程是递归进行,最终达到整个数组成为有序状态。

- (NSMutableArray *)quickSortByArray:(NSMutableArray *)array{
    if (array.count < 2) {
        return array;
    }
    NSInteger num = array.count / 2;
    NSInteger centerValue = [array[num] integerValue];
    [array removeObjectAtIndex:num];
    
    NSMutableArray * left = [NSMutableArray array];
    NSMutableArray * right = [NSMutableArray array];
    for (NSUInteger index = 0; index < array.count; index ++) {
        if ([array[index] integerValue] < centerValue) {
            [left addObject:array[index]];
        }else{
            [right addObject:array[index]];
        }
    }
    
    NSMutableArray * leftArr = [self quickSortByArray:left];
    NSMutableArray * righArr = [self quickSortByArray:right];
    [leftArr addObject:[NSNumber numberWithInteger:centerValue]];
    [leftArr addObjectsFromArray:[righArr copy]];
    if (leftArr.count == 12) {
        //排序结束
        NSLog(@"123");
    }
    return leftArr;
}
快速排序过程
常用的十大排序算法_第5张图片
image

  快速排序的时间复杂度最好情况下为O(nlogn),最坏情况下为O(n^2),平均时间复杂度为 O(nlogn)

  空间复杂度为O(logn)


堆排序

  堆排序的原理是利用堆这种数据结构而设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足子节点的键值或索引总是大于(或小于)它的父节点。

  所以,堆排序的过程就是讲一个无序序列构造成一个大顶堆(或小顶堆)的过程

推荐这篇博客   堆排序详解


计数排序

  计数排序是一个非基于比较的排序算法,计数排序用到一个额外的数组B,其原理是遍历未排序的序列A,结束后使得B中每一个元素的下标涵盖A中所有不重复的元素值,而B中每个元素值代表元素下标值在A中作为元素值的个数。即:

         A:   [10, 5, 10, 4, 3, 12, 3, 4, 8, 10 , 6]
    遍历后得到B
              [ 0, 0, 0, 2, 2, 1, 1, 0, 1, 0, 3, 0, 1]
  再遍历B更新A得到
              [3, 3, 4, 4, 5, 6, 8, 10, 10, 10, 12]

  以上只是最简单的过程,对序列B的长度可以优化成A中最大元素max和最小元素min的差值+1.

  B的长度可以优化成 k = max - min + 1 。

  计数排序的时间复杂度为O(n + k). 空间复杂度为O(k).

推荐这篇博客   漫画:什么是计数排序?


桶排序

  桶排序是分治思想的典型引用,即将一个无序序列中的元素按值大小分成若干个区间(桶或箱),然后再对每一个区间进行排序。在对每一个区间进行排序的时候可能会使用其它排序方法或者递归的适用桶排序。

  假设原始无序序列为。A : [1, 3, 2, 7, 8, 6, 3 , 2, 6 , 10, 12,15,20]

   我们发现序列A的元素值集合并不大,即最小值为1 最大值为20 。我们就可以对这种序列适用桶排序。

  首先,创建固定量的桶:如何确定桶的区间呢?

  假定我们把序列分为2份。T0: [a, b) 和 T1 : [c, d)

  序列A中最大值为20,最小值为1 ,则区间长度L为

     L = (20 - 1 + 1)/2 = 10

  所以T0 : [1, 10 +1), 即 [1, 11)。 T1 : [10 + 1, 10 + 10 + 1), 即[11, 21)

  然后,遍历A,计算元素应该被分配在第几个桶中:例如第一个元素1

  Tn = floor((1-1) / L) = 0 即元素1应该被放在T0中。

  以此类推,直到所有的元素都被放入对应的桶中。然后在分别对每一个

  桶中的元素进行排序,最后在合并T0 到 Tn的桶,即得到最终的有序序列。

推荐这篇博客   排序算法之桶排序的深入理解以及性能分析


基数排序

推荐这篇博客   算法与数据结构(十七) 基数排序(Swift 3.0版)

你可能感兴趣的:(常用的十大排序算法)