JHHK

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

【进阶】4、排序(四)

目录

快速排序(Quick Sort)

快速排序(Quick Sort)
1960年由查尔斯·安东尼·理查德·霍尔(Charles Antony Richard Hoare,缩写为C. A. R. Hoare)提出
昵称为东尼·霍尔(Tony Hoare)

一、快速排序 – 执行流程

① 从序列中选择一个轴点元素(pivot)
✓ 假设每次选择 0 位置的元素为轴点元素

② 利用 pivot 将序列分割成 2 个子序列
✓ 将小于 pivot 的元素放在pivot前面(左侧)
✓ 将大于 pivot 的元素放在pivot后面(右侧)
✓ 等于pivot的元素放哪边都可以

③ 对子序列进行 ① ② 操作
✓ 直到不能再分割(子序列中只剩下1个元素)

img

快速排序的本质
逐渐将每一个元素都转换成轴点元素

二、快速排序的轴点构造

img

三、快速排序的时间复杂度

img

在轴点左右元素数量比较均匀的情况下,同时也是最好的情况
T n = 2 ∗ T n/2 + O n = O(nlogn)

如果轴点左右元素数量极度不均匀,最坏情况
T n = T n − 1 + O n = O(n2)

为了降低最坏情况的出现概率,一般采取的做法是
随机选择轴点元素

最好、平均时间复杂度:O(nlogn)
最坏时间复杂度:O(n2)
由于递归调用的缘故,空间复杂度:O(logn)
属于不稳定排序

四、快速排序的实现

/**
 最好、平均时间复杂度:O(nlogn) 最坏时间复杂度:O(n2)
 由于递归调用的缘故,空间复杂度:O(logn)
 */
-(void)sort{
    
    [self sortWithBegin:0 end:(int)self.array.count];
}



/// 快速排序[beigin end)
/// @param begin 起始index
/// @param end 结束index
-(void)sortWithBegin:(int)begin end:(int)end{
    
    //至少需要两个元素
    if(end - begin<2) return;
    
    //找到轴点元素的位置
    int mid = [self pivotIndexWithBegin:begin end:end];
    
    
    //对左半部分进行快速排序
    [self sortWithBegin:begin end:mid];
    
    
    //对有半部分进行快速排序
    [self sortWithBegin:mid+1 end:end];
    
}




/// 返回轴点元素索引[begin end)
/// @param begin 起始索引
/// @param end 终止索引
-(int)pivotIndexWithBegin:(int)begin end:(int)end{
    
    //为了降低最坏情况的出现概率,一般采取的做法是 随机选择轴点元素
    int randomIndex = begin+ random()%(end - begin);//[begin end)
    [self swapWithIndex1:begin index2:randomIndex];
    
    //备份pivot元素
    id pivot = self.array[begin];
    
    //因为是左闭右开所以end--;
    end--;

    //当begin = end时,结束
    while (begin<end) {
        
        
        while(begin<end){
            //末尾元素大于pivot,end-- 思考:可以将条件改为 <=0 吗? 不可以
            if ([self compareWithValue1:pivot value2:self.array[end]] < 0) {
                end--;
                
            //末尾元素小于等于pivot,进行交换 begin++
            }else{
                //self.array[begin] = self.array[end];
                [self moveWithFromIndex:end toIndex:begin];
                begin++;
                
                //结束,进入轮换
                break;
            }
        }
        
        
        while(begin<end){
            //起始元素小于末尾元素,begin++ 思考:可以将条件改为 >=0 吗? 不可以
            if ([self compareWithValue1:pivot value2:self.array[begin]] > 0) {
                begin++;
                
            //起始元素大于等于末尾元素,进行交换,end--
            }else{
                //self.array[end] = self.array[begin];
                [self moveWithFromIndex:begin toIndex:end];
                end--;
                
                //结束,进入轮换
                break;
            }
        }
    }
    
    //放置轴点元素
    self.array[begin] = pivot;
    
    //执行到此处begin == end 返回begin或end都可以。该位置就是pivot的索引
    return begin;
}
    

五、快速排序与轴点相等的元素

