公正的选举Unbiased election 《算法》2.5.16, 检测稳定性 《算法》2.5.17 , 强制稳定 《算法》2.5.18, Kendall tau距离 《算法》2.5.19

来源:互联网 发布:centos安装sftp 编辑:程序博客网 时间:2024/06/06 00:18
Sedgewick 算法第四版
习题2.5.16 公正的选举Unbiased election:(英文版题目见http://algs4.cs.princeton.edu/25applications/      网址里的creative problem 16)
         “为了避免对名字排在字母表靠后的候选人的偏见,加州在2003年的州长选举中奖所有候选人按照以下字母顺序排列:
R W Q O J M V A H B S G Z X N T C I E K U P D Y F L
创建一个遵守这种顺序的数据类型并编写一个用例California,在它的静态方法main中将字符串按照这种方式排序。假设所有字符全部是大写的.“

思路一:
       每次遇见一个字母,就在 R W Q O J M V A H B S G Z X N T C I E K U P D Y F L 这个数组里查它的index作为优先级。就像英文版答案里用的java的index_of函数一样。
       但是这种方法感觉太慢了。
思路二:
       如果可以维护一个查index的容器,可以直接用字母查优先级,那最好了。STL的map不错,不过这样要建立字母和优先级的一一对应关系,然后还要依靠map的查找效率。
思路三:
       考虑到char是整型,A到Z是连续+1的,这样字母本来就有一个数字对应,再做一个数组,直接用字母的数值带进去就可以得到优先级。这样应该是比较快的了。
但是还有一个问题就是怎么算字母的优先级。总不能一个一个人工数,A在原数组的第几个就是几。。。
          (1)一个方法是类似方法一,不过这次只用做一遍,以后直接用数组查就可以了。
          (2)另一个方法是,做一个新的struct,包含value和orgin_index,按value排一次,然后输出orgin_index就得到value最小的那个元素在原数组里的index了,可以作为优先级使用。
          (3)用struct还是嫌麻烦,可以考虑用一个辅助数组,从0到n排列好。代表着原数组里各元素的初始位置。然后按 orgin_order[ i ]将辅助数组排序一次
即伪代码 sort ( support_array, pred );  bool pred (int lhs, int rhs) { return orgin_order[lhs]<orgin_order[rhs]};
,这样就直接得到了对应value从小到大排列的原数组index数组,可以直接作为优先级数组使用。本题里用  char c; c-'A'即为index,带入这个辅助数组就得到优先级


注:后面的代码不是排首字母,每一位都比
~~~~~代码后补


习题2.5.17
检测稳定性
     ”扩展练习2.1.16中的check()方法,对指定数组调用sort(),如果排序结果是稳定的则返回true,否则返回false。不要假设sort()只会使用exch()移动数据。“

思路一:sort之后,相等的元素应该满足原来的顺序。又转换成2.5.16的问题了。我们希望获得一个用来判断现有的几个元素是否符合原来顺序的东西。由于原来可能是排序过的,也可能是乱序的,即使是有序的也不一定知道如何排序的,所以不能期望依靠原来的排序算法来判断现有数组。应该是将每一个元素在原数组的位置做成一个优先级数组,类似上面思路三的东西。不过这次的元素不一定是char,没有天然可用的对应数字。所以思路三的第(3)似乎不能用了。但是仔细一想,没有必要纠结于用一个元素去查它的优先级,本题只要求我们判断排序是否稳定,我们只需要这样子:
         用类似上题思路三的(3),做一个辅助数组,和原数组一样长,从0到n赋值好。然后按新的排序算法,将辅助数组排序,pred用orgin_array[ value_of_support_array ],
即伪代码 sort ( support_array, pred );  bool pred (int lhs, int rhs) { return orgin_array[lhs]<orgin_array[rhs]};
这样辅助数组就变成了按新的sort方法排序后,原数组的各元素的index数组,最前面的是按新sort方法,最小的值在原数组的index。
这时候我们检查这个辅助数组,一个一个检查,按新sort方法相等的元素,应该是从小到大排列的,因为它们的值代表着在原数组的顺序。


注:思路一有个小问题,即每次sort的结果可能不一样,无法根据已有的排序过的数组判断。所以我们这里没有对真正的数组sort,如果需要的话,可以根据辅助数组copy一下。
注:又发现一个小问题,即如果不知道新sort方法的实现,如何判断排序后两元素优先级是否相等。如果仅对std::sort的话,因为std:sort是需要传入pred函数的,默认是用std::less。所以可以用operator=  或者(!std::less(a,b))&&(! std::less(b,a))。建议用后一种,因为如果用了修改了pred的std::sort或者重载了operator<的类型,operator=可能没有对应的重载或者和operator<有冲突,用 ( ! pred ( a , b ) ) && ( ! pred ( b , a ) )才符合真正顺序
注:写出来代码,发现没什么卵用啊(见后面附的代码),这个函数只能用std::sort,传pred。然而std::sort一般情况是quicksort,可以预测几乎都是unstable的。理想情况下应该可以用各种用户自定义的sort,并且这些sort应该传入pred。但是比如博主自己练习的时候敲了一堆排序方法,接口和std::sort不一样,这样就很尴尬了。。。想起来有一个小技巧,就是把有关排序的部分抽离出来单独做一个虚函数,然后后续用户可以继承并覆盖这个函数,其他部分不能改。但是本题这个似乎不太好抽离。。。待博主有空改一改。


~~~~~~代码后补

习题2.5.18
强制稳定
           ”编写一段能够将任意排序变得稳定的封装代码,创建一种新的数据类型作为键,将键的原始索引保存在其中,并在调用sort()之后再恢复原始的键。“


和17一个思路,不过17是按原序比较一遍,18是按原序将新的优先级相同的再sort一遍。


~~~~~~~~~~代码后补

注:
实践发现18和17还是有差别的。因为17只需要判断是否是stable sort,所以我们取了个巧,直接sort了一次辅助数组。
在18里,要求sort真正要sort的数组,然后要求sort后是stable的,这样就无法只sort辅助数组了。并且std::sort主要基于quicksort的,里面有随机性,无法sort一次辅助数组再sort一次真正数组,然后根据辅助数组处理。
所以18采用 16题的思路三(2)

注:
代码敲出来发现了和17题一样的问题。这个函数只能传入pred,并用std::sort排序。而题目要求是一个对任意排序方法强制stable的封装,应该可以传入sort函数和pred。待作者有空改一改。

注:
发现并不是不能用类似17的思路,只sort辅助数组。可以将辅助数组sort后,根据辅助数组,再另copy出一个sort后的数组。不过这样要额外的空间。


习题2.5.19
Kendall tau距离
          ”编写一段程序KendallTau.java,在线性对数时间内计算两组排列之间的Kendall tau距离。“

思路一:
如果是一个标准数组和一个乱序的比,那就是逆序对的数量。直接利用insert sort排序同时更新逆序对就行
可是现在是两个不一定标准的。
根据16,17,18的思路
可以把其中一个作为“标准”,对另一个insert sort

注:
看了答案后发现我真是太蠢了。
结合了英文版网页答案后的思路:
从元素找index,这是反函数啊。可以直接用数组弄出来。。将思路一中蠢的部分改一改
不过这个有个小问题就是最好两个数组的元素都是从0到n全占满。
如果不占满的话,这种简便的反函数就很浪费空间
如果改用哈希的话,比如map,就又成了原来的map思路了。
暂且再写一个map版,和V1看看哪个快

注:
看百度上有用merge sort实现求逆序对的
英文版答案有不sort,直接求逆序对的
可以从这里改进
这里作者偷懒了,用改编版的insert sort。

注:
本来以为V1折腾了那么多,可能会非常慢。map看起来很简洁,似乎哈希也很快。
作者实现两个版本后,测试了一下,尽管第一个版本看起来很繁琐,可是是确确实实的线性对数复杂度的,第二个版本用到了map,博主还没有学到这个部分,不好分析。不过结果是在size 100下,V2时间一般是V1 三到四倍, size 1000 V2时间是V1 十倍左右。但是这两个版本都做了不同实现方式的防御性设计,可能导致有一些偏差。并且map版理论上可以用额外的空间做成类似英文版答案那种的静态数组的反函数,这样速度会有一个大的提升。

~~~~~~~代码后补


exercise2.5.16.h
#pragma once#include <array>//R W Q O J M V A H B S G Z X N T C I E K U P D Y F L//也可以自定义顺序std::array<int, 26> help_get_index_list(std::string order = " R W Q O J M V A H B S G Z X N T C I E K U P D Y F L ");std::array<int, 26> help_get_index_list_V2(std::string order = " R W Q O J M V A H B S G Z X N T C I E K U P D Y F L ");class pred233 {public:bool operator()(const std::string& lhs, const std::string& rhs);};
cpp
#include "exercise2.5.16.h"#include <string>#include <sstream>#include <algorithm>#include <iostream>std::array<int,26> help_get_index_list(std::string order){struct order_helper_unit {char c;int origin_index;};//给定的顺序std::istringstream iss(order);order_helper_unit units[26];for (int i = 0; i < 26; ++i) {char c;iss >> c;order_helper_unit u;u.c = c;u.origin_index = i;units[i] = u;}//按字母排序std::sort(units, units + 26, [&](order_helper_unit lhs, order_helper_unit rhs)->bool {return lhs.c < rhs.c;});// order_list[0]也就是A的优先级,一般小的会排前面吧std::array<int, 26> order_list;for (int i = 0; i < 26; ++i) {order_list[i] = units[i].origin_index;}return order_list;}std::array<int, 26> help_get_index_list_V2(std::string order){//紧凑的26的字母顺序std::string orgin_order;std::istringstream iss(order);for (int i = 0; i < 26; ++i) {char c;iss >> c;std::cout << c;orgin_order += c;}std::array<int, 26> ret;//0到25for (int i = 0; i < 26; ++i) {ret[i] = i;}//按order中的顺序排std::sort(ret.begin(), ret.end(), [&](int lhs,int rhs) {return orgin_order.at(lhs) < orgin_order.at(rhs);});return ret;}bool pred233::operator()(const std::string & lhs, const std::string & rhs){//所有位都用这个顺序比较static auto order = help_get_index_list_V2();if (lhs == rhs)return false;else {//不等,一位一位比for (int i = 0; i < std::min(lhs.size(), rhs.size()); ++i) {if ((lhs[i]<'A' || lhs[i]>'Z') || (rhs[i]<'A' || rhs[i]>'Z')) {//如果有不是大写字母的//用默认序if (lhs[i] > rhs[i]) {return false;}else if (lhs[i] < rhs[i]) {return true;}//如果连这个也想等,那就判断下一位}else{if (order[lhs[i] - 'A'] > order[rhs[i] - 'A']) {return false;}else if (order[lhs[i] - 'A'] < order[rhs[i] - 'A']) {return true;}}//如果相等,判断下一位}//如果比完了,都相同,那么短的小return (lhs.size() < rhs.size()) ? true : false;}}

2.5.17
#pragma once#include <algorithm>#include <stdexcept>#include <iostream>//假设调用std::sorttemplate<class STL_Array,class _Pred = std::less<typename STL_Array::value_type >>bool exercise_2_5_17(STL_Array & arr, _Pred pred=_Pred());template<class STL_Array, class _Pred>inline bool exercise_2_5_17(STL_Array & arr, _Pred pred){//辅助数组STL_Array sup;for (int i = 0; i < sup.size(); ++i) {sup[i] = i;}//将辅助数组排序std::sort(sup.begin(), sup.end(), [&](int lhs, int rhs)->bool {return pred(arr[lhs], arr[rhs]);});//输出一下for (int i = 0; i < sup.size(); ++i) {std::cout << "sup["<<i<<"]\t"<<sup[i]<<"\t:"<<arr[sup[i]] << std::endl;}//看排序后的辅助数组,优先级相等的元素是否有逆序for (int i = 0; i < sup.size() - 1; ++i) {//优先级相等,根据std::sort的实现,为 (!(a<b))&&(!(b<a)) if ((!pred(arr[sup[i]], arr[sup[i + 1]])) && (!pred(arr[sup[i + 1]], arr[sup[i]]))) {if (sup[i] > sup[i + 1]) {return false;}else if (sup[i] == sup[i + 1]) {//。。。正常不会出现的,说明这个sort居然居然居然,居然以为优先级相等的元素就真的相等了!!!!就可以随意copy了!!!//!太天真了!!!!//!!naive!!!!//误我大事!!!!//竖子不足与谋!!!!!!!!!!!!!//所以就不要return false了//抛个异常好了//本来是测稳定性的,强行测了一波这个//注!:这里的测验并不完全,请自行在is_sorted方法里实现正确的std::cout <<"WARNING: "<< "sup[" << i << "] = sup[" << i + 1 << "]" << std::endl;throw std::logic_error("同志,换个sort吧,这个有毛病,优先级相同的它就直接copy几个出来,太不负责了!");}}}return true;}



2.5.18
#pragma once#include <array>#include <algorithm>//和2.5.17一个思路,17是判断一下顺序,这个是直接再按原顺序sort一遍//不过这里不能只排辅助数组,所以做一个struct包含index和valuetemplate<class Element, size_t size, class _Pred = std::less<Element>>void my_stable_sort(std::array<Element, size>& arr, _Pred pred = _Pred());template<class Element, size_t size, class _Pred>inline void my_stable_sort(std::array<Element, size>& arr, _Pred pred){struct unit{Element value;int index;};std::array<unit, size> sup;for (int i = 0; i < size; ++i) {sup[i] = { arr[i],i };}std::sort(sup.begin(), sup.end(), [&](unit lhs, unit rhs)->bool {return pred(lhs.value, rhs.value);});//按value排序完毕//将同优先级的再按index排一遍for (int i = 0; i < size - 1;) {//i和i+1优先级相同//if ((!(pred(sup[i].value, sup[i + 1].value))) && (!(pred(sup[i + 1].value), sup[i].value))) {int beg = i;int end = i + 1;//尾后indexElement cmp = sup[i].value;//找相同优先级的范围while (end < size && ((!pred(cmp, sup[end].value))) && (!pred(sup[end].value, cmp))) {++end;}//从beg到end按index 排一遍if (end > beg + 1) {std::sort(sup.begin() + beg, sup.begin() + end, [&](unit lhs, unit rhs)->bool {return lhs.index < rhs.index;});}//直接将i赋值为正确的位置i = end;}//这样sup就是stable sort之后的数组了//输出一下for (int i = 0; i < sup.size(); ++i) {std::cout << "sup[" << i << "]\tindex:\t"<<sup[i].index << "\tvalue:\t" << sup[i].value << std::endl;}//这里我将value赋值回arrfor (int i = 0; i < size; ++i) {arr[i] = sup[i].value;}}


2.5.19
#pragma once#include <array>#include <algorithm>#include <iostream>#include <unordered_map>//思路一://如果是一个标准数组和一个乱序的比,那就是逆序对的数量。直接insert sort一次,在排序同时更新逆序对就行//可是现在是两个不一定标准的。//根据16,17,18的思路//可以把其中一个作为“标准”,对另一个insert sort//注://看了答案后发现我真是太蠢了。//结合了英文版网页答案后的思路://从元素找index,这是反函数啊。可以直接用数组弄出来。。将思路一中蠢的部分改一改//不过这个有个小问题就是最好两个数组的元素都是从0到n全占满。//如果不占满的话,这种简便的反函数就很浪费空间//如果改用哈希的话,比如map,就又成了原来的map思路了。//暂且写一个map版,和V1看看哪个快//注://看百度上有用merge sort实现求逆序对的//英文版答案有不sort,直接求逆序对的//可以从这里改进//先重新写一个可以传入pred的insert sort复习一下好了template<class Element,size_t size,class _Pred=std::less<Element>>int insert_sort_calculate_inversion(std::array<Element, size>& arr, _Pred pred = _Pred());template<class Element, size_t size>int exercise_2_5_19(std::array<Element, size>& lhs_arr, std::array<Element, size>& rhs_arr);template<class Element, size_t size>int exercise_2_5_19_V2(std::array<Element, size>& lhs_arr, std::array<Element, size>& rhs_arr);//思路一实现template<class Element, size_t size, class _Pred>inline int insert_sort_calculate_inversion(std::array<Element, size>& arr, _Pred pred){int inversion = 0;//逆序对数量//每个位置都要向前insert一次,第一个直接算作已经insert过for (int now = 1; now < size; ++now) {Element now_value = arr[now];int before = now - 1;while (before >= 0 && !pred(arr[before], now_value)) {//swap(arr[before],arr[now];arr[before + 1] = arr[before];--before;++inversion;}arr[before + 1] = now_value;}//排序完毕//输出逆序对return inversion;}template<class Element, size_t size>inline int exercise_2_5_19(std::array<Element, size>& lhs_arr, std::array<Element, size>& rhs_arr){//防御性设计//需要两个数组有同样的元素,仅仅顺序不同,还不能有重复元素?//因为和后面有很多动作可以合并,所以防御性设计融合到后面//检测完毕后,应该是两个只有顺序不同的数组,没有重复元素//对rhs排序,以lhs的顺序//伪代码/*int ret = insert_sort_calculate_inversion(rhs_arr, [&](const Element& lhs, const Element& rhs) {return index_of(lhs, lhs_arr) < index_of(rhs, lhs_arr);});*///这里有个小问题,index_of太麻烦,利用16,17,18题的思路,应该可以有一一对应的数组以供查优先级//这里有些麻烦,无法找到两个数组之间相同元素的沟通桥梁以作为查询数组的index。//一个思路是,因为这两个数组有相同的元素,只是顺序不同,所以都sort一下,从小到大就是每个元素的index//同时sort的同时就可以获得其原顺序的辅助数组,这样两个数组就可以联通起来了//另一个思路是V2的利用map,根据元素查index//将防御性检测融入到两个sup中,所以lhs的sup以前只是index数组,现在变成结构体unit的数组//lhs的sup,value从小到大,每个位置对应了lhs_arr中的indexstruct unit_for_lhs{Element value;int orgin_index;};//initstd::array<unit_for_lhs, size> sup_lhs;for (int i = 0; i < size; ++i) {sup_lhs[i] = { lhs_arr[i],i };}//sort by valuestd::sort(sup_lhs.begin(), sup_lhs.end(), [&](unit_for_lhs lhs, unit_for_lhs rhs)->bool {return lhs.value < rhs.value;});//rhs的supstruct unit_for_rhs{Element value;int orgin_index;int after_value_sort_index;};//initstd::array<unit_for_rhs, size> sup_rhs;for (int i = 0; i < size; ++i) {sup_rhs[i] = { rhs_arr[i],i,-1 };}//sort by valuestd::sort(sup_rhs.begin(), sup_rhs.end(), [&](unit_for_rhs lhs, unit_for_rhs rhs)->bool {return lhs.value < rhs.value;});//for (int i = 0; i < size; ++i) {sup_rhs[i].after_value_sort_index = i;}//防御性设计,两个sup现在都是按value排列,他们应该对应value相同,并且不能有重复元素for (int i = 0; i < size - 1; ++i) {if (sup_lhs[i].value != sup_rhs[i].value) {std::cout << "两数组元素不同";return -1;}if (sup_lhs[i].value == sup_lhs[i + 1].value) {std::cout << "有重复元素";return -1;}}//还差最后一个的比较if (sup_lhs[size - 1].value != sup_rhs[size - 1].value) {std::cout << "两数组元素不同";return -1;}//防御性判断完毕,两数组元素相同,无重复//恢复rhs的原序,但现在已经有足够的信息std::sort(sup_rhs.begin(), sup_rhs.end(), [&](unit_for_rhs lhs, unit_for_rhs rhs)->bool {return lhs.orgin_index < rhs.orgin_index;});//rhs的sup,每个unit保留了value,orgin_index,after_value_sort_index//对rhs的sup进行insert sort来计算inversion//,每个位置的元素拿自己的after_value_sort_index,用这个index在lhs_sup中找到它在lhs_arr的index,作为优先级int inversion = insert_sort_calculate_inversion(sup_rhs, [&](unit_for_rhs lhs, unit_for_rhs rhs)->bool {return sup_lhs[lhs.after_value_sort_index].orgin_index < sup_lhs[rhs.after_value_sort_index].orgin_index;});//排序完毕//在最后一次排序的过程中,统计出inversionreturn inversion;}//map版实现template<class Element, size_t size>inline int exercise_2_5_19_V2(std::array<Element, size>& lhs_arr, std::array<Element, size>& rhs_arr){//差一个防御性设计//放到后面操作中一起//构造lhs的反函数,也就是从元素找index//用unordered_mapstd::unordered_map<Element, int> lhs_inv;for (int i = 0; i < size; ++i) {lhs_inv.emplace(lhs_arr[i], i);}//直接通过map去sort rhs//防御性设计,如果rhs有lhs没有的元素,那么应该at不到,会throwint ret = -1;try{ret=insert_sort_calculate_inversion(rhs_arr, [&](const Element& lhs, const Element& rhs) {return lhs_inv.at(lhs)<lhs_inv.at(rhs);});}catch (const std::out_of_range&){std::cout << "元素不同";return -1;}//如果rhs缺lhs的一部分元素,并且没有多出来lhs没有的,那么说明rhs有在lhs中包含的重复元素,sort后会有重复indexfor (int i = 0; i < size - 1; ++i) {if (rhs_arr[i] == rhs_arr[i + 1]) {std::cout << "有重复元素";return -1;}}//如果lhs有重复元素,会包含到rhs相应的情况中return ret;}



0 0
原创粉丝点击