JHHK

欢迎来到我的个人网站
行者常至 为者常成

【进阶】2、排序(二)

目录

插入排序(Insertion Sort)

一、插入排序原理

插入排序非常类似于扑克牌的排序

img

执行流程
① 在执行过程中,插入排序会将序列分为2部分
✓ 头部是已经排好序的,尾部是待排序的
② 从头开始扫描每一个元素
✓ 每当扫描到一个元素,就将它插入到头部合适的位置,使得头部数据依然保持有序

二、代码实现

//时间复杂度:最好O(n)、最坏O(n^2)、平均O(n^2)
//空间复杂度O(1)
-(void)sort{
    for (int begin = 1; begin<self.array.count; begin++) {
        int current = begin;
        while(current>0 &&[self compareWitnIndex1:current index2:current-1]<0){
            [self swapWitnIndex1:current index2:current-1];
            current--;
        }
    }
}

三、插入排序-逆序对

什么是逆序对?
数组 <2,3,8,6,1> 的逆序对为:<2,1> <3,1> <8,1> <8,6> <6,1>,共5个逆序对

img

插入排序的时间复杂度与逆序对的数量成正比关系
逆序对的数量越多,插入排序的时间复杂度越高

最坏、平均时间复杂度:O(n^2) 最好时间复杂度:O(n)
空间复杂度:O(1)
属于稳定排序

当逆序对的数量极少时,插入排序的效率特别高 甚至速度比 O(nlogn) 级别的快速排序还要快,数据量不是特别大的时候,插入排序的效率也是非常好的

四、插入排序-优化

思路是将【交换】转为【挪动】
① 先将待插入的元素备份
② 头部有序数据中比待插入元素大的,都朝尾部方向挪动1个位置
③ 将待插入元素放到最终的合适位置

img

//时间复杂度:最好O(n)、最坏O(n^2)、平均O(n^2)
//空间复杂度O(1)
-(void)sort{
    for (int begin = 1; begin<self.array.count; begin++) {
        
        int current = begin;
        id curValue = self.array[current];
        while(current>0 &&[self compareWithValue1:curValue value2:self.array[current-1]]<0){
            
            //将交换替换为挪动
            //[self swapWitnIndex1:current index2:current-1];
            self.array[current] = self.array[current-1];
            current--;
        }
        
        self.array[current] = curValue;
    }
}

一、介绍

如何确定一个元素在数组中的位置?(假设数组里面全都是整数)
如果是无序数组,从第 0 个位置开始遍历搜索,平均时间复杂度:O(n)

0 1 2 3 4 5 6 7 8 9
31 66 17 15 28 20 59 88 45 56

如果是有序数组,可以使用二分搜索,最坏时间复杂度:O(logn)

0 1 2 3 4 5 6 7 8 9
15 17 20 28 31 45 56 59 66 88

二、思路

假设在 [begin, end) 范围内搜索某个元素 v,mid == (begin + end) / 2
如果 v < m,去 [begin, mid) 范围内二分搜索
如果 v > m,去 [mid + 1, end) 范围内二分搜索
如果 v == m,直接返回 mid

img

三、示例分析

img

四、代码实现

/// 二分搜索返回index,未找到返回-1
/// @param array 要搜索的数组
/// @param element 要搜索的元素
-(int)binarySearch:(NSArray*)array element:(id)element{
    
    if (array == nil || array.count == 0) return -1;
    
    //前闭后开
    int begin = 0;
    int end = (int)array.count;
    while (begin < end) {
        int mid = (begin + end)>>1;
        if ([array[mid] compare:element] == -1) {
            begin = mid+1;
        }else if([array[mid] compare:element] == 1){
            end = mid;
        }else{
            return mid;
        }
    }
    return -1;
}

插入排序优化

一、优化思路

在元素 v 的插入过程中,可以先二分搜索出合适的插入位置,然后再将元素 v 插入

0 1 2 3 4 5 6 7
2 4 8 8 8 12 14 v

要求二分搜索返回的插入位置:第1个大于 v 的元素位置
如果 v 是 5,返回 2
如果 v 是 1,返回 0
如果 v 是 15,返回 7
如果 v 是 8,返回 5

假设在 [begin, end) 范围内搜索某个元素 v,mid == (begin + end) / 2
如果 v < m,去 [begin, mid) 范围内二分搜索
如果 v ≥ m,去 [mid + 1, end) 范围内二分搜索

img

二、示例分析1

img

三、示例分析2

img

四、示例分析3

img

五、代码实现

//时间复杂度:最好O(n)、最坏O(n^2)、平均O(n^2)
//空间复杂度O(1)

-(void)sort{
    for (int begin = 1; begin<self.array.count; begin++) {
        [self insertSourceIndex:begin desIndex:[self searchIndex:begin]];
    }
}



/// 插入元素
/// @param sourceIndex 原始索引
/// @param desIndex 插入到的索引
-(void)insertSourceIndex:(int)sourceIndex desIndex:(int)desIndex{
    id element = self.array[sourceIndex];
    
    for (int i = sourceIndex; i>desIndex; i--) {
        //self.array[i] = self.array[i-1];
        [self moveWithFromIndex:i-1 toIndex:i];
    }
    
    self.array[desIndex] = element;
}



/// 获取index位置的元素将要出入到的位置索引
/// @param index 元素的索引
-(int)searchIndex:(int)index{
    if(self.array == nil || self.array.count == 0 ) return -1;
    
    id element = self.array[index];
    int beigin = 0;
    int end = index;
    while (beigin<end) {
        int mid = (beigin + end) >>1;
        if ([self compareWithValue1:element value2:self.array[mid]] == -1) {
            end = mid;
        }else{
            beigin = mid+1;
        }
    }
    return beigin;
}

需要注意的是,使用了二分搜索后,只是减少了比较次数,但插入排序的平均时间复杂度依然是 O(n^2)


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.