STL源码剖析学习十七:算法之其他算法

lower_bound 二分查找的一个版本,在已排序区间中查找value
如果区间中有该元素,则返回迭代器,指向第一个该元素
如果没有改元素,则返回一个不小于value的元素
返回值为:在不破坏排序的情况下,可插入value的位置

__lower_bound(first, last, value, Distance*, forward_iterator_tag)
{
    Distance len = 0;
    distance(first, last, len);
    Distance half;
    ForwardIterator middle;
    while(len > 0)
    {
        half = len >> 1;
        middle = first; //对于前向迭代器才需要用这种方式寻找位置,对于随机访问迭代器可以直接+ 
        first = advance(middle, half);
        if(*middle < value)
        {
            first = middle + 1;
            ++first;
            len = len - half - 1;
        }
        else //即使是相等,还需要迭代,因为要找出相等的元素中最前一个的位置 
        {
            len = half;
        }
    }
    return first;
}

 

upper_bound 和上述函数类似,寻找的是符合条件的位置的上限

__upper_bound(first, last, value, Distance*, forward_iterator_tag)
{
    Distance len = 0;
    distance(first, last, len);
    Distance half;
    ForwardIterator middle;
    
    while(len > 0)
    {
        half = len >> 1;
        middle = first;
        //和lower的区别在于,如果是相等,一个是向前搜索要查找到最前的位置
        //一个是向后搜索想要查找到最后的位置 
        if(*middle > value)
        {
            len = half;
        }
        else
        {
            first = middle;
            ++first;
            len = len - half - 1;
        }
    }
    return first;
} 

 


binary_search 在有序区间中查找某个元素

binary_search(first, last, value)
{
    ForwardIterator i = lower_bound(first, last, value);
    return i!=last && !(value<*i);//要学会运用这种逻辑运算 
}

 

函数实现原理如下:

在当前序列中,从尾端往前寻找两个相邻元素,前一个记为*i,后一个记为*ii,并且满足*i < *ii。然后再从尾端寻找另一个元素*j,如果满足*i < *j,即将第i个元素与第j个元素对调,并将第ii个元素之后(包括ii)的所有元素颠倒排序,即求出下一个序列了。


next_permutation/prev_permutation 返回下一个/上一个全排列

next_permutation(first, last)
{
    if(first == last)
        return false;
    BidirectionalIterator i = first;
    ++i;
    if(i == last)
        return false;
    i = last;
    --i;
    
    for(;;)
    {
        BidirectionalIterator ii = i;
        --i;
        if(*i < *ii)
        {
            BidirectionalIterator j = last;
            while(!(*i < *--j));
            iter_swap(i, j);
            reverse(ii, last);
        }
        if(i == last)
        {
            reverse(first, last);
            return false;
        }
    }
}

 

prev_permutation(first, last)
{
    if(first == last)
        return false;
    BidirectionalIterator i = first;
    ++i;
    if(i == last)
        return false;
    i = last;
    --i;
    
    for(;;)
    {
        BidirectionalIterator ii = i;
        --i;
        if(*ii < *i)
        {
            BidirectionalIterator j = last;
            while(!(*i < *--j));
            iter_swap(i, j);
            reverse(ii, last);
        }
        if(i == last)
        {
            reverse(first, last);
            return false;
        }
    }
}

 


random_shuffle 将区间内的元素随机重排,获得N!种全排列中的任意一种

__random_shuffle(first, last)
{
    if(first == last)
        return;
    for(RandomAccessIterator i=first+1; i!=last; ++i)
    {
        iter_swap(i, first+Distance(rand()%((i-first)+1)));
        //不理解这样就一定能产生随机化的结果。。 
    }
}

 

 

partial_sort/partial_sort_copy 将 first到last区间中的元素重新排列,使前middle-first 小的数按序排列在前面的区间中
其余的元素排列在后面的区间中,但是不保证顺序
前半序列采用堆排序,然后遍历后半序列,如果发现有比堆顶元素小的数则和堆顶交换,并且重新调整堆
遍历完后最小的前n 个数都在前半部分序列中,将该序列堆排序即可

partial_sort(first, middle, last)
{
    make_heap(first, middle);
    for(RandomAccessIterator i=middle; i<last; ++i)
    {
        if(*i < *first)
            __pop_heap(first, middle, i, T(*i), distance_type(first));
            //pop_heap中调用了adjust_heap将堆顶的元素放到i的位置上,并把i位置上的元素插入到堆里 
    } 
    sort_heap(first, middle);
} 

 

 

sort
STL 中的sort: 数据量大时采用快排,分段递归排序
一旦分段后的数据量小于某个门槛,为避免快排的递归调用带来的过大的额外负荷,就改用插入排序,
如果递归深度过大,就会改用堆排序

__insertion_sort(first, last)
{
    if(first == last)
        return;
    for(RandomAccessIterator i=first+1; i!=last; ++i)
    {
        __linear_insert(first, i, value_type(first));
    }
} 
__linear_insert(first, last, T*)
{
    T value = *last;
    if(value < *first)
    {
        copy_backward(first, last, last+1);
        *first = value;
    }
    else
        __unguarded_linear_insert(last, value);
}

__unguarded_linear_insert(last, value)
{
    RandomAccessIterator next = last;
    --next;
    while(value < *next)
    {
        *last = *next;
        last = next;
        --next;
    }
    *last = value;
}

 


一般的插入排序在内层循环中要进行两个判断,判断两者大小和判断是否越界
但是采用上面这种方式可以保证最小值必定在内循环区间的边缘,因此不需要判断越界,可以提高效率

快排的步骤:
1.如果s的元素是0或者1,结束
2.取s 中任何一个元素,当做枢纽v
3.将s 分割为l r两部分,使l内的元素都小于等于v,r内的元素都大于v
4.对l,r递归执行快排