img

如果序列中的所有元素都与轴点元素相等,利用目前的算法实现,轴点元素可以将序列分割成 2 个均匀的子序列

思考:cmp 位置的判断分别改为 ≤、≥ 会起到什么效果?

轴点元素分割出来的子序列极度不均匀
导致出现最坏时间复杂度 O(n2)

img

希尔排序(Shell Sort)

一、希尔排序

1959年由唐纳德·希尔(Donald Shell)提出
希尔排序把序列看作是一个矩阵,分成 𝑚 列,逐列进行排序
从某个整数逐渐减为1
当 𝑚 为1时,整个序列将完全有序
因此,希尔排序也被称为递减增量排序(Diminishing Increment Sort)

矩阵的列数取决于步长序列(step sequence)
✓ 比如,如果步长序列为{1,5,19,41,109,…},就代表依次分成109列、41列、19列、5列、1列进行排序
✓ 不同的步长序列,执行效率也不同

二、希尔排序示例一

希尔本人给出的步长序列是 𝑛/2^𝑘,比如 𝑛 为16时,步长序列是{1, 2, 4, 8}

img

分成8列进行排序

img

分成4列进行排序

img

分成2列进行排序

img

分成1列进行排序

img

不难看出来,从8列 变为 1列的过程中,逆序对的数量在逐渐减少
因此希尔排序底层一般使用插入排序对每一列进行排序,也很多资料认为希尔排序是插入排序的改进版

三、希尔排序示例二

假设有11个元素,步长序列是{1, 2, 5}

img

假设元素在第 col 列、第 row 行,步长(总列数)是 step
✅ 那么这个元素在数组中的索引是 col + row * step
✅ 比如 9 在排序前是第 2 列、第 0 行,那么它排序前的索引是 2 + 0 * 5 = 2
✅ 比如 4 在排序前是第 2 列、第 1 行,那么它排序前的索引是 2 + 1 * 5 = 7

四、希尔排序实现

-(void)sort{
    
    NSArray * stepSequence = [self shellStepSequence];
    
    for (int i = 0; i<stepSequence.count; i++) {
        [self sortWithStep: [stepSequence[i] intValue]];
    }
}





/// 按步长进行排序
/// @param step 步长
-(void)sortWithStep:(int)step{
    
    for (int col = 0; col<step; col++) {
        
        
        //希尔排序是基于插入排序的
        //col col+step col+2*step ...
        for (int begin = col+step; begin<self.array.count; begin+=step) {
            int current = begin;
            while(current>col &&[self compareWithIndex1:current index2:current-step]<0){
                [self swapWithIndex1:current index2:current-step];
                current-=step;
            }
        }
        
        
    }
}


/// 获取希尔步长序列
-(NSArray*)shellStepSequence{
    NSMutableArray * array = [NSMutableArray array];
    int stepSequence = (int)self.array.count;
    while ((stepSequence>>=1)>0   ) {
        [array addObject:@(stepSequence)];
    }
    return array;
}

最好情况是步长序列只有1,且序列几乎有序,时间复杂度为 O(n)
空间复杂度为O(1),属于不稳定排序

五、希尔排序步长

希尔本人给出的步长序列,最坏情况时间复杂度是 O(n2)

目前已知的最好的步长序列,最坏情况时间复杂度是 O(n^(4/3)) ,1986年由Robert Sedgewick提出

img

/// Robert Sedgewick 提出
-(NSArray*)sedgewickStepSequence{
    NSMutableArray * array = [NSMutableArray array];
    
    int k = 0,step = 0;
    
    while (true) {
        if (k%2 == 0) {
            int pow0 = pow(2, k >> 1);//计算以x为底数的y次幂
            step = 1+9*(pow0*pow0-pow0);
        }else{
            int pow1 = pow(2,(k-1)>>1);
            int pow2 = pow(2,(k+1)>>1);
            step = 1 + 8 * pow1 * pow2 - 6 * pow2;
        }
        
        if (step>=self.array.count) {
            break;
        }
        
        [array insertObject:@(step) atIndex:0];
        k++;
    }

    return array;
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.