zoukankan      html  css  js  c++  java
  • STL中的算法小结

    (1)要运用STL的算法,首先必须包含头文件<algorithm>,某些STL算法用于数值处理,因此被定义于头文件<numeric>

    (2)所有STL算法都被设计用来处理一个或多个迭代器区间,第一个区间通常以起点和终点表示,至于其他区间,多数情况下你只需提供起点便足以,其终点可以自动以第一个区间的元素数量推断出来,调用者必须保证这些区间的有效性。
    STL算法采用覆盖模式而非安插模式,所以调用者必须保证目标区间拥有足够的元素空间,当然你也可以运用特殊的安插型迭代器将覆盖模式改变为安插模式。

    (3)尾词_if:如果算法有两种形式,参数个数都相同,但第一形式的参数要求传递一个值,第二形式的参数要求传递一个函数或仿函数,那么尾词_if就派上了用场,无尾词的那个要求传递数值,有尾词的那个要求传递函数或仿函数。不过并非所有“要求传递仿函数”的算法都有尾词_if,如果算法以额外参数来接受这样的函数或仿函数,那么不同版本的算法就可以采用相同的命名。
    尾词_copy:这个尾词用来表示在此算法中,元素不光被操作,还会被复制到目标区间。

    (4)for_each()算法:
    UnaryProc for_each (InputIterator beg, InputIterator end, UnaryProc op);
    1对区间[beg, end)中的每一个元素调用op(elem)
    2返回op(已在算法内部被变动过)的一个副本
    3op的任何返回值都会被忽略
    4op可以变动元素
    5复杂度:线性

    (5)元素计数算法:
    difference_type count (InputIterator beg, InputIterator end, const T& value);
    difference_type count_if (InputIterator beg, InputIterator end, UnaryPredicate op);
    1第一形式会计算区间[beg, end)中的元素等于value的元素个数
    2第二形式会计算区间[beg, end)中令以下一元判断式结果为true的元素个数:op(elem)
    3返回值型别为difference_type,是表现迭代器间距的型别
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6关联式容器(set/multiset,map/multimap)提供了一个等效的成员函数count()用来计算等于某个value或key的元素个数
    7时间复杂度:线性

    (6)最大最小值算法:
    InputIterator min_element (InputIterator beg, InputIterator end);
    InputIterator min_element (InputIterator beg, InputIterator end, CompFunc op);
    InputIterator max_element (InputIterator beg, InputIterator end);
    InputIterator max_element (InputIterator beg, InputIterator end, CompFunc op);
    1所有这些算法都返回区间[beg, end)中的最大或最小值的位置
    2上述无op参数版本以operator<进行元素比较
    3op用来比较两个元素:op(elem1, elem2)如果第一个元素小于第二个元素,应当返回true
    4如果存在多个最大或最小值,上述算法返回找到的第一个最大或最小值
    5op不应该改动传进去的参数
    6时间复杂度:线性

    (7)搜寻元素算法:
    InputIterator find (InputIterator beg, InputIterator end, const T& value);
    InputIterator find_if (InputIterator beg, InputIterator end, UnaryPredicate op);
    1第一形式返回区间[beg, end)中的第一个“元素值等于value”的元素位置
    2第二形式返回区间[beg, end)中令一元判断式op(elem)结果为true的第一个元素的位置
    3如果没有找到匹配元素,两种形式都返回end
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6如果是已序区间,应使用lower_bound(),upper_bound(),equal_range()或binary_search()算法以获取更高的性能
    7关联式容器(set/multiset,map/multimap)提供了一个等效的成员函数find(),拥有对数时间复杂度
    8时间复杂度:线性

    InputIterator search_n (InputIterator beg, InputIterator end, Size count, const T& value);
    InputIterator search_n (InputIterator beg, InputIterator end, Size count, const T& value, BinaryPredicate op);
    1第一形式返回区间[beg, end)中的第一组“连续count个元素值全等于value”的元素位置
    2第二形式返回区间[beg, end)中的第一组“连续count个元素使op(elem, value)结果为true”的元素位置
    3如果没有找到匹配元素,两种形式都返回end
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6时间复杂度:线性

    ForwardIterator1 search (ForwardIterator1 beg, ForwardIterator1 end, ForwardIterator2 searchBeg, ForwardIterator2 searchEnd);
    ForwardIterator1 search (ForwardIterator1 beg, ForwardIterator1 end, ForwardIterator2 searchBeg, ForwardIterator2 searchEnd, BinaryPredicate op);
    1两种形式都返回区间[beg, end)内“和区间[searchBeg, searchEnd)完全吻合”的第一个子区间内的第一个元素位置
    2第一形式中,子区间元素必须完全等于[searchBeg, searchEnd)的元素
    3第二形式中,子区间元素和[searchBeg, searchEnd)的对应元素必须使op(elem, searchElem)结果为true
    4如果没有找到匹配子区间,两种形式都返回end
    5注意op在函数调用过程中不应该改变自身状态
    6op不应该改动传进来的参数
    7时间复杂度:线性

    ForwardIterator find_end (ForwardIterator beg, ForwardIterator end, ForwardIterator searchBeg, ForwardIterator searchEnd);
    ForwardIterator find_end (ForwardIterator beg, ForwardIterator end, ForwardIterator searchBeg, ForwardIterator searchEnd, BinaryPredicate op);
    1两种形式都返回区间[beg, end)内“和区间[searchBeg, searchEnd)完全吻合”的最后一个子区间内的第一个元素位置
    2第一形式中,子区间元素必须完全等于[searchBeg, searchEnd)的元素
    3第二形式中,子区间元素和[searchBeg, searchEnd)的对应元素必须使op(elem, searchElem)结果为true
    4如果没有找到匹配子区间,两种形式都返回end
    5注意op在函数调用过程中不应该改变自身状态
    6op不应该改动传进来的参数
    7时间复杂度:线性

    ForwardIterator find_first_of (ForwardIterator1 beg, ForwardIterator1 end, ForwardIterator2 searchBeg, ForwardIterator2 searchEnd);
    ForwardIterator find_first_of (ForwardIterator1 beg, ForwardIterator1 end, ForwardIterator2 searchBeg, ForwardIterator2 searchEnd, BinaryPredicate op);
    1第一形式返回第一个“既在区间[beg, end)中出现,也在区间[searchBeg, searchEnd)中出现”的元素位置
    2第二形式返回区间[beg, end)中的第一个这样的元素:他和区间[searchBeg, searchEnd)内的每一个元素进行op(elem, searchElem)结果都为ture
    3如果没有找到匹配元素,两种形式都返回end
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6你可以使用逆向迭代器来搜寻最后一个这样的元素
    7时间复杂度:线性

    InputIterator adjacent_find (InputIterator beg, InputIterator end);
    InputIterator adjacent_find (InputIterator beg, InputIterator end, BinaryPredicate op);
    1第一形式返回区间[beg, end)中的第一对“连续两个相等元素”之中的第一个元素位置
    2第二形式返回区间[beg, end)中的第一对“连续两个元素均使op(elem, nextElem)结果为true“的其中第一个元素位置
    3如果没有找到吻合元素,两种形式都返回end
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6时间复杂度:线性

    bool equal (InputIterator1 beg, InputIterator1 end, InputIterator2 cmpBeg);
    bool equal (InputIterator1 beg, InputIterator1 end, InputIterator2 cmpBeg, BinaryPredicate op);
    1第一形式判断区间[beg, end)内的元素是否都和以cmpBeg开头的区间内的元素相等
    2第二形式判断区间[beg, end)内的元素和以cmpBeg开头的区间内的对应元素是否都能使op(elem, cmpElem)结果为true
    3调用者必须保证以cmpBeg开头的区间具有足够的元素
    4当序列不相等时,如果想要了解其间的不同,应使用mismatch()算法
    5注意op在函数调用过程中不应该改变自身状态
    6op不应该改动传进来的参数
    7时间复杂度:线性

    pair<InputIterator1, InputIterator2> mismatch (InputIterator1 beg, InputIterator1 end, InputIterator2 cmpBeg);
    pair<InputIterator1, InputIterator2> mismatch (InputIterator1 beg, InputIterator1 end, InputIterator2 cmpBeg, BinaryPredicate op);
    1第一形式返回区间[beg, end)和以cmpBeg开头的区间之中的第一组两两相异的对应元素
    2第二形式返回区间[beg, end)和以cmpBeg开头的区间之中的第一组”使op(elem, cmpElem)结果为false“的对应元素
    3如果没有找到相异点,就返回一个pair,以end和第二序列的对应元素组成。这并不意味着两个序列相等,因为第二序列有可能包含更多的元素
    4调用者必须保证以cmpBeg开头的区间具有足够的元素
    5如果想要了解两个序列是否相等,应使用equal()算法
    6注意op在函数调用过程中不应该改变自身状态
    7op不应该改动传进来的参数
    8时间复杂度:线性

    bool lexicographical_compare (InputIterator1 beg1, InputIterator1 end1, InputIterator2 beg2, InputIterator2 end2);
    bool lexicographical_compare (InputIterator1 beg1, InputIterator1 end1, InputIterator2 beg2, InputIterator2 end2, CompFunc op);
    1两种形式都用来判断区间[beg1, end1)的元素是否小于区间[beg2, end2)的元素。所谓”小于“是指本着”字典次序“的意义
    2第一形式使用operator<
    3第二形式使用op(elem1, elem2)来判断
    4注意op在函数调用过程中不应该改变自身状态
    5op不应该改动传进来的参数
    6时间复杂度:线性

    (8)复制元素算法:
    OutputIterator copy (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg);
    BidirectionalIterator1 copy_backward (BidirectionalIterator1 sourceBeg, BidirectionalIterator1 sourceEnd, BidirectionalIterator2 destEnd);
    1这两个算法都将源区间[sourceBeg, sourceEnd)中的所有元素复制到以destBeg为起点或以destEnd为终点的目标区间里去
    2返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    3destBeg或destEnd不可处于[sourceBeg, sourceEnd)区间内
    4copy()正向遍历序列,copy_backward()逆向遍历序列
    5STL没有所谓的copy_if()算法,所以如果要复制符合某特定准则的元素,请使用remove_copy_if()算法
    6如果希望在复制过程中逆转元素次序,应该使用reverse_copy()
    7调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    8如果想把容器内的所有元素赋值给另一容器,应当使用assignment操作符(当两个容器的型别相同时)或者使用容器的assign()成员函数(当两个容器的型别不同时)
    9如果希望在复制过程中删除某元素,请使用remove_copy()和remove_copy_if()算法
    10如果希望在复制过程中改变元素,请使用transform()或replace_copy()算法
    11时间复杂度:线性

    (9)转换元素算法:
    OutputIterator transform (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, UnaryFunc op);
    1针对源区间[sourceBeg, sourceEnd)中的每一个元素调用op(elem)并将结果写到以destBeg为起点的目标区间
    2返回目标区间内最后一个被转换的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    3调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    4sourceBeg和destBeg可以相同,因此你可以使用这个算法来变动某一序列内的元素
    5如果想以某值来替换符合某一标准的元素,应使用replace()算法
    6时间复杂度:线性

    OutputIterator transform (InputIterator1 source1Beg, InputIterator1 source1End, InputIterator2 source2Beg, OutputIterator destBeg, BinaryFunc op);
    1针对第一个源区间[source1Beg, source1End)以及从source2Beg开始的第二个源区间的对应元素调用op(source1Elem, source2Elem)并将结果写到以destBeg起始的目标区间
    2返回目标区间内最后一个被转换的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    3调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    4调用者必须保证第二源区间有足够空间(至少拥有和第一源区间相同的空间大小)
    5source1Beg,source2Beg,destBeg可以相同,因此你可以让元素自己和自己结合然后将结果覆盖至某个序列
    6时间复杂度:线性

    (10)互换元素内容算法:
    ForwardIterator2 swap_ranges (ForwardIterator1 beg1, ForwardIterator1 end1, ForwardIterator2 beg2);
    1将区间[beg1, end1)内的元素和从beg2开始的区间内的对应元素互换
    2返回第二区间中的最后一个被交换元素的下一位置
    3调用者必须保证目标区间有足够空间
    4两区间不得重叠
    5如果要将相同型别的两个容器内的全部元素互换,应使用swap()成员函数这样通常更快
    6时间复杂度:线性

    (11)赋值算法:
    void fill (ForwardIterator beg, ForwardIterator end, const T& newValue);
    void fill_n (OutputIterator beg, Size num, const T& newValue);
    1fill()将区间[beg, end)内的每一个元素都赋予新值newValue
    2fill_n()将从beg开始的前num个元素赋予新值newValue
    3调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    4时间复杂度:线性

    void generate (ForwardIterator beg, ForwardIterator end, Func op);
    void generator_n (OutputIterator beg, Size num, Func op);
    1generate()调用op产生新值并赋给区间[beg, end)内的每一个元素
    2generate_n()调用op产生新值并赋给以beg开始的区间内的前num个元素
    3调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    4时间复杂度:线性

    (12)替换元素算法:
    void replace (ForwardIterator beg, ForwardIterator end, const T& oldValue, const T& newValue);
    void replace_if (ForwardIterator beg, ForwardIterator end, UnaryPredicate op, const T& newValuedValue);
    1replace()将区间[beg, end)内每一个与oldValue相等的元素替换为newValue
    2replace_if()将区间[beg ,end)内每一个令op(elem)为true的元素替换为newValue
    3op不应该在函数调用过程中改变自身状态
    4时间复杂度:线性

    OutputIterator replace_copy (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, const T& oldValue, const T& newValue);
    OutputIterator replace_copy_if (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, UnaryPredicate op, const T& newValue);
    1replace_copy()是copy()和replace()的组合,它将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg开始的目标区间去,同时将与oldValue相等的所有元素替换成newValue
    2replace_copy_if()是copy()和replace_if()的组合,它将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg开始的目标区间去,同时将令op(elem)结果为true的所有元素替换成newValue
    3俩个算法都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4op不应该在函数调用过程中改变自身状态
    5时间复杂度:线性

    (13)移除性算法:
    ForwardIterator remove (ForwardIterator beg, ForwardIterator end, const T& value);
    ForwardIterator remove_if (ForwardIterator beg, ForwardIterator end, UnaryPredicate op);
    1remove()会移除区间[beg, end)中的每一个与value相等的元素
    2remove_if()会移除区间[beg, end)中的每一个令op(elem)结果为true的元素
    3两个算法都会返回变动后的序列的新逻辑终点(也就是最后一个未被移除元素的下一个位置)
    4这些算法会把原来置于后面的未被移除元素向前移动,覆盖被移除元素
    5未被移除元素在相对次序上保持不变
    6调用者在调用此算法后,应保证从此采用返回的新逻辑终点而不再使用原始终点end
    7op不应该在函数调用过程中改变自身状态
    8由于会发生元素变动,所以这些算法不可用于关联式容器,关联式容器提供了功能相似的成员函数erase()
    9list提供了一个等效成员函数remove():不是重新赋值元素,而是重新安排指针
    10时间复杂度:线性

    OutputIterator remove_copy (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, const T& value);
    OutputIterator remove_copy_if (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, UnaryPredicate op);
    1remove_copy()是copy()和remove()的组合,它将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg开始的目标区间去,同时移除与value相等的所有元素
    2remove_copy_if()是copy()和remove_if()的组合,它将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg开始的目标区间去,同时移除令op(elem)结果为true的所有元素
    3俩个算法都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4op不应该在函数调用过程中改变自身状态
    5调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    6时间复杂度:线性

    ForwardIterator unique (ForwardIterator beg, ForwardIterator end);
    ForwardIterator unique (ForwardIterator beg, ForwardIterator end, BinaryPredicate op);
    1两个算法都会移除连续重复元素中的多余元素
    2第一形式将区间[beg, end)内的所有与前一个元素相等的元素移除,所以源区间必须先经过排序,才能使用这个算法
    3第二形式将每一个“位于元素e之后并且造成op(elem ,e)结果为true”的所有elem元素移除,换言之此op并非用来将元素和其原本的前一元素比较,而是将它和未被移除的前一元素比较
    4两种形式都返回被变动后的序列的新终点(最后一个未被移除的元素的下一个位置)
    5两个算法将原本位置在后的未被移除元素向前移动,覆盖被移除元素
    6未被移除元素在相对次序上保持不变
    7调用者在调用此算法后,应保证从此采用返回的新逻辑终点而不再使用原始终点end
    8op不应该在函数调用过程中改变自身状态
    9由于会发生元素变动,所以这些算法不可用于关联式容器
    10list提供了一个等效成员函数unique():不是重新赋值元素,而是重新安排指针
    11时间复杂度:线性

    OutputIterator unique_copy (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg);
    OutputIterator unique_copy (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, BinaryPredicate op);
    1两种形式都是copy()和unique()的组合
    2两者都将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg开始的目标区间去,并移除重复元素
    3俩个算法都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    5时间复杂度:线性

    (14)变序性算法:
    void reverse (BidirectionalIterator beg, BidirectionalIterator end);
    OutputIterator reverse_copy (BidirectionalIterator sourceBeg, BidirectionalIterator sourceEnd, OutputIterator destBeg);
    1reverse()将区间[beg, end)内的元素全部逆序
    2reverse_copy()会将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg起始的目标区间,并在复制过程中颠倒安置次序
    3reverse_copy()返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    5list提供了一个等效成员函数reverse():不是重新赋值元素,而是重新安排指针
    6时间复杂度:线性

    void rotate (ForwardIterator beg, ForwardIterator newBeg, ForwardIterator end);
    1将区间[beg, end)内的元素进行旋转,执行后*newBeg成为新的第一元素
    2调用者必须保证newBeg是区间[beg, end)内的一个有效位置,否则会引发未定义的行为
    3时间复杂度:线性

    void rotate_copy (ForwardIterator sourceBeg, ForwardIterator newBeg, ForwardIterator sourceEnd, OutputIterator destBeg);
    1这是rotate()和copy()的组合
    2将源区间[sourceBeg, sourceEnd)内的元素复制到以destBeg起始的目标区间中,同时旋转元素使*newBeg成为新的第一元素
    3返回目标区间内最后一个被复制的元素的下一个位置
    4调用者必须保证newBeg是区间[sourceBeg, sourceEnd)内的一个有效位置,否则会引发未定义的行为
    5调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    6源区间和目标区间不可重叠
    7时间复杂度:线性

    bool next_permutation (BidirectionalIterator beg, BidirectionalIterator end);
    bool prev_permutation (BidirectionalIterator beg, BidirectionalIterator end);
    1next_permutation()会改变区间[beg, end)内的元素次序,使它符合“下一个排列次序”
    2prev_permutation()会改变区间[beg, end)内的元素次序,使它符合“上一个排列次序”
    3如果元素得以排列成“正规”次序,则两个算法都返回true。所谓正规次序,对next_permutation()而言为升序,对prev_permutation()而言为降序。因此如果要走遍所有排序,你必须先将所有元素(按升序或降序)排序,然后开始以循环方式调用prev_permutation()或next_permutation()直到算法返回false
    4时间复杂度:线性

    void random_shuffle (RandomAccessIterator beg, RandomAccessIterator end);
    void random_shuffle (RandomAccessIterator beg, RandomAccessIterator end, RandomFunc& op);
    1第一形式使用一个均匀分布随机数产生器来打乱区间[beg, end)内的元素次序
    2第二形式使用op来打乱区间[beg, end)内的元素次序。算法内部会使用一个整数值来调用op:op(max),它应该返回一个大于零而小于max的随机数但不包括max自身
    3注意op是一个non-const reference,所有你不可以将暂时数值或一般函数传进去
    4时间复杂度:线性

    BidirectionalIterator partition (BidirectionalIterator beg, BidirectionalIterator end, UnaryPredicate op);
    BidirectionalIterator stable_partition (BidirectionalIterator beg, BidirectionalIterator end, UnaryPredicate op);
    1两种算法将区间[beg, end)中的“造成op(elem)结果为true”的元素向前端移动
    2两种算法都返回“令op(elem)结果为false”的第一个元素位置
    3两者差别为无论元素是否符合给定的准则,stable_partition()会保持它们之间的相对次序
    4你可以运用此算法根据排序准则将所有元素分割为两部分,nth_element()具有类似能力
    5op不应该在函数调用中改变自身状态
    6时间复杂度:partition()为线性,stable_partition()当系统有足够的内存时为线性、不足时为O(nlgn)

    (15)排序算法:
    void sort (RandomAccessIterator beg, RandomAccessIterator end);
    void sort (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    void stable_sort (RandomAccessIterator beg, RandomAccessIterator end);
    void stable_sort (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    1sort()和stable_sort()的上述第一形式使用operator<对区间[beg, end)内的所有元素进行排序
    2sort()和stable_sort()的上述第二形式使用op(elem1, elem2)作为排序准则对区间[beg, end)内的所有元素进行排序
    3op不应该在函数调用中改变自身状态
    4sort()和stable_sort()的区别是后者保证相等元素的原本相对次序在排序后保持不变
    5不可以对list调用这些算法,因为list不支持随机存取迭代器不过list提供了一个成员函数sort()
    6时间复杂度:sort():O(nlgn),stable_sort()当系统有足够的内存时为O(nlgn)、不足时为O(nlgn*lgn)

    void partial_sort (RandomAccessIterator beg, RandomAccessIterator sortEnd, RandomAccessIterator end);
    void partial_sort (RandomAccessIterator beg, RandomAccessIterator sortEnd, RandomAccessIterator end, BinaryPredicate op);
    1上述第一形式使用operator<对区间[beg, end)内的所有元素进行排序,使区间[beg, sortEnd)内的元素处于有序状态
    2上述第二形式使用op(elem1, elem2)作为排序准则对区间[beg, end)内的所有元素进行排序,使区间[beg, sortEnd)内的元素处于有序状态
    3op不应该在函数调用中改变自身状态
    4和sort()不同的是partial_sort()并不对全部元素进行排序:一旦第一个元素直至sortEnd之间的所有元素都排好次序就立刻停止,不会对剩余的元素进行排序
    5如果end和sortEnd相等,那么partial_sort()会对整个序列进行排序,平均而言其效率不及sort(),不过最差情况而言优于sort()
    6时间复杂度:线性和O(nlgn)之间

    RandomAccessIterator partial_sort_copy (InputIterator sourceBeg, InputIterator sourceEnd, RandomAccessIterator destBeg, RandomAccessIterator destEnd);
    RandomAccessIterator partial_sort_copy (InputIterator sourceBeg, InputIterator sourceEnd, RandomAccessIterator destBeg, RandomAccessIterator destEnd, BinaryPredicate op);
    1两者都是copy()和partial_sort()的组合
    2它们将元素从源区间[sourceBeg, sourceEnd)复制到目标区间[destBeg, destEnd)同时排序
    3被排序元素的数量是源区间和目标区间两者所含元素数量的最小值
    4两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    5时间复杂度:线性和O(nlgn)之间

    void nth_element (RandomAccessIterator beg, RandomAccessIterator nth, RandomAccessIterator end);
    void nth_element (RandomAccessIterator beg, RandomAccessIterator nth, RandomAccessIterator end, BinaryPredicate op);
    1两种形式都对区间[beg, end)内的元素进行排序,使第n个位置上的元素就位,也就是说所有在位置n之前的元素小于等于它,所有位置在n之后的元素都大于等于它。这样你就得到了根据n位置上的元素分割开来的两个子序列,第一子序列的元素统统小于第二子序列的元素。如果你只需要n个最大或最小的元素,但不要求它们必须已序,那么这个算法就很有用。
    2上述第一形式使用operator<作为排序准则
    3上述第二形式使用op(elem1, elem2)作为排序准则
    4op不应该在函数调用中改变自身状态
    5时间复杂度:线性

    (16)Heap算法:
    void make_heap (RandomAccessIterator beg, RandomAccessIterator end);
    void make_heap (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    1两种形式都将[beg, end)内的元素转化为heap
    2第二形式将op(elem1, elem2)视为排序准则
    3只有在多于一个元素的情况下才有必要使用这些函数来处理heap
    4时间复杂度:线性

    void push_heap (RandomAccessIterator beg, RandomAccessIterator end);
    void push_heap (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    1两种形式都将end之前的最后一个元素加入原本就是个heap的[beg, end-1)区间内使整个区间[beg, end)成为一个heap
    2第二形式将op(elem1, elem2)视为排序准则
    3调用者必须保证进入函数时,区间[beg, end-1)内的元素原本已形成一个heap,而新元素紧跟其后
    4时间复杂度:对数

    void pop_heap (RandomAccessIterator beg, RandomAccessIterator end);
    void pop_heap (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    1以上两种形式都将heap[beg, end)内的最高元素也就是第一个元素移动到最后的位置上,并将剩余区间[beg, end-1)内的元素组织起来成为一个新heap
    2第二形式将op(elem1, elem2)视为排序准则
    3调用者必须保证进入函数时,区间[beg, end)内的元素原本已形成一个heap,而新元素紧跟其后
    4时间复杂度:对数

    void sort_heap (RandomAccessIterator beg, RandomAccessIterator end);
    void sort_heap (RandomAccessIterator beg, RandomAccessIterator end, BinaryPredicate op);
    1以上两种形式都将heap[beg, end)转换为一个已序序列
    2第二形式将op(elem1, elem2)视为排序准则
    3此算法一结束,该区间就不再是个heap了
    4调用者必须保证进入函数时,区间[beg, end)内的元素原本已形成一个heap,而新元素紧跟其后
    5时间复杂度:对数

    (17)搜寻元素:
    bool binary_search (ForwardIterator beg, ForwardIterator end, const T& value);
    bool binary_search (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op);
    1两种形式都用来判断已序区间[beg, end)中是否含有和value等值的元素
    2第二形式将op(elem1, elem2)视为排序准则
    3如果想获得被搜寻元素的位置,应使用lower_bound(),upper_bound()或equal_range()算法
    4调用者必须保证进入算法之际该区间已序
    5时间复杂度:如果搭配随机存取迭代器则为对数复杂度,否则为线性

    bool includes (InputIterator1 beg, InputIterator1 end, InputIterator2 searchBeg, InputIterator2 searchEnd);
    bool includes (InputIterator1 beg, InputIterator1 end, InputIterator2 searchBeg, InputIterator2 searchEnd, BinaryPredicate op);
    1两种形式都用来判断已序区间[beg, end)是否包含另一个已序区间[searchBeg, searchEnd)中的每一个元素。也就是说对于[searchBeg, searchEnd)中的每一个元素,如果[beg, end)必有一个对应的相等元素那么[searchBeg, searchEnd)肯定是[beg, end)的子集
    2第二形式将op(elem1, elem2)视为排序准则
    3调用者必须保证在进入算法之际两区间都应该已经按照相同的排序准则排好序了
    4时间复杂度:线性

    ForwardIterator lower_bound (ForwardIterator beg, ForwardIterator end, const T& value);
    ForwardIterator lower_bound (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op);

    ForwardIterator upper_bound (ForwardIterator beg, ForwardIterator end, const T& value);
    ForwardIterator upper_bound (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op);
    1lower_bound()返回第一个“大于等于value”的元素位置,这是可插入“元素值为value”且“不破坏区间[beg, end)已序性”的第一个位置
    2upper_bound()返回第一个“大于value”的元素位置,这是可插入“元素值为value”且“不破坏区间[beg, end)已序性”的最后一个位置
    3如果不存在其值为value的元素,上述算法都返回end
    4op(elem1, elem2)视为排序准则
    5调用者必须保证在进入算法之际所有区间都应该已经按照排序准则排好序了
    6如果要同时获得lower_bound()和upper_bound()的结果,请使用equal_range()
    7关联式容器分别提供等效成员函数,性能更好
    8时间复杂度:如果搭配随机存取迭代器则为对数复杂度,否则为线性

    pair<ForwardIterator, ForwardIterator> equal_range (ForwardIterator beg, ForwardIterator end, const T& value);
    pair<ForwardIterator, ForwardIterator> equal_range (ForwardIterator beg, ForwardIterator end, const T& value, BinaryPredicate op);
    1两种形式都返回与value相等的元素所形成的区间,在此区间内插入其值为value的元素并不会破坏区间[beg, end)的已序性
    2和下式等效:make_pair(lower_bound(……), upper_bound(……))
    3第二形式将op(elem1, elem2)视为排序准则
    4调用者必须保证在进入算法之际两区间都应该已经按照相同的排序准则排好序了
    5关联式容器分别提供等效成员函数,性能更好
    6时间复杂度:如果搭配随机存取迭代器则为对数复杂度,否则为线性

    (18)合并元素:
    OutputIterator merge (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg);
    OutputIterator merge (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op);
    1两者都将源区间[source1Beg, source1End)和[source2Beg, source2End)内的元素合并,使得以destBeg起始的目标区间内含两个源区间的所有元素
    2目标区间内的所有元素都将按顺序排列
    3两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4第二形式将op(elem1, elem2)视为排序准则
    5源区间没有任何变化
    6调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    7list提供了一个等效成员函数merge()
    8根据标准调用者应当确保两个源区间一开始都是已序,然而在大部分实作版本中上述算法可以将两个无序的源区间内的元素合并到一个无序的目标区间中,不过要考虑到这样的移植性
    9目标区间和源区间不得重叠
    10如果你要确保两个源区间中都存在的元素在目标区间中只出现一次,请使用set_union()
    11如果你要获得同时存在于两个源区间的所有元素,请使用set_intersection()
    12时间复杂度:线性

    OutputIterator set_union (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg);
    OutputIterator set_union (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op);
    1两者都将源区间[source1Beg, source1End)和[source2Beg, source2End)内的元素合并,得到以destBeg起始的目标区间——这个区间内含的元素要不来自第一源区间,要不就来自第二源区间,或是同时来自两个区间
    2同时出现于两个源区间内的元素在并集区间中将只出现一次。不过如果原来的某个源区间内原本就存在重复元素,则目标区间也会有重复元素,重复的个数是两个源区间内的重复个数的较大值
    3两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4第二形式将op(elem1, elem2)视为排序准则
    5源区间没有任何变化
    6调用者应当确保两个源区间一开始都是已序
    7调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    8目标区间和源区间不得重叠
    9若想得到两个源区间的全部元素,应使用merge()
    10时间复杂度:线性
    11目标区间内的所有元素都按顺序排列

    OutputIterator set_intersection (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg);
    OutputIterator set_intersection (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op);
    1两者都将源区间[source1Beg, source1End)和[source2Beg, source2End)内的元素合并,得到以destBeg起始的目标区间——这个区间内含的元素不但存在于第一源区间,也存在于第二源区间
    2如果原来的某个源区间内原本就存在重复元素,则目标区间也会有重复元素,重复的个数是两个源区间内的重复个数的较小值
    3两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    4第二形式将op(elem1, elem2)视为排序准则
    5源区间没有任何变化
    6调用者应当确保两个源区间一开始都是已序
    7调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    8目标区间和源区间不得重叠
    9目标区间内的所有元素都按顺序排列
    10时间复杂度:线性

    OutputIterator set_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg);
    OutputIterator set_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op);
    1两者都将源区间[source1Beg, source1End)和[source2Beg, source2End)内的元素合并,得到以destBeg起始的目标区间——这个区间内含的元素只存在于第一源区间,不存在于第二源区间
    2目标区间内的所有元素都按顺序排列
    3如果原来的某个源区间内原本就存在重复元素,则目标区间也会有重复元素,重复的个数是第一源区间内的重复个数减去第二源区间内的相应重复个数,如果第二源区间内的重复个数大于第一源区间内的相应重复个数,则目标区间内的对应重复个数将会是零
    4两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    5第二形式将op(elem1, elem2)视为排序准则
    6源区间没有任何变化
    7调用者应当确保两个源区间一开始都是已序
    8调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    9目标区间和源区间不得重叠
    10时间复杂度:线性

    OutputIterator set_symmetric_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg);
    OutputIterator set_symmetric_difference (InputIterator source1Beg, InputIterator source1End, InputIterator source2Beg, InputIterator source2End, OutputIterator destBeg, BinaryPredicate op);
    1两者都将源区间[source1Beg, source1End)和[source2Beg, source2End)内的元素合并,得到以destBeg起始的目标区间——这个区间内含的元素或存在于第一源区间,或存在于第二源区间,但不同时存在于两个源区间
    2目标区间内的所有元素都按顺序排列
    3如果原来的某个源区间内原本就存在重复元素,则目标区间也会有重复元素,重复的个数是两个源区间内的对应重复元素的个数差值
    4两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    5第二形式将op(elem1, elem2)视为排序准则
    6源区间没有任何变化
    7调用者应当确保两个源区间一开始都是已序
    8调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    9目标区间和源区间不得重叠
    10时间复杂度:线性

    void inplace_merge (BidirectionalIterator beg1, BidirectionalIterator end1beg2, BidirectionalIterator end2);
    void inplace_merge (BidirectionalIterator beg1, BidirectionalIterator end1beg2, BidirectionalIterator end2, BinaryPredicate op);
    1两者都是将已源区间[beg1, end1beg2)和[end1beg2, end2)的元素合并,使区间[beg1, end2)成为两者之总和(且形成已序)
    2时间复杂度:内存足够为线性否则为O(nlgn)

    (19)数值算法:
    运用数值算法需要包含头文件:#include<numeric>

    T accumulate (InputIterator beg, InputIterator end, T initValue);
    T accumulate (InputIterator beg, InputIterator end, T initValue, BinaryFunc op);
    1第一形式计算initValue和区间[beg, end)内的所有元素总和,具体地说它针对每一个元素调用initValue+=elem
    2第二形式计算initValue和区间[beg, end)内每一个元素进行op运算的结果,具体地说它针对每一个元素调用initValue=op(initValue, elem)
    3如果序列为空则两者都返回initValue
    4时间复杂度:线性

    T inner_product (InputIterator1 beg1, InputIterator1 end1,InputIterator2 beg2, T initValue);
    T inner_product (InputIterator1 beg1, InputIterator1 end1,InputIterator2 beg2, T initValue, BinaryFunc op1, BinaryFunc op2);
    1第一形式计算并返回[beg1, end1)区间和以beg2为起始的区间的对应元素组(再加上initValue)的内积。具体地说针对两区间内的每一组对应元素调用initValue=initValue+elem1*elem2
    2第二形式将[beg1, end1)区间和以beg2为起始的区间的对应元素组进行op2运算再和initValue进行op1运算并将结果返回。具体地说针对两区间内的每一组对应元素调用initValue=op1(initValue, op2(elem1, elem2))
    3如果第一区间为空则两者都返回initValue
    4调用者必须保证以beg2为起始的区间内含足够元素空间
    5op1和op2都不得变动其参数内容
    6时间复杂度:线性

    OutputIterator partial_sum (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg);
    OutputIterator partial_sum (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, BinaryFunc op);
    1第一形式计算源区间[sourceBeg, sourceEnd)中的每个元素的部分和,然后将结果写入以destBeg为起始的区间
    2第二形式计算源区间[sourceBeg, sourceEnd)中的每个元素和其先前所有元素进行op运算,然后将结果写入以destBeg为起始的区间
    3对于数列a1 a2 a3 ……它们分别计算a1, a1+a2, a1+a2+a3,……和a1, a1 op a2, a1 op a2 op a3,……
    4 两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    5第一形式相当于把一个相对值序列转换为一个绝对值序列,就此而言partial_sum()正好和adjacent_difference()互补
    6源区间和目标区间可以相同
    7调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    8op不得变动其参数内容
    9时间复杂度:线性

    OutputIterator adjacent_difference (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg);
    OutputIterator adjacent_difference (InputIterator sourceBeg, InputIterator sourceEnd, OutputIterator destBeg, BinaryFunc op);
    1第一形式计算源区间[sourceBeg, sourceEnd)中的每个元素和其紧邻前驱元素的差值,然后将结果写入以destBeg为起始的区间
    2第二形式计算源区间[sourceBeg, sourceEnd)中的每个元素和其紧邻前驱元素进行op运算,然后将结果写入以destBeg为起始的区间
    3第一个元素只是被很单纯的加以复制
    4对于数列a1 a2 a3 a4……它们分别计算a1, a2-a1, a3-a2,a4-a3, ……和a1, a2 op a1, a3 op a2, a4 op a3,……
    5 两者都返回目标区间内最后一个被复制的元素的下一个位置,也就是第一个未被覆盖的元素的位置
    6第一形式相当于把一个绝对值序列转换为一个相对值序列,就此而言partial_sum()正好和adjacent_difference()互补
    7源区间和目标区间可以相同
    8调用者必须保证目标区间有足够的空间,否则就得使用insert迭代器
    9op不得变动其参数内容
    10时间复杂度:线性

    (原创)详解KMP算法

    KMP算法应该是每一本《数据结构》书都会讲的,算是知名度最高的算法之一了,但很可惜,我大二那年压根就没看懂过~~~

    之后也在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么一回事,但总感觉有些地方自己还是没有完全懂明白。这两天花了点时间总结一下,有点小体会,我希望可以通过我自己的语言来把这个算法的一些细节梳理清楚,也算是考验一下自己有真正理解这个算法。

    什么是KMP算法:

    KMP是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的。其中第一位就是《计算机程序设计艺术》的作者!!

    KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。模式串就是关键字(接下来称它为P),如果它在一个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常用手段)。

     

    首先,对于这个问题有一个很单纯的想法:从左到右一个个匹配,如果这个过程中有某个字符不匹配,就跳回去,将模式串向右移动一位。这有什么难的?

    我们可以这样初始化:

     

    之后我们只需要比较i指针指向的字符和j指针指向的字符是否一致。如果一致就都向后移动,如果不一致,如下图:

     

    A和E不相等,那就把i指针移回第1位(假设下标从0开始),j移动到模式串的第0位,然后又重新开始这个步骤:

     

    基于这个想法我们可以得到以下的程序:

    复制代码
     1 /**
     2 
     3  * 暴力破解法
     4 
     5  * @param ts 主串
     6 
     7  * @param ps 模式串
     8 
     9  * @return 如果找到,返回在主串中第一个字符出现的下标,否则为-1
    10 
    11  */
    12 
    13 public static int bf(String ts, String ps) {
    14 
    15     char[] t = ts.toCharArray();
    16 
    17     char[] p = ps.toCharArray();
    18 
    19     int i = 0; // 主串的位置
    20 
    21     int j = 0; // 模式串的位置
    22 
    23     while (i < t.length && j < p.length) {
    24 
    25        if (t[i] == p[j]) { // 当两个字符相同,就比较下一个
    26 
    27            i++;
    28 
    29            j++;
    30 
    31        } else {
    32 
    33            i = i - j + 1; // 一旦不匹配,i后退
    34 
    35            j = 0; // j归0
    36 
    37        }
    38 
    39     }
    40 
    41     if (j == p.length) {
    42 
    43        return i - j;
    44 
    45     } else {
    46 
    47        return -1;
    48 
    49     }
    50 
    51 }
    复制代码

    上面的程序是没有问题的,但不够好!(想起我高中时候数字老师的一句话:我不能说你错,只能说你不对~~~)

    如果是人为来寻找的话,肯定不会再把i移动回第1位,因为主串匹配失败的位置前面除了第一个A之外再也没有A,我们为什么能知道主串前面只有一个A?因为我们已经知道前面三个字符都是匹配的!(这很重要)。移动过去肯定也是不匹配的!有一个想法,i可以不动,我们只需要移动j即可,如下图:

     

    上面的这种情况还是比较理想的情况,我们最多也就多比较了再次。但假如是在主串“SSSSSSSSSSSSSA”中查找“SSSSB”,比较到最后一个才知道不匹配,然后i回溯,这个的效率是显然是最低的。

    大牛们是无法忍受“暴力破解”这种低效的手段的,于是他们三个研究出了KMP算法。其思想就如同我们上边所看到的一样:“利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置。”

    所以,整个KMP的重点就在于当某一个字符与主串不匹配时,我们应该知道j指针要移动到哪

    接下来我们自己来发现j的移动规律:

     

    如图:C和D不匹配了,我们要把j移动到哪?显然是第1位。为什么?因为前面有一个A相同啊:

     

    如下图也是一样的情况:

     

    可以把j指针移动到第2位,因为前面有两个字母是一样的:

     

    至此我们可以大概看出一点端倪,当匹配失败时,j要移动的下一个位置k。存在着这样的性质:最前面的k个字符和j之前的最后k个字符是一样的

    如果用数学公式来表示是这样的

    P[0 ~ k-1] == P[j-k ~ j-1]

    这个相当重要,如果觉得不好记的话,可以通过下图来理解:

     

    弄明白了这个就应该可能明白为什么可以直接将j移动到k位置了。

    因为:

    当T[i] != P[j]时

    有T[i-j ~ i-1] == P[0 ~ j-1]

    由P[0 ~ k-1] == P[j-k ~ j-1]

    必然:T[i-k ~ i-1] == P[0 ~ k-1]

    公式很无聊,能看明白就行了,不需要记住。

    这一段只是为了证明我们为什么可以直接将j移动到k而无须再比较前面的k个字符。

    好,接下来就是重点了,怎么求这个(这些)k呢?因为在P的每一个位置都可能发生不匹配,也就是说我们要计算每一个位置j对应的k,所以用一个数组next来保存,next[j] = k,表示当T[i] != P[j]时,j指针的下一个位置。

    很多教材或博文在这个地方都是讲得比较含糊或是根本就一笔带过,甚至就是贴一段代码上来,为什么是这样求?怎么可以这样求?根本就没有说清楚。而这里恰恰是整个算法最关键的地方。

    复制代码
     1 public static int[] getNext(String ps) {
     2 
     3     char[] p = ps.toCharArray();
     4 
     5     int[] next = new int[p.length];
     6 
     7     next[0] = -1;
     8 
     9     int j = 0;
    10 
    11     int k = -1;
    12 
    13     while (j < p.length - 1) {
    14 
    15        if (k == -1 || p[j] == p[k]) {
    16 
    17            next[++j] = ++k;
    18 
    19        } else {
    20 
    21            k = next[k];
    22 
    23        }
    24 
    25     }
    26 
    27     return next;
    28 
    29 }
    复制代码

    这个版本的求next数组的算法应该是流传最广泛的,代码是很简洁。可是真的很让人摸不到头脑,它这样计算的依据到底是什么?

    好,先把这个放一边,我们自己来推导思路,现在要始终记住一点,next[j]的值(也就是k)表示,当P[j] != T[i]时,j指针的下一步移动位置

    先来看第一个:当j为0时,如果这时候不匹配,怎么办?

     

    像上图这种情况,j已经在最左边了,不可能再移动了,这时候要应该是i指针后移。所以在代码中才会有next[0] = -1;这个初始化。

    如果是当j为1的时候呢?

     

    显然,j指针一定是后移到0位置的。因为它前面也就只有这一个位置了~~~

    下面这个是最重要的,请看如下图:

      

     

    请仔细对比这两个图。

    我们发现一个规律:

    当P[k] == P[j]时,

    有next[j+1] == next[j] + 1

    其实这个是可以证明的:

    因为在P[j]之前已经有P[0 ~ k-1] == p[j-k ~ j-1]。(next[j] == k)

    这时候现有P[k] == P[j],我们是不是可以得到P[0 ~ k-1] + P[k] == p[j-k ~ j-1] + P[j]。

    即:P[0 ~ k] == P[j-k ~ j],即next[j+1] == k + 1 == next[j] + 1。

    这里的公式不是很好懂,还是看图会容易理解些。

    那如果P[k] != P[j]呢?比如下图所示:

     

    像这种情况,如果你从代码上看应该是这一句:k = next[k];为什么是这样子?你看下面应该就明白了。

     

    现在你应该知道为什么要k = next[k]了吧!像上边的例子,我们已经不可能找到[ A,B,A,B ]这个最长的后缀串了,但我们还是可能找到[ A,B ]、[ B ]这样的前缀串的。所以这个过程像不像在定位[ A,B,A,C ]这个串,当C和主串不一样了(也就是k位置不一样了),那当然是把指针移动到next[k]啦。

    有了next数组之后就一切好办了,我们可以动手写KMP算法了:

    复制代码
     1 public static int KMP(String ts, String ps) {
     2 
     3     char[] t = ts.toCharArray();
     4 
     5     char[] p = ps.toCharArray();
     6 
     7     int i = 0; // 主串的位置
     8 
     9     int j = 0; // 模式串的位置
    10 
    11     int[] next = getNext(ps);
    12 
    13     while (i < t.length && j < p.length) {
    14 
    15        if (j == -1 || t[i] == p[j]) { // 当j为-1时,要移动的是i,当然j也要归0
    16 
    17            i++;
    18 
    19            j++;
    20 
    21        } else {
    22 
    23            // i不需要回溯了
    24 
    25            // i = i - j + 1;
    26 
    27            j = next[j]; // j回到指定位置
    28 
    29        }
    30 
    31     }
    32 
    33     if (j == p.length) {
    34 
    35        return i - j;
    36 
    37     } else {
    38 
    39        return -1;
    40 
    41     }
    42 
    43 }
    复制代码

    和暴力破解相比,就改动了4个地方。其中最主要的一点就是,i不需要回溯了。

    最后,来看一下上边的算法存在的缺陷。来看第一个例子:

     

    显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]

    所以下一步我们应该是把j移动到第1个元素咯:

     

    不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

    显然,发生问题的原因在于P[j] == P[next[j]]

    所以我们也只需要添加一个判断条件即可:

    复制代码
    public static int[] getNext(String ps) {
    
        char[] p = ps.toCharArray();
    
        int[] next = new int[p.length];
    
        next[0] = -1;
    
        int j = 0;
    
        int k = -1;
    
        while (j < p.length - 1) {
    
           if (k == -1 || p[j] == p[k]) {
    
               if (p[++j] == p[++k]) { // 当两个字符相等时要跳过
    
                  next[j] = next[k];
    
               } else {
    
                  next[j] = k;
    
               }
    
           } else {
    
               k = next[k];
    
           }
    
        }
    
        return next;
    
    } 
    复制代码

    好了,至此。KMP算法也结束了。

    很奇怪,好像不是很难的东西怎么就把我困住这么久呢?

    仔细想想还是因为自己太浮躁了,以前总是草草应付,很多细节都没弄清楚,就以为自己懂了。结果就只能是似懂非懂的。要学东西真的需要静下心来。

     
     
    分类: 数据结构
  • 相关阅读:
    FreeCommander 学习手册
    String详解, String和CharSequence区别, StringBuilder和StringBuffer的区别 (String系列之1)
    StringBuffer 详解 (String系列之3)
    StringBuilder 详解 (String系列之2)
    java io系列26之 RandomAccessFile
    java io系列25之 PrintWriter (字符打印输出流)
    java io系列24之 BufferedWriter(字符缓冲输出流)
    java io系列23之 BufferedReader(字符缓冲输入流)
    java io系列22之 FileReader和FileWriter
    java io系列21之 InputStreamReader和OutputStreamWriter
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3267708.html
Copyright © 2011-2022 走看看