media-of-three partitioning 取头尾中央三个位置的值的中值作为v

__ungarded_partition(first, last, pivot)
{
    while(1)
    {
        while(*first < pivot)
            ++first;
        --last;
        while(pivot < *last)
            --last;
        if(!(first<last))
            return first;
        iter_swap(first, last);
        ++first; 
    }
} 

 

快排的关键在于v 的选择,如果选择不当,会导致性能恶化
introsort 混合式排序方法 内省式排序
在一般情况下都和快排一样,但是当有分割行为有恶化的趋势时会自动检测,改成堆排序
综合性能又比一开始就用堆排序来的好

SGI STL的sort

sort(first, last)
{
    if(first != last)
    {
        __introsort_loop(first, last, value_type(first), __lg(last-first)*2);
        __final_insertion_sort(first, last);
    }
} 

 


其中__lg 用来控制分割恶化的情况

__introsort_loop(first, last, T*, depth_limit)
{
    while(last - first > __stl_threshold)//__stl_threshold = 16 16个元素以下就不用快排改用插入排序 
    {
        //当分割恶化时,改用堆排序 
        if(depth_limit == 0)
        {
            partial_sort(first, last, last);
            return;
        }
        --depth_limit;
        
        RandomAccessIterator cut = __unguarded_partition(first, last, T(__media(*first, *(first+(last-first)/2), *(last-1))));
        __introsort_loop(cut, last, value_type(first), dept_limit);
        last = cut;
    }
} 

 

当上述函数操作完成,整个区间内有多个元素个数小于16的子区间,其中的元素部分排序但是没有完全排序
继续调用插入排序对这些子区间分别进行排序。

 

equal_range返回一个pair是lower_bound和upper_bound的组合

__equal_range(first, last, value, Distance*, random_access_iterator_tag)
{
    Distance len = last - first;
    Distance half;
    RandomAccessIterator left, right, middle;
    
    while(len > 0)
    {
        half = len >> 1;
        middle = first + half;
        if(*middle < value)
        {
            first = middle + 1;
            len = len - half -1;
        }
        else if(value < *middle)
        {
            len = half;
        }
        else
        {
            left = lower_bound(first, middle, value);
            right = upper_bound(++middle, last, value);
            return pair<RandomAccessIterator, RandomAccessIterator>(left, right);
        }
    }
    return pair<RandomAccessIterator, RandomAccessIterator>(first, first);
    //如果没有找到value 则指向第一个大于value的值 
}

 

 

 inplace_merge 应用于有序区间,把两个连接在一起且各自有序的序列合并成一个序列,仍保持有序
 稳定操作,保持相对次序不变,如果有相同元素,第一个序列的排在前面
 内部实现时根据有无缓冲不同处理
 
 有缓冲区的情况下的辅助函数

__merge_adaptive(first, middle, last, len1, len2, buffer, buffer_size)
 {
     if(len1 <= len2 && len1 <= buffer_size)//缓冲区可以容纳序列1 
     {
         Pointer end_buffer = copy(first, middle, last);
         merge(buffer, end_buffer, middle, last, first);
    }
    else if(len2 <= buffer_size)//缓冲区可以容纳序列2 
    {
        Pointer end_buffer = copy(middle, last, buffer);
        __merge_backward(first, middle, buffer, end_buffer, last);
    }
    else//缓冲区两个都不能容纳:分割递归,让缓冲区长度减半,看是否能容纳 
    {
        BidirectionalIterator first_cut = first;
        BidirectionalIterator second_cut = middle;
        Distance len11 = 0;
        Distance len22 = 0;
        if(len1 > len2)
        {
            len11 = len1/2;
            advance(first_cut, len11);
            second_cut = lower_bound(middle, last, *first_cut);
            distance(middle, second_cut, len22);
        }
        else
        {
            len22 = len2 / 2;
            advance(second_cut, len22);
            first_cut = upper_bound(first, middle, *second_cut);
            distance(first, first_cut, len11);
        }
        BidirectionalIterator new_middle = __rotate_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size);
        __merge_adaptive(first, first_cut, new_middle, len11, len22, buffer, buffer_size);
        __merge_adaptive(new_middle, second_cut, last, len1-len11, len2-len22, buffer, buffer_size);
    }
 } 

 

 
 nth_element 重新排列区间,使迭代器nth 所指向的元素与整个区间内排序后,同一位置的元素同值
 保证nth-last 中没有任何一个元素不大于 first-nth 中的元素
 比较近似于partition
 
 不断用首尾中央三点中值为枢纽之分割法,将序列分割成更小的子序列,
 如果nth 落入左子序列, 就继续分割左子序列,如果落在右子序列就分割右子序列,最后对小于3的序列进行排序 

 nth_element(first, nth, last, T*)
 {
     while(last - first > 3)//当区间中元素过少时就改用插入排序 
     {
         RandomAccessIterator cut = __ungarded_partition(first, last, T(__media(*first, *(first+(last-first)/2),*(last-1))));
         if(cut <= nth)
             first = cut;
         else
             last = cut;
     }
     __insertion_sort(first, last);
 } 

 

 
 
 merge_sort 归并排序,需要额外的内存,内存之间的拷贝需要时间,但是实现简单

 mergesort(first, last)
 {
     n = distance(first, last);
     if(n == 0 || n == 1)
         return;
    else
    {
        BidirectionalIter mid = first + n / 2;
        mergesort(first, mid);
        mergesort(mid, last);
        inplace_merge(first, mid, last); 
    }
 } 

 

posted @ 2012-04-30 18:46  w0w0  阅读(298)  评论(0)    收藏  举报