Objective-c各种排序算法

Objective-C排序算法

根据将排序记录是否全部放置在内存中,将排序分为内排序和外排序,之前讲的都是内排序,这里总结一下,内排序分为四类:插入排序、交换排序、选择排序和归并排序。


image

快排

快速排序是面试中经常会被问的一个排序算法。一般要求手写。
快排是对冒泡排序的一种改进。
平均时间复杂度O(nlogn),最坏时间复杂度O(n*n)

因为二分查找的时间复杂度是o(logn), 每次分成两段,那么分的次数就是logn了,每一次处理需要n次计算,那么时间复杂度就是nlogn了!

最坏是O(n^2). 这种情况就是数组刚好的倒序,然后每次去中间元的时候都是取最大或者最小。
稳定性:不稳定。

快速排序时间复杂度为O(n×log(n))的证明


+ (void)quickSort:(NSMutableArray *)arr low:(NSInteger)low high:(NSInteger)high {
    if (arr.count == 0 || low >= high) {
        return;
    }
    
    NSInteger i = low, j = high;
    NSInteger key = [arr[low] integerValue];
    
    while (i < j && [arr[j] integerValue] >= key) {
        j--;
    }
    
    arr[i] = arr[j];
    
    while (i < j && [arr[i] integerValue] <= key) {
        i++;
    }
    
    arr[j] = arr[i];
    
    NSLog(@"一次排序结果:%@", arr);
    
    arr[i] = @(key);
    
    [[self class] quickSort:arr low:low high:i-1];
    [[self class] quickSort:arr low:i+1 high:high];
}

堆排序

堆排序是一种选择排序,其时间复杂度为O(nlogn)。
定义:

定义

堆的存储

堆的存储

其基本思想为(大顶堆):

  1. 将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn),且满足R[1,2...n-1]<=R[n];
  3. 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。

因此对于堆排序,最重要的两个操作就是构造初始堆和调整堆,其实构造初始堆事实上也是调整堆的过程,只不过构造初始堆是对所有的非叶节点都进行调整。
由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最大的数据并入到后面的有序区间,故操作完成后整个数组就有序了。

已知一棵完全二叉树各节点的编号为0到n,如何得出其第一个非叶子节点的编号
最后一个叶子节点的索引值是n-1,它的父节点索引值是[(n-1)-1]/2 = n/2 -1

/* (最大)堆的向下调整算法
  *
  * 注:数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
  *     其中,N为数组下标索引值,如数组中第1个数对应的N为0。
  *
  * 参数说明:
  *     a -- 待排序的数组
  *     start -- 被下调节点的起始位置(一般为0,表示从第1个开始)
  *     end   -- 截至范围(一般为数组中最后一个元素的索引)
*/
- (void)heapAdjust:(NSMutableArray *)marr start:(int)start end:(int)end {
    int lchild = 2*start; //i的左孩子节点序号
    int rchild = 2*start+1; //i的右孩子节点序号
    int max = start;  //临时变量
    
    if (start <= end/2) {  //如果i是叶节点就不用进行调整
        
        if (lchild <= end && [marr[lchild] integerValue] > [marr[max] integerValue]) {
            max = lchild;
        }
        
        if (rchild <= end && [marr[rchild] integerValue] > [marr[max] integerValue] ) {
            max = rchild;
        }
        
        if (max != start) {
            [marr exchangeObjectAtIndex:start withObjectAtIndex:max];
            // 避免调整之后以max为父节点的子树不是堆
            [self heapAdjust:marr start:max end:end];
        }
    }
}


// 建大根堆
- (void)buildHeap:(NSMutableArray *)marr  {
    int size = (int)marr.count;
    // 最后一个叶子节点的索引值是n-1,它的父节点索引值是[(n-1)-1]/2 = n/2 -1
    for (int i = size/2-1; i >= 0; --i) {
        [self heapAdjust:marr start:i end:size-1];
    }
}

- (void)heapSort:(NSMutableArray *)marr {
    [self buildHeap:marr];
    
    NSLog(@"build after: %@", marr);
    
    int len = (int)marr.count-1;
    for (int i = len; i >= 0; i--) {
        // 交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面
        [marr exchangeObjectAtIndex:0 withObjectAtIndex:i];
        // 重新调整堆顶节点成为大顶堆
        [self heapAdjust:marr start:0 end:i-1];
    }
}



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *arr = @[@(12), @(14), @(100),  @(29), @(8), @(21), @(15), @(59), @(50), @(99)];
        NSMutableArray *marr = [NSMutableArray arrayWithArray:arr];
        SGSort *sort = [[SGSort alloc] init];
        [sort heapSort:marr];
        NSLog(@"heapsort result =%@", marr);
    }
    return 0;
}

// 输出结果
2017-08-27 19:55:04.657 SuanFaXueXi[3487:106882] build after: (
    100,
    99,
    50,
    59,
    14,
    21,
    15,
    29,
    12,
    8
)
2017-08-27 19:55:07.151 SuanFaXueXi[3487:106882] heapsort result =(
    8,
    12,
    14,
    15,
    21,
    29,
    50,
    59,
    99,
    100
)

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)

堆排序适合于数据量非常大的场合(百万数据)。
堆排序不需要大量的递归或者多维的暂存数组。这对于数据量非常巨大的序列是合适的。比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法,在数据量非常大的时候,可能会发生堆栈溢出错误。

归并排序

希尔排序

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

计数排序

如果在面试中有面试官要求你写一个O(n)时间复杂度的排序算法,你千万不要立刻说:这不可能!虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序,只不过有前提条件,就是待排序的数要满足一定的范围的整数,而且计数排序需要比较多的辅助空间。其基本思想是,用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。
 
今天学习了计数排序,貌似计数排序的复杂度为o(n)。很强大。他的基本思路为:

  1. 我们希望能线性的时间复杂度排序,如果一个一个比较,显然是不实际的,书上也在决策树模型中论证了,比较排序的情况为nlogn的复杂度。

  2. 既然不能一个一个比较,我们想到一个办法,就是如果我在排序的时候就知道他的位置,那不就是扫描一遍,把他放入他应该的位置不就可以了嘛。

3.要知道他的位置,我们只需要知道有多少不大于他不就可以了吗?

  1. 以此为出发点,我们怎么确定不大于他的个数呢?我们先来个约定,如果数组中的元素都比较集中,都在[0, max]范围内。我们开一个max的空间b数组,把b数组下标对应的元素和要排序的A数组下标对应起来。这样不就可以知道不比他大的有多少个了吗?我们只要把比他小的位置元素个数求和,就是不比他大的。例如:A={3,5,7};我们开一个大小为8的数组b,把a[0] = 3 放入b[3]中,使b[3] = 0; 同理 b[5] = 1; b[7] = 2;其他我们都设置为-1,哈哈我们只需要遍历一下b数组,如果他有数据,就来出来,铁定是当前最小的。如果要知道比a[2]小的数字有多少个,值只需要求出b[0] – b[6]的有数据的和就可以了。这个0(n)的速度不是盖得。

  2. 思路就是这样咯。但是要注意两个数相同的情况A = {1,2,3,3,4},这种情况就不可以咯,所以还是有点小技巧的。

6.处理小技巧:我们不把A的元素大小与B的下标一一对应,而是在B数组对应处记录该元素大小的个数。这不久解决了吗。哈哈。例如A = {1,2,3,3,4}我们开大小为5的数组b;记录数组A中元素值为0的个数为b[0] = 0, 记录数组A中元素个数为1的b[1] = 1,同理b[2] = 1, b[3] = 2, b[4] = 1;好了,这样我们就知道比A4小的元素个数是多少了:count = b[0] + b[1] + b[2] + b[3] = 4;他就把A[4]的元素放在第4个位置。

算法系列:计数排序

桶排序

基数排序

总结

在前面的介绍和分析中我们提到了冒泡排序、选择排序、插入排序三种简单的排序及其变种快速排序、堆排序、希尔排序三种比较高效的排序。后面我们又分析了基于分治递归思想的归并排序还有计数排序、桶排序、基数排序三种线性排序。我们可以知道排序算法要么简单有效,要么是利用简单排序的特点加以改进,要么是以空间换取时间在特定情况下的高效排序。
  



1. 从平均时间来看,快速排序是效率最高的,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后者相比较的结果是,在n较大时归并排序使用时间较少,但使用辅助空间较多。

2. 上面说的简单排序包括除希尔排序之外的所有冒泡排序、插入排序、简单选择排序。其中直接插入排序最简单,但序列基本有序或者n较小时,直接插入排序是好的方法,因此常将它和其他的排序方法,如快速排序、归并排序等结合在一起使用。

3. 基数排序的时间复杂度也可以写成O(d*n)。因此它最使用于n值很大而关键字较小的的序列。若关键字也很大,而序列中大多数记录的最高关键字均不同,则亦可先按最高关键字不同,将序列分成若干小的子序列,而后进行直接插入排序。

4. 从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为O(n^2)的简单排序也是稳定的。但是快速排序、堆排序、希尔排序等时间性能较好的排序方法都是不稳定的。稳定性需要根据具体需求选择。

5. 上面的算法实现大多数是使用线性存储结构,像插入排序这种算法用链表实现更好,省去了移动元素的时间。具体的存储结构在具体的实现版本中也是不同的。

参考

排序 Heap Sort
堆排序算法解析
堆排序
面试中的排序算法总结
计数排序,传说时间复杂度为0(n)的排序

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容

  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,729评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,173评论 0 52
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,264评论 0 35
  • statusBar 20像素,拨打手机时为40像素 navigationBar 44像素 未完待续
    NapoleonY阅读 201评论 0 0