-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 227 KB
/
content.json
1
{"meta":{"title":"Yongbosmart","subtitle":" Follow excellence ,and success will chase you","description":"Follow excellence ,and success will chase U","author":"yongbosmart","url":"https://yongbosmart.github.io"},"pages":[{"title":"","date":"2018-05-06T01:42:31.884Z","updated":"2018-05-06T01:42:18.601Z","comments":true,"path":"baidu_verify_GyMbpHRSjz.html","permalink":"https://yongbosmart.github.io/baidu_verify_GyMbpHRSjz.html","excerpt":"","text":"GyMbpHRSjz"},{"title":"算法作业-有向图(2)","date":"2018-03-27T16:00:00.000Z","updated":"2018-05-05T08:22:27.438Z","comments":true,"path":"算法作业-有向图(2).html","permalink":"https://yongbosmart.github.io/算法作业-有向图(2).html","excerpt":"","text":"上次回顾:找图中的最短路径求得图中的强连通通路"},{"title":"About","date":"2018-04-27T03:23:29.000Z","updated":"2018-05-25T23:22:15.343Z","comments":true,"path":"about/index.html","permalink":"https://yongbosmart.github.io/about/index.html","excerpt":"","text":"关于我教育经历山东大学 2015年9月 - 2019年6月计算机科学与技术专业 本科 相关课程:数据结构,操作系统,算法设计与分析,机器学习,信息检索,计算机图形学,信息安全导论 技能 Java,C++,Bootstrap(熟悉) Java Web,Python, Tensorflow(努力精进中……) 水水的项目 团队项目 阅读理解系统 MyMajor交友App iBook读书交友软件 个人项目 宾馆客房管理系统 Web-个人博客 各种Java小游戏…… 现在的座右铭~Follow excellence, and success will follow you. Contact me邮箱:yongbosmart@gmail.com GitHub:https://github.com/yongbosmart"},{"title":"categories","date":"2018-04-30T12:40:46.000Z","updated":"2018-04-30T13:37:04.076Z","comments":false,"path":"categories/index.html","permalink":"https://yongbosmart.github.io/categories/index.html","excerpt":"","text":""},{"title":"tags","date":"2018-04-30T12:40:12.000Z","updated":"2018-04-30T13:35:27.751Z","comments":false,"path":"tags/index.html","permalink":"https://yongbosmart.github.io/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"dfs以及搜索问题","slug":"dfs及搜索问题","date":"2020-02-15T16:00:00.000Z","updated":"2020-02-16T09:52:32.439Z","comments":true,"path":"2020/02/16/dfs及搜索问题/","link":"","permalink":"https://yongbosmart.github.io/2020/02/16/dfs及搜索问题/","excerpt":"","text":"dfs以及搜索问题DFS是搜索的手段之一。它从某个状态开始,不断地转移状态直到无法转移,然后回退到前一步的状态,继续转移到其他状态。直到找到最终的解。 全排列123456789101112131415161718给定一个没有重复数字的序列,返回其所有可能的全排列。示例:输入: [1,2,3]输出:[ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/permutations著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 题解: C++可以用库 1234567891011121314class Solution {public: vector<vector<int>> permute(vector<int>& nums) { vector<vector<int>> res; sort(nums.begin(),nums.end());//先排序 do { res.push_back(nums); }while(next_permutation(nums.begin(),nums.end())); return res; }}; 另一种全排列的方法,回溯的思想 思路来源于网友们。 12345678910111213141516171819202122class Solution {public: void backsee(int n,vector<vector<int>>& res,vector<int>& nums,int index){ if(index==n) res.push_back(nums); for(int i=index;i<n;i++){//可以想象第一个数有n种选择,第二个数位置就是n-1种了 swap(nums[i],nums[index]); backsee(n,res,nums,index+1);//问题缩小到第二个数 swap(nums[i],nums[index]); } } vector<vector<int>> permute(vector<int>& nums) { //类似搜索树 vector<vector<int>> res; backsee(nums.size(),res,nums,0); return res; }}; 带剪枝的全排列123456789101112131415给定一个可包含重复数字的序列,返回所有不重复的全排列。示例:输入: [1,1,2]输出:[ [1,1,2], [1,2,1], [2,1,1]]来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/permutations-ii著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 题解: 1、使用了STL 的count函数来剪枝,然后时间复杂度惊人。 执行用时 :2280 ms, 在所有 C++ 提交中击败了5.03%的用户 内存消耗 :10 MB, 在所有 C++ 提交中击败了65.48%的用户 12345678910111213141516171819202122class Solution {public: void backsee(int n,vector<vector<int>>& res,vector<int>& nums,int index){ //参数:总长度n,结果res,中间结果nums,index if(index==n&&count(res.begin(),res.end(),nums)==0) res.push_back(nums); for(int i=index;i<n;i++){//可以想象第一个数有n种选择,第二个数位置就是n-1种了 swap(nums[i],nums[index]); backsee(n,res,nums,index+1);//问题缩小到第二个数 swap(nums[i],nums[index]); } } vector<vector<int>> permuteUnique(vector<int>& nums) { //题目思路,看题解,可用set<vector>剔除重复的 vector<vector<int>> res; backsee(nums.size(),res,nums,0); return res; }}; 2、使用C++自带全排列 1234567891011121314151617可以使用next_permutation,前提是先从小到大排序,该函数输出的就是无重复的全排列,运行时间24ms,击败98.87%。vector<vector<int>> permuteUnique(vector<int>& nums){ sort(nums.begin(), nums.end()); vector<vector<int>>result; result.push_back(nums); while (next_permutation(nums.begin(), nums.end())) { result.push_back(nums); } return result;}去重,考虑重复的定义,其实就是同一位选进去了多个相同的数,换句话说就是若要不重复,同一位对同样的数只能使用一个,因此我们可以在每次DFS之前,也就是为本位置选数之前,判断是否已经使用过相同的数了,若没有则正常DFS,若有则跳过本次循环,这样不仅去掉了重复,而且减少了递归次数。作者:luo-ben-zhu-xiao-man-tou链接:https://leetcode-cn.com/problems/permutations-ii/solution/dfsqu-zhong-huo-zhe-shi-yong-czi-dai-quan-pai-lie-/来源:力扣(LeetCode)著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 3、参考了评论区大佬 用map进行改进 执行用时 :8 ms, 在所有 C++ 提交中击败了95.62%的用户 内存消耗 :11.1 MB, 在所有 C++ 提交中击败了25.72%的用户 12345678910111213141516171819202122232425262728293031class Solution {public: void backsee(int n,vector<vector<int>>& res,vector<int>& nums,int index,int used[]){ //参数:总长度n,结果res,中间结果nums,index map<int,int> ex; if(index==n) res.push_back(nums); for(int i=index;i<n;i++){//可以想象第一个数有n种选择,第二个数位置就是n-1种了 // cout<<i<<endl; if(ex.count(nums[i])==1){//这个位置某数已经待过了,所以不考虑了 continue; } swap(nums[i],nums[index]); backsee(n,res,nums,index+1,used);//问题缩小到第二个数 swap(nums[i],nums[index]); ex[nums[i]]=1; } } vector<vector<int>> permuteUnique(vector<int>& nums) { vector<vector<int>> res; int used[nums.size()]; memset(used,0,sizeof(used)); sort(nums.begin(),nums.end()); backsee(nums.size(),res,nums,0,used); return res; }}; 子集题目12345678910111213141516171819202122给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。示例:输入: nums = [1,2,3]输出:[ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], []]来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/subsets著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 题解这个问题类似于下面的目标和、部分和问题。 执行用时 :0 ms, 在所有 C++ 提交中击败了100.00%的用户 内存消耗 :13.1 MB, 在所有 C++ 提交中击败了7.49%的用户 1234567891011121314151617181920212223242526272829class Solution {public: void sousuo(vector<vector<int>>& res,vector<int>& nums,int index){ if(index==nums.size()){ res.push_back(nums); return; } int tmp=nums[index]; //该元素不在 nums.erase(nums.begin()+index); sousuo(res,nums,index); //该元素在 nums.insert(nums.begin()+index,tmp); sousuo(res,nums,index+1); } vector<vector<int>> subsets(vector<int>& nums) { //想了一下方法 dfs 动态规划 vector<vector<int>> res; // res.push_back(nums); sousuo(res,nums,0); return res; }}; 部分和&&目标和问题部分和问题这一段来自《挑战程序设计语言(第二版)》 给定整数a1、a2、…….an,判断是否可以从中选出若干数,使它们的和恰好为K。 .png) 样例输入: 123n=4a={1,2,4,7}k=13 样例输出 1Yes {13=2+4+7} 样例输入: 123n=4a={1,2,4,7}k=15 样例输出 1No 题解思路:从$a_1$开始按顺序决定每个数加或者不加,在全部n个数后判断它们的和是不是k即可。 状态数是$2^{n+1}$所以复杂度是O($2^n$) 12345678910111213141516171819int a[MAX_N];int n,k;//已经从前i项得到了和sum,然后对于i项之后进行分支。bool dfs(int i,int sum){ //如果前n项都计算过了,则返回sum是否与k相等 if(i==n) return sum==k; //不加上a[i]的情况 if(dfs(i+1,sum)) return ture; //加上a[i]的情况 if(dfs(i+1,sum+a[i])) return true; return false;}void solve(){ if (dfs(0,0)) printf(\"Yes\\n\"); else printf(\"No\\n\");} 目标和问题[动态规划等解法待补充]类似问题 123456789101112131415161718192021给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。返回可以使最终数组和为目标数 S 的所有添加符号的方法数。示例 1:输入: nums: [1, 1, 1, 1, 1], S: 3输出: 5解释: -1+1+1+1+1 = 3+1-1+1+1+1 = 3+1+1-1+1+1 = 3+1+1+1-1+1 = 3+1+1+1+1-1 = 3一共有5种方法让最终目标和为3。来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/target-sum著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 这是一种类似暴力的解法 执行用时 :1776 ms, 在所有 C++ 提交中击败了8.70%的用户 内存消耗 :8.8 MB, 在所有 C++ 提交中击败了29.83%的用户 12345678910111213141516171819202122class Solution {public: int jieguo(vector<int>& nums, int S,int now,int index,int count){ if(index==nums.size()){ if(now==S) return count+=1; else return count; } int res=0; res=jieguo(nums,S,now+nums[index],index+1,count); res=jieguo(nums,S,now-nums[index],index+1,res); return res; } int findTargetSumWays(vector<int>& nums, int S) { //用dfs方法 int res=jieguo(nums,S,0,0,0); return res; }};","categories":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/categories/日常练习/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/tags/算法/"},{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"spark练习总结","slug":"spark学习代码","date":"2020-02-15T16:00:00.000Z","updated":"2020-02-16T09:54:41.051Z","comments":true,"path":"2020/02/16/spark学习代码/","link":"","permalink":"https://yongbosmart.github.io/2020/02/16/spark学习代码/","excerpt":"","text":"spark练习总结嵌套json处理json形如{“ope”:12,”user”:[{“apple”:13,”pear”:{“color”:”yellow”}},{“apple”:15,”pear”:{“color”:”red”}}]} 这种一层嵌套一层。 在网上搜到两种处理方法。 1.得到map/list结构 List(Map(name->”sdf”,dict->List(…)) 123456789101112 //repp为json字符串 def regJson(json: Option[Any]) = json match { case Some(map: Map[String, Any]) => map // case other => \"Unknow data structure : \" + other } val jsonS = JSON.parseFull(repp) val first = regJson(jsonS)val rddData = sc.parallelize(first.values.toList.head.asInstanceOf[List[Object]])//转化成RDD import org.json4s._ import org.json4s.jackson.JsonMethods._val z = rddData.map(x => compact(render(Extraction.decompose(x)(DefaultFormats)))).collect()//得到RDD转回的json格式 2.利用sparksql 遇到array用explode散开,遇到dict当做父子关系。比如pear.color,再比如explode(user) {“ope”:12,”user”:[{“apple”:13,”pear”:{“color”:”yellow”}},{“apple”:15,”pear”:{“color”:”red”}}]} 1234567891011 val conf = new SparkConf().setMaster(\"local\").setAppName(\"JSONApp\"); //通过conf来创建sparkcontext val sc = new SparkContext(conf); val ss = SparkSession.builder().config(conf).getOrCreate() ss.read.json(\"tmp.json\").createOrReplaceTempView(\"users\") //得到复杂的json文件 val df1 = ss.sql(\"select explode(user) from users\") df1.printSchema() df1.show() val dft = df1.select(df1(\"col.id\"), df1(\"col.info\"), df1(\"col.hits\")).toDF(\"id\", \"info\", \"hits\")dft.toJSON.collectAsList().toString()//得到dataframe的json形式 dataframe常用操作遍历123456789val ids = dft.select(dft(\"id\")).collect() //先求collectval len = ids.length//再求长度var q = 0var opess = dft.select(dft(\"id\"))while (q < len) { val table = dft.where(\"id=%s\".format(ids(q)(0))) println(ids(q)(0))//ids(q)是一个row q+=1} 但是求collect需要慎重,因为很可能dataframe数值很多很大,引起问题。 json构造dataframe(一个有趣的知识点)https://blog.csdn.net/liangrui1988/article/details/97665409 需要构造好scheme 123456789101112//压平list还要构造结构(因为未来还要还原结构)val arrayStruct = ArrayType(StructType(Seq( StructField(\"date\", StringType, true), StructField(\"name\", StringType, true), StructField(\"rid\", StringType, true), StructField(\"value\", LongType, true), StructField(\"days\", StringType, true))), true)val ashema = StructType(List(StructField(\"oid\", StringType, true), StructField(\"recs\", arrayStruct, true)))val bshema = StructType(List(StructField(\"oid\", StringType, true), StructField(\"reps\", arrayStruct, true)))val schema = StructType(List(StructField(\"recs\", StringType, true)))val schema2 = StructType(List(StructField(\"reps\", StringType, true)))// true代表不为空 UDF可以使用自定义方法,在SQL语句和列。 写的比较好的博客: https://www.cnblogs.com/cc11001100/p/9463909.html https://www.jianshu.com/p/b1e9d5cc6193 特殊方法scala判断类型方法 getClass() scala强制转换方法.asInstanceOf[List[Object]] (注意是中括号)","categories":[{"name":"数据分析","slug":"数据分析","permalink":"https://yongbosmart.github.io/categories/数据分析/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"读书笔记","slug":"读书笔记","permalink":"https://yongbosmart.github.io/tags/读书笔记/"}]},{"title":"PCA和因子分析","slug":"因子分析","date":"2019-05-05T16:00:00.000Z","updated":"2020-02-16T09:57:15.416Z","comments":true,"path":"2019/05/06/因子分析/","link":"","permalink":"https://yongbosmart.github.io/2019/05/06/因子分析/","excerpt":"","text":"PCA和因子分析https://zhuanlan.zhihu.com/p/21580949 PCA :高维数据——》降成低维数据(低维数据保存了高维数据大部分信息,甚至可以还原大部分高维数据) (降维可以理解成减少相关量,最后得到线性无关量) 降维投影,希望投影后投影值尽可能分散,而这种分散程度,可以用数学上的方差来表述,即寻找基,使方差值最大。 降维问题的优化目标:将一组N维向量降为K维(K大于0,小于N),其目标是选择K个单位(模为1)正交基,使得原始数据变换到这组基上后,各字段两两间协方差为0,而字段的方差则尽可能大(在正交的约束下,取最大的K个方差)。 因子分析:高维数据——》与假定的几个因子有关系。 因子分析常见名词解释来源:https://support.minitab.com/zh-cn/minitab/18/help-and-how-to/modeling-statistics/multivariate/how-to/factor-analysis/interpret-the-results/all-statistics-and-graphs/ 因子载荷因子载荷表示因子对变量的解释程度。载荷范围可以为 -1 到 1。 检查载荷模式,以确定对每个变量影响最强的因子。接近于 -1 或 1 的载荷表明因子对变量的影响非常强。接近于 0 的载荷表明因子对变量的影响很弱。有些变量可能对多个因子施加高载荷。 非旋转因子载荷通常很难解释。因子旋转简化了载荷结构,并且使因子更容易辨识和解释。然而,没有哪种旋转方法在所有情况下都表现最佳。您可能想要尝试不同的旋转,并使用产生最佳解释结果的旋转。您还可以对旋转载荷排序,从而更为清楚地评估因子中的载荷。 适合公司 (0.778)、适合工作 (0.844) 和潜能 (0.645) 在因子 1 上具有较大的正载荷,因此该因子描述员工适合度以及在公司的成长潜力。 相貌 (0.73)、受欢迎度 (0.615) 和自信心 (0.743) 在因子 2 上具有较大的正载荷,因此该因子描述个人才能。 沟通能力 (0.802) 和组织能力 (0.889) 在因子 3 上具有较大的正载荷,因此该因子描述工作技能。 信件 (0.947) 和简历 (0.789) 在因子 4 上具有较大的正载荷,因此该因子描述写作技能。 公因子方差(共同度)公因子方差是由因子解释的每个变量的变异性比率。无论您为分析使用非旋转因子载荷还是旋转因子载荷,公因子方差值都是相同的。在这些结果中,从 12 个变量中提取了 4 个因子。所有变量的公因子方差值通常比较高,这说明 4 个因子充分表示变量。例如,适合工作中 0.895 或 89.5% 的变异性都由这 4 个因子来解释。 解释 检查公因子方差值以评估因子对每个变量的解释程度。公因子方差越接近于 1,因子对变量的解释程度越好。如果因子对某些变量的拟合有重大影响,则可以决定添加因子。 方差(特征值)碎石图的依据 由每个因子解释的数据变异性。如果使用主分量提取方法且不旋转载荷,则每个因子的方差等于特征值。虽然所有因子解释的总变异保持不变,但旋转会改变每个因子解释的变异比率的分布。 解释 检查每个因子的方差。方差越高,表明因子解释的数据变异性所占比率也越高。如果不知道要在分析中提取多少因子,请先使用主分量提取方法且不旋转,将默认因子数(提取的最大因子数)作为初步评估。然后,将重要因子定义为方差(特征值)大于特定值的因子。例如,一项标准是包括特征值至少为 1 的任何因子。另一种方法是直观地评估碎石图上的特征值,以确定特征值在哪个点显示的变化不大并且接近 0。有关更多信息,请参见有关碎石图的主题。 方差贡献率方差贡献率 (% Var) 指的是由每个因子解释的数据变异性的比率。方差贡献率的值范围可以为 0 (0%) 到 1 (100%)。 解释 检查每个因子的方差贡献率值。方差贡献率值越高表明因子解释的变异性越多。因此,您可以使用方差贡献率值确定哪些因子最重要。 方差贡献率的公因子方差值表示分析中所有因子解释的总变异。使用此值可帮助确定分析中使用的因子数是否解释数据中足够量的总变异。 代码实现1234567891011fa = FactorAnalyzer(n_factors=n_factors, rotation='varimax')fa.fit(dfu)l = pd.DataFrame(fa.loadings_)# 成分矩阵,旋转载荷# print(\"公因子方差:\\n\", fa.get_communalities()) # 公因子方差# print(\"\\n成分矩阵:\\n\", fa.loadings_) # 成分矩阵c = pd.DataFrame(fa.get_communalities())# 公因子方差# s = fa.get_scores(dfu)s = pd.DataFrame(fa.transform(dfu))# print(fa.transform(dfu))v=pd.DataFrame(fa.get_factor_variance())#给出贡献率,总方差 特征值 方差贡献率 方差累计贡献率 图形碎石图——特征值 载荷图等","categories":[{"name":"读书笔记","slug":"读书笔记","permalink":"https://yongbosmart.github.io/categories/读书笔记/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"读书笔记","slug":"读书笔记","permalink":"https://yongbosmart.github.io/tags/读书笔记/"}]},{"title":"算法自解","slug":"算法自解","date":"2019-05-05T16:00:00.000Z","updated":"2020-02-16T10:04:25.472Z","comments":true,"path":"2019/05/06/算法自解/","link":"","permalink":"https://yongbosmart.github.io/2019/05/06/算法自解/","excerpt":"","text":"算法自解最新版见:https://www.jianshu.com/p/252346e3497b (更新中……) 努力让自己熟练编程,关键代码,然后快速入门。 数据结构等各种知识点屡看屡忘的我,为以后整理的笔记…… 以《算法全解》《挑战程序设计竞赛 算法与数据结构》等为基础 常用的数据结构stack 12345入栈,如例:s.push(x);出栈,如例:s.pop();注意,出栈操作只是删除栈顶元素,并不返回该元素。访问栈顶,如例:s.top()判断栈空,如例:s.empty(),当栈空时,返回true。访问栈中的元素个数,如例:s.size()。 queue 123456入队,如例:q.push(x); 将x 接到队列的末端。出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。访问队首元素,如例:q.front(),即最早被压入队列的元素。访问队尾元素,如例:q.back(),即最后被压入队列的元素。判断队列空,如例:q.empty(),当队列空时,返回true。访问队列中的元素个数,如例:q.size() vector 12345vector< vector<int> > m;vector<int> q;q.push_back(3);m.push_back(q);m[0][0]; 数组 1C++不易获得数组长度,因此建议把数组长度传入方法里。 输入输出与具体方法pintf函数是每次输出结果; print,cout最好不要混用,print是即时刷新,等效cout<<flush;混用有时会表现顺序输出不同。 (%与/的应用)日期差值输入两个日期:YYYYMMDD格式,得到两个日期之间的差值。 对于一个整数,%100:得到整数后两位 /100得到整数除后两位的。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869// ConsoleApplication1.cpp: 定义控制台应用程序的入口点。//#include <stdio.h>#include <iostream>#include <cstring>#include <stdlib.h>using namespace std;bool issun(int year) { if ((year % 100 != 0 &&year % 4 == 0)||year%400==0) {//判断 闰年 的方法!! return true; } else{ return false; }}int main(){ int a1, a2; int year1, year2, mon1, mon2, day1, day2; while(scanf(\"%d%d\",&a1,&a2)!=EOF){ //没有强调,需要保证前者早于后者。 if (a1 > a2) { int tmp = a1; a1 = a2; a2 = tmp; } day1 = a1 % 100;//得到后两位 a1 = a1 / 100;//得到除后两位的前面的数 mon1 = a1 % 100; a1 = a1 / 100; year1 = a1 % 10000;//得到后四位 day2 = a2 % 100; a2 = a2 / 100; mon2 = a2 % 100; a2 = a2 / 100; year2 = a2 % 10000; int days = 1; int mon[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; int mons[13] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 }; while (!(year1 == year2&&mon1 == mon2&&day1 == day2)) { day1++; if (issun(year1)) { if (day1 == mons[mon1] + 1) { day1 = 1; mon1++; } } else { if (day1 == mon[mon1] + 1) { day1 = 1; mon1++; } } if (mon1 == 13) { mon1 = 1; year1++; } days++; } printf(\"%d\\n\", days); } return 0;} 进制转换P进制转十进制y $a_1a_2\\dots a_n\\to a_1P^{n-1}+\\dots+a_2P^{1}+a_1$ 123456int y=0,product=1;while(x!=0){ y=y+(x%10)*product;//y是结果,x%10为了得到个位数 x=x/10; product=prodcut*P;//product的值会分别变成1,p,p^2,p^3,p^4…} 十进制数y转换成Q进制数z 12345int z[40],num=0;do{ z[num++]=y%Q;//除基取余 得到最后一位数 y=y/Q;}while(y!=0); 这样z数组从高位z[num-1]到低位z[0]即为Q进制z,进制转换完成。 字符串对于回文串的处理:1.根据对称性;2.利用for循环,从后向前遍历(逻辑性比较简单) 思路1. 假设字符串str的下标从0开始,由于“回文串”是正读和反读都一样的字符串,因此只需要遍历字符串的前一半(注意:不需要取到i==len/2),如果出现字符str[i]不等于其对称位置str[len-1-i],就说明这个字符串不是“回文串”;如果前一半的所有字符str[i]都等于对称位置 str[len-1-i],则说明这个字符串是回文串。 1234567891011121314#include <cstdio>#include <cstring>const int maxn=256;//判断字符串str是否是“回文串”bool judge(char str[]){ int len =strlen(str);//字符串长度 for(int i=0;i<len/2;i++){//i枚举字符串的前一半 if(str[i]!=str[len-1-i]){ return false; } } return true;} 全排列(略) 二分查找这里没有用递归(事实上是可以用递归的),这里是动态改变left和right的值。注意传入参数为A[] 思想,只要left木有大于right,就接着找,but根据大于小于动态变化mid为right或者left 1234567891011121314151617181920212223242526272829#include <stdio.h>//A[]为严格递增序列,left为二分下界,right为二分上界,x为欲查询的数//二分区间为左闭右闭的[left,right],传入的初值为[0,n-1]int binarySearch(int A[],int left,int right,int x){ int mid;//mid 为left和right中点 while(left<=right){//不用递归,用while界定界限。 mid=(left+right)/2;//取中点 //有的时候会觉得(left+right)/2可能比较大,所以也可以用left+(right-left)/2 if(A[mid]==x){ return mid; }else if(A[mid]>x){ //中间的数大于x right=mid-1; }else{//中间的数小于x left=mid+1; } } return -1;//查找失败}int main(){ const int n=10; int A[n]={1,3,4,6,7,8,10,11,12,15}; printf(\"%d%d\\n\",binarySearch(A,0,n-1,6),binarySearch(A,0,n-1,9)); return 0;}输出3 -1; 排序快排注意,数组传 a[]或者*a 解析:1.递归程序。 结束标准:left>right 2.选定基准 a[left] 标兵 i和j 3.i和j比较换位。i和j相遇的条件:因为i和j分开运算,j先减,i后加,因为while小于的限制,i不可能超过j, j停止的位置一定小于基准数(因为需要和i换) i停止的位置可能大于基准数(和j换),可能=j(i,j条件的限制) 12345678910111213141516171819202122232425262728293031323334void quicksort(int a[],int left,int right){//x,起始点,y,长度 递归实现的快排,好像是这样的QAQ //注意对比和标程的区别 int i,j,t,temp; if(left>=right)//传入left,right的要有越界提醒 return; temp=a[left];//temp中存基准数 i=left; j=right; //三个while,while中n次换位,while外一次换位 while(i<j){//小于表明没有相遇 直到i和j相遇,i与j是一个数 //顺序很重要,要先从右往左找 while(a[j]>=temp&&i<j)//注意这里也要标明i<j,反复左移 j--;//找到基准右侧比基准小的数 //再从左往右找 while(a[i]<=temp&&i<j)//注意这里一定是小于等于 i++;//找到基准左侧比基准大的数 //交换两个数在数组中的位置, if(i<j){ t=a[i]; a[i]=a[j]; a[j]=t; } } //最终将基准数归位。即基准数为最左边那个数,先把它右边的数大于它小于它的排好,再将基准数归位。 a[left]=a[i]; a[i]=temp; quicksort(a,left,i-1); quicksort(a,i+1,right);} 冒泡排序&选择排序冒泡和选择的区别: 选择循环a[i]和a[j] 冒泡循环a[j]和a[j+1],外面大层是i 12345678910111213141516171819202122232425262728293031323334void xuanze(int *a){//由大到小// int *b=a; for(int i=0;i<n;i++){ for(int j=i+1;j<n;j++){//注意这里是i+1 if(a[i]<a[j]){ int tmp=a[j]; a[j]=a[i]; a[i]=tmp; } } } for(int i=0;i<n;i++){ cout<<a[i]<<\" \"; } cout<<endl;}void maopao(int *a){// int *b=a; for(int i=0;i<n;i++){ for(int j=0;j<n-i;j++){//注意这里是n-i //注意边界,第一次可能越界。思考怎么更改 if(a[j]<a[j+1]){//冒泡是j和j+1之间的关系 int tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp; } } } for(int i=0;i<n;i++){ printf(\"%d\",a[i]); printf(\"%s\",\" \"); }} 归并排序先搞定递归的,日后再搞定非递归的。 (《算法笔记》) 1234567891011121314151617181920212223242526const int maxn=100;void merge(int A[],int L1,int R1,int L2,int R2){ //将数组A的L1,R1,L2,R2区间合并为有序区间。 int i=L1,j=L2; int temp[maxn],index=0;//temp是合并数组的临时数组 while(i<=R1&&j<=R2){//两个数组都没有过界 if(A[i]<=A[j]){ temp[index++]=A[i++]; }else{ temp[index++]=A[j++]; } } while(i<=R1) temp[index++]=A[i++];//最后转移剩下的数组 while(j<=R2) temp[index++]=A[j++]; for(int i=0;i<index;i++){//因为是相邻的两个数组,更新开头的就可以 A[L1+i]=temp[i]; }}void mergesort(int A[],int left,int right){ if(left<right){ int mid=(left+right)/2;//这种递归都是不停地更新mid mergesort(A,left,mid); mergrsort(A,mid+1,right);//切分数组 merge(A,left,mid,mid+1,right);//合并切分的数组 }} 堆堆 完全二叉树,左孩子2i位,右孩子2i+1位 树中每个节点的值都不小于(或不大于)其左右孩子节点的值。 完全二叉树一般用数组存储 这里是较小堆,主要方法是向上或向下调整,都有low(向下调整的点),high(next) 增添是添到末尾,向上调整,注意边界条件,比上2时刻大于low; 删除是把末尾放在堆顶,向下调整,注意边界条件,*2小于high,需要比较左右孩子。 堆支持以下的基本操作: build:建立一个空堆; insert:向堆中插入一个新元素;【向上调整堆】 update:将新元素提升使其符合堆的性质; get:获取当前堆顶元素的值; delete:删除堆顶元素;【向下调整堆】 heapify:使删除堆顶元素的堆再次成为堆 标程(百练上对应题目AC代码)PKU2017年夏令营机试题 定义一个数组,初始化为空。在数组上执行两种操作: 1、增添1个元素,把1个新的元素放入数组。 2、输出并删除数组中最小的数。 使用堆结构实现上述功能的高效算法。 输入 第一行输入一个整数t,代表测试数据的组数。对于每组测试数据,第一行输入一个整数n,代表操作的次数。每次操作首先输入一个整数type。当type=1,增添操作,接着输入一个整数u,代表要插入的元素。当type=2,输出删除操作,输出并删除数组中最小的元素。1<=n<=100000。 输出 每次删除操作输出被删除的数字。 样例输入 123456789101112251 11 21 32241 51 11 72 样例输出 123121 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#include<stdio.h>#include<queue>#include <iostream>#include <cstdio>#include <algorithm>#include <cmath>#include <cstring>using namespace std;int MAX=100000;int heap[100000+5];void downjust(int low,int high){//low为欲调整节点,high为最后一个元素节点 //自上向下调整 int j=low; int k=2*low;//得到孩子 while(k<=high){//我写的是!=// int tmp=heap[k]; if(k+1<high&&heap[k+1]<heap[k]){//左右比较,取最小 k=k+1;} if(heap[k]<heap[j]){ swap(heap[k],heap[j]);//注意swap方法 j=k; k=k*2; }else{//如果不小于,则后面的也都大于了,可以不用比较了,直接break break; } }}void upjust(int low,int high){ //自下向上调整 int k=high/2; int j=high; while(k>=low){ if(heap[j]<heap[k]){ swap(heap[j],heap[k]); j=k; k=k/2; }else{ break; } }}int main(){ int m; int high=0;//high初始化为0 scanf(\"%d\",&m);//输入m组 for(int i=0;i<m;i++){ int n; scanf(\"%d\",&n); memset(heap,0,sizeof(heap));//清空每次操作后的堆 high=0; for(int j=0;j<n;j++){ int op,num; scanf(\"%d\",&op);//得到操作 if(op==1){//如果是增添 scanf(\"%d\",&num); heap[high]=num;//添到末尾 upjust(0,high);//自下向上调整 high++; }else{ printf(\"%d\",heap[0]); printf(\"%s\",\"\\n\"); heap[0]=heap[high-1];//得到最后一个数 downjust(0,high-1);//自上向下调整 high--; } } } return 0;} 链表二叉树【有时间得写一写基本功能实现】 [C++实现(指针的用法和解析)和java实现] 基本结构(数组)1.利用左孩子和右孩子分别是2i,和2i+1,轻松实现。 2.单纯构造node数组(from 《挑战程序设计语言》) 基本结构(链表):1234567891011121314151617181920212223242526272829303132333435363738394041424344struct node{ int data; node* lchild;//二叉树定义的每一个节点都是带指针的 node* rchild;}//新建节点or插入节点,返回一个指针node* newNode(int v){ node* Node=new node(); Node->data=v; Node->lchild=Node->rchild=NULL; return Node;}//二叉树的查找void search(node* root,int x,int newdata){ if(root==NULL){ return;//空树 } if(root->data==x){ root->data=newdata; } search(root->lchild,x,newdata);//往左子树搜索x search(root->rchild,x,newdata);//往右子树搜索x}//二叉树的插入void insert(node* &root,int x){ if(root == NULL){ root=newNode(x); return; } if(由二叉树的性质,x应该插在左子树){ insert(root->lchild,x); }else{ insert(root->rchild,x); }}//先序遍历void preorder(node* root){ if(root==NULL){ return; } // printf中 preorder(root->lchild); preorder(root->rchild);中左右} 中序:左中右 后序:左右中 12345678910//层序void LayerOrder(node* root){ //BFS queue<node*> q; q.push(root); while(!q.empty()){ node* now=q.front(); q.pop(); if(now->lchild!=NULL) q.push(now->lchild); if(now->rchild!=NULL) q.push(now->rchild); } 遍历的非递归实现思想: 12345678910111213141516171819202122vector<int> proorder(Node* root){ //根左右 vector<int> res; if(NULL==root){ return res; } stack<Node*> s; s.put(root); while(!s.empty()){ Node * node=s.top(); s.pop(); res.push_back(node_val); if(node->left){ s.push(node->left); } if(node->right){ s.push(node->right); } } reverse(res.begin(),res.end());//倒转方法 return res;} (二叉搜索树 二叉树链表具体实现)一种链表实现12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include<iostream>using namespace std;//定义节点typedef struct node{ struct node *lchild; struct node *rchild; char data;}BiTreeNode, *BiTree; //*BiTree的意思是给 struct node*起了个别名,叫BiTree,故BiTree为指向节点的指针。//按照前序顺序建立二叉树void createBiTree(BiTree &T) //&的意思是传进来节点指针的引用,括号内等价于 BiTreeNode* &T,目的是让传递进来的指针发生改变{ char c; cin >> c; if('#' == c) //当遇到#时,令树的根节点为NULL,从而结束该分支的递归 T = NULL; else { T = new BiTreeNode; T->data=c; createBiTree(T->lchild); createBiTree(T->rchild); }}//前序遍历二叉树并打印void preTraverse(BiTree T){ if(T) { cout<<T->data<<\" \"; preTraverse(T->lchild); preTraverse(T->rchild); }}//中序遍历二叉树并打印void midTraverse(BiTree T){ if(T) { midTraverse(T->lchild); cout<<T->data<<\" \"; midTraverse(T->rchild); }}//后续遍历二叉树并打印void postTraverse(BiTree T){ if(T) { postTraverse(T->lchild); postTraverse(T->rchild); cout<<T->data<<\" \"; }}int main(){ BiTree T; //声明一个指向二叉树根节点的指针 createBiTree(T); cout<<\"二叉树创建完成!\"<<endl; cout<<\"前序遍历二叉树:\"<<endl; preTraverse(T); cout<<endl; cout<<\"中序遍历二叉树:\"<<endl; midTraverse(T); cout<<endl; cout<<\"后序遍历二叉树:\"<<endl; postTraverse(T); return 0;} 叶子节点路径二叉树根到叶子节点每一条路径【求二叉树是否存在和值为N的路径】 123456789101112131415161718192021222324252627282930313233343536373839404142#include <iostream>#include <cstdio>using namespace std;struct node{ int x; node* left=NULL; node* right=NULL;}node* newnode(int c){ node* x0=new node(); x0->x=c; return x0;}int count=0;void sum(node* current,int result){ //int data=-1; int data=current->x; newres=result*10+data; if(current->left!=NULL){ sum(current->left,newres); } if(current->right!=NULL){ sum(current->right,newres); } if(current->rigth==NULL&&current->left==NULL){ count+=newres; return; }}int main() { node *root =new node(); root->x=1; //cout << \"Hello World!\" << endl;} dfs&bfs&dijiesiqula算法快排 bfs dfs 图算法【算法课本、书】 dijiesiqula算法 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758void dfs(G){ for(G的每一个顶点u){ if(vis[u]==false){//这个节点木有被访问过 vis[u]=true; dfs(u); } } }void bfs(){ queue q; while(q非空){ 取出q的队首元素u进行访问 for(从u出发可达的所有顶点v){ if(v没被访问过) 加入队列 } }}//循环n次void Dijkstra(G,d[],s){ for(循环n次){ u=使d[u]最小还未被访问的顶点; 记u已被访问; for(从u出发能到达所有顶点v){ if(v未被访问&&以u为中介点能使s到顶点v距离更优){ 优化d[v] } } } }//值代表到原点的路径,开始别的均为极大,除了原点为0//数组:点到原点的距离。二维矩阵:点与点之间的距离void Dijkstra(int s){ fill(d,d+MAXV,INF);//给节点赋予一个极大值 d[s]=0; for(int i=0;i<n;i++){//注意这个for字,可能会被return终止 int u=-1;MIN=INF; //找没有被访问过的点中,d(离源点距离)最小的,令u为那一个点 //得到初始加入集合的点和它到集合的距离 for(int j=0;j<n;j++){//这里也有个for,,用于求最小 if(vis[j]==false&&d[j]<MIN){ u=j; MIN=d[j]; } } if(u==-1)return;//没有找到(最后的终止条件) vis[u]=true; //对找到的那个点临接的每一个未被访问过的点,更新d值 for(int v=0;v<n;v++){ if(vis[v]==false&&G[u][v]!=INF&&d[u]+G[u][v]<d[v]){ d[v]=d[u]+G[u][v]; } } }} 并查集(最小生成树)【回顾博客上的那篇文章】https://www.jianshu.com/p/25b08412e313 G题 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123#include<stdio.h>#include<queue>#include <iostream>#include <cstdio>#include <algorithm>#include <cmath>#include <cstring>using namespace std;int point[55];int findfather(int a){//寻找祖先,开始的时候,祖先都是自己 if(point[a]!=a){ return findfather(point[a]); }else{ return a; }}void unionab(int a,int b){//合并一个方向 if(findfather(a)!=findfather(b)){ point[a]=b; }}bool issame(int a,int b){//判断是否是一个祖先 if(findfather(a)==findfather(b)) return true; else return false;}int main(){ int m; queue<int> answer; scanf(\"%d\",&m); while(m!=0){ int n; scanf(\"%d\",&n); if(n==0){ printf(\"%d\",0); printf(\"%s\",\"\\n\");// string s;// getline(cin,s); scanf(\"%d\",&m); continue; }else{ for(int i=0;i<55;i++){ point[i]=i; } int line[n][3]; for(int i=0;i<n;i++){ for(int j=0;j<3;j++){ scanf(\"%d\",&line[i][j]); } } int graph[m+1][m+1]; int ans=0; int zuxian=-1; memset(graph,-1,sizeof(graph)); for(int i=0;i<n;i++){ int x=line[i][0],y=line[i][1],len=line[i][2]; if(graph[x][y]==-1||graph[x][y]>len){ graph[x][y]=len; graph[y][x]=len; } } int edge=0; for(int i=0;i<n;i++){ int minx=0,miny=0;// int min=105; for(int i=1;i<=m;i++){ for(int j=i+1;j<=m;j++){ //每次找最小的,找到后可以赋一个较大值 if(graph[i][j]<min&&graph[i][j]!=-1){ minx=i; miny=j; min=graph[i][j]; } } } if(!(minx==0&&miny==0)){ if(!issame(minx,miny)){//怎么保证单向呢 unionab(findfather(minx),findfather(miny)); ans+=min; edge++; } graph[minx][miny]=105; graph[miny][minx]=105; } answer.push(ans); }// string s;// getline(cin,s); scanf(\"%d\",&m); } while(!answer.empty()){ printf(\"%d%s\",answer.front(),\"\\n\"); answer.pop(); }// int m; return 0;} 动态规划 设计一个简单的任务队列, 要求分别在 1,3,4 秒后打印出 “1”, “2”, “3” 字典序给你一个数字n(n < 1e9), 再给你一个数字k(k < n), 要求你找到1, 2, 3, … n按照字典序排序后, 第k大的数字 【看算法书】","categories":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/categories/算法/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/tags/算法/"},{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"http常见总结","slug":"http","date":"2019-03-29T16:00:00.000Z","updated":"2019-03-30T02:03:26.017Z","comments":true,"path":"2019/03/30/http/","link":"","permalink":"https://yongbosmart.github.io/2019/03/30/http/","excerpt":"","text":"http常见总结网络综述tcp/ip协议广义可以认为是ip协议通信过程中,使用到的协议族的统称。 tcp/ip协议族可以分为以下4层。 应用层:决定向用户提供应用服务时通信的活动。FTP,dns,http 传输层:提供两台计算机之间的数据传输。tcp和udp 网络层:处理流动的数据包,选择传输路线。路由。 链路层:处理连接的硬件部分及物理部分。 客户端发出想看web页面的请求,tcp接到http报文分割,打上标记序号端口号再给网络层,网络层增加Mac地址再给链路层(每通过一层则增加首部)。然后传到服务端再拆分组合起来。 ip地址指明了节点被分配的地址。Mac地址指网卡所属的固定地址。ip地址可以和Mac地址进行配对。ip地址可变换,但Mac地址基本不改变。 tcp三次握手?为什么不是两次【一个双向建立连接的过程】 TCP在传输之前会进行三次沟通,一般称为“三次握手”,传完数据断开的时候要进行四次沟通,一般称为“四次挥手”。 12345678910111213 1)序号:seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。 (2)确认序号:ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。 (3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下: (A)URG:紧急指针(urgent pointer)有效。 (B)ACK:确认序号有效。 (C)PSH:接收方应该尽快将这个报文交给应用层。 (D)RST:重置连接。 (E)SYN:发起一个新连接。 (F)FIN:释放一个连接。 需要注意的是: (1)不要将确认序号ack与标志位中的ACK搞混了。 (2)确认方ack=发起方seq+1,两端配对。 三次握手: A seq x SYN=1,ACK=0 B seq y(自己的序号) ack=x+1(准备接收A的) SYN=1;ACK=1 A seq (x+1) ack y+1(准备接收B的)ACK=1 由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接, 收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。 A FIN=1 传送最后数据,FIN_wait_1 seq=x B 收到FIN后 发ack=x+1 seq=v B 发FIN=1 传输完毕 seq=w ack=x+1 A seq=x+1 ack=w+1 tcp&udp区别tcp是一种面向连接的、可靠的、基于字节流的传输层通信协议 TCP UDP 是否连接 面向连接 面向非连接 传输可靠性 可靠 不可靠 应用场合 传输大量的数据,对可靠性要求较高的场合 传送少量数据、对可靠性要求不高的场景 速度 慢 快 .TCP保证数据正确性,UDP可能丢包, TCP保证数据顺序,UDP不保证。 应用 HTTP,HTTPS,FTP等传输文件的协议,POP,SMTP等邮件的传输协议 视频传输、实时通信 DNS域名解析:由域名查找ip地址。 http请求过程以下代码会生成一个http请求。客户端向服务器请求 123<form method="GET" action="/cgi/sample.cgi"> <input type="text" name="field"></form> 根据协议生成消息体先显示文字,有图片再请求显示图片(不过会预留空间)。由于每条请求消息中只能写一个URI,实验每次只能获取1个文件。如果需要获取多个文件,必须对每个文件单独发送1条请求。 HTTP请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 Content-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些Asp网页点击的结果却是下载到的一个文件或一张图片的原因。 实例来自http://www.runoob.com/http/http-tutorial.html下面实例是一点典型的使用GET来传递数据的实例: 客户端请求: 1234GET /hello.txt HTTP/1.1 【请求行: get方法,对hello.txt文件操作,协议版本】User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3【以下是请求头,此例子木有请求正文】Host: www.example.com 【通过dns找到服务器】Accept-Language: en, mi 【声明浏览器所用的语言】 请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。 服务端响应: 1234567891011HTTP/1.1 200 OK 【状态行,200表示返回状态】Date: Mon, 27 Jul 2009 12:28:53 GMT 【响应头】Server: Apache 【服务器类 型】Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT 【日期】ETag: "34aa387-d-1568eb00"Accept-Ranges: bytesContent-Length: 51 【长度】Vary: Accept-EncodingContent-Type: text/plain空行响应正文 相关性质不保存状态即无状态协议。so引入了cookie 用完整的请求URI来定位资源。 keep-alive:持久连接,减少了tcp连接的重新建立。 相关方法这些方法是对服务器的一系列操作 1 GET 请求指定的页面信息,并返回实体主体。 2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。(类似于上传) 5 DELETE 请求服务器删除指定的页面。 6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 7 OPTIONS 允许客户端查看服务器的性能。 8 TRACE 回显服务器收到的请求,主要用于测试或诊断。 get&post区别GET的语义是请求获取指定的资源。 POST的语义是根据请求负荷(报文主体)对指定的资源做出处理,具体的处理方式视资源类型而不同。请求资源。 直观区别是一个在url中一个在请求体中,一个参数多一个参数比较少。 GET请求,浏览器会把http header和data一并发送出去,服务器响应200,返回数据。 POST请求,浏览器先发送header,服务器响应100,浏览器再发送data,服务器响应200,返回数据。 cookie & session HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。 cookie客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。 有了Cookie这样的技术实现,服务器在接收到来自客户端浏览器的请求之后,就能够通过分析存放于请求头的Cookie得到客户端特有的信息,从而动态生成与该客户端相对应的内容。通常,我们可以从很多网站的登录界面中看到“请记住我”这样的选项,如果你勾选了它之后再登录,那么在下一次访问该网站的时候就不需要进行重复而繁琐的登录动作了,而这个功能就是通过Cookie实现的。 SessionSession是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。 Session保存在服务器端。为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。 Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可以使用request.getSession(true)强制生成Session。 Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问服务器一次,无论是否读写Session,服务器都认为该用户的Session“活跃(active)”了一次。 session存储及详细论述后端怎么存储session以及登录验证全过程 session可以持久化为数据库、文件等一系列地方。可以使用Redis存储。 https://blog.csdn.net/qq_15096707/article/details/74012116 在创建了Session的同时,服务器会为该Session生成唯一的Session id,而这个Session id在随后的请求中会被用来重新获得已经创建的Session;在Session被创建之后,就可以调用Session相关的方法往Session中增加内容了,而这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从而再次使用之。 (java后台有一系列关于session的方法) https://www.cnblogs.com/lxp503238/p/6549563.html 会话范围:会话范围是某个用户从首次访问服务器开始,到该用户关闭浏览器结束! > 会话:一个用户对服务器的多次连贯性请求!所谓连贯性请求,就是该用户多次请求中间没有关闭浏览器! 服务器会为每个客户端创建一个session对象,session就好比客户在服务器端的账户,它们被服务器保存到一个Map中,这个Map被称之为session缓存!> Servlet中得到session对象:HttpSession session = request.getSession(); > > Jsp中得到session对象:session是jsp内置对象之下,不用创建就可以直接使用! session域相关方法:> void setAttribute(String name, Object value); > > Object getAttribute(String name); > > void removeAttribute(String name); session&cookie区别 cookie数据存放在客户的浏览器上,session数据放在服务器上; cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session; session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE; 单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K; Cookie和Session的方案虽然分别属于客户端和服务端,但是服务端的session的实现对客户端的cookie有依赖关系的,上面我讲到服务端执行session机制时候会生成session的id值,这个id值会发送给客户端,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,而这个id值在客户端会保存下来,保存的容器就是cookie,因此当我们完全禁掉浏览器的cookie的时候,服务端的session也会不能正常使用 状态码 200 - 请求成功 301 - 资源(网页等)被永久转移到其它URL 404 - 请求的资源(网页等)不存在 500 - 内部服务器错误 1**正在处理 信息,服务器收到请求,需要请求者继续执行操作 2**处理完毕 成功,操作被成功接收并处理 3**进一步处理 重定向,需要进一步的操作以完成请求 4** 客户端错误,请求包含语法错误或无法完成请求 5** 服务器错误,服务器在处理请求的过程中发生了错误 301&302: 301:永久性重定向,需要更新书签 302; 暂时性重定向,不一定需要更新书签。 http缓存强缓存&弱缓存 与传统访问方式不同,CDN网络则是在用户和服务器之间增加缓存层,将用户的访问请求引导到最优的缓存节点而不是服务器源站点,从而加速访问速度。 CDN是将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。解决因分布、带宽、服务器性能带来的访问延迟问题,适用于站点加速、点播、直播等场景。最简单的CDN网络由一个DNS服务器和几台缓存服务器组成。 http&httpshttps基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护 通过抓包可以看到数据不是明文传输,而且HTTPS有如下特点: 内容加密:采用混合加密技术,中间者无法直接查看明文内容 验证身份:通过证书认证客户端访问的是自己的服务器 保护数据完整性:防止传输的内容被中间人冒充或者篡改 https过程: 来源:https://blog.csdn.net/xiaoming100001/article/details/81109617 client向server发送请求https://baidu.com,然后连接到server的443端口。 服务端必须要有一套数字证书,可以自己制作,也可以向组织申请。 【比如来源于CA】 1CA中心为每个使用公开密钥的用户发放一个数字证书,数字证书的作用是证明证书中列出的用户合法拥有证书中列出的公开密钥。CA机构的数字签名使得攻击者不能伪造和篡改证书。 区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。 传送证书 这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间、服务端的公钥,第三方证书认证机构(CA)的签名,服务端的域名信息等内容。 客户端解析证书 [解析数字签名是否真实,得到服务器公钥] 这部分工作是由客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值(秘钥)。然后用证书对该随机值进行加密。 传送加密信息 【利用公钥加密对称密钥】 这部分传送的是用证书加密后的秘钥,目的就是让服务端得到这个秘钥,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。 6.服务段加密信息服务端用私钥解密秘密秘钥,得到了客户端传过来的私钥,然后把内容通过该值进行对称加密。 7.传输加密后的信息这部分信息是服务端用私钥加密后的信息,可以在客户端被还原。 8.客户端解密信息客户端用之前生成的私钥解密服务端传过来的信息,于是获取了解密后的内容。","categories":[{"name":"计算机网络","slug":"计算机网络","permalink":"https://yongbosmart.github.io/categories/计算机网络/"}],"tags":[{"name":"计算机网络","slug":"计算机网络","permalink":"https://yongbosmart.github.io/tags/计算机网络/"}]},{"title":"推荐算法&评分预测问题","slug":"推荐算法&评分预测问题","date":"2019-03-24T16:00:00.000Z","updated":"2019-03-30T02:16:22.706Z","comments":true,"path":"2019/03/25/推荐算法&评分预测问题/","link":"","permalink":"https://yongbosmart.github.io/2019/03/25/推荐算法&评分预测问题/","excerpt":"","text":"推荐算法&评分预测问题参考书本: 项亮, 推荐系统实践. 2012 本文系阅读笔记 实际系统-topN问题 推荐系统各种研究比赛-评分预测问题。 评分预测问题最基本数据集就是用户评分数据集。(u,i,r) 用户u给物品i评了r分。 评分预测问题是如何通过已知的用户历史记录评分预测未知的用户评分记录。 离线实验方法一般可以用均方根误差RMSE度量预测的精度: $RMSE=\\frac{\\sqrt{\\sum_{(u,t)\\in T}(r_{ui}-\\hat{r})^2}}{|Test|}$ 评分预测的目的就是找到最好的模型最小化测试集的RMSE 【即预测数据的平均误差最小】 划分训练集和测试集的方法: 如果和时间没有关系—可以随机 如果和时间有关系—用户的旧行为为训练集,新行为为测试集。 Netflix 通过如下方式划分数据集,首先将每个用户的评分记录按照从早到晚进行排序,然后将用户最后 10%的评分记录作为测试集,前 90% 的评分记录作为训练集。 评分预测方法平均值 全局平均值 它的定义为训练集中所有评分记录的评分平均值。 $\\mu =\\frac{\\sum_{(u,i)\\in Train}r_{ui}}{\\sum_{(u,i)\\in Train}1}$ 而最终的预测函数可以直接定义为 $\\hat{r}_{ui}=\\mu$ 用户评分平均值 用户u的评分平均值$\\bar{r_{u}}$定义为用户u在训练集中所有评分的平均值。 $\\bar{r_{u}} = \\frac{\\sum_{i \\in N(u)} r_{ui}}{\\sum_{i \\in N(u)}1}$ 而最终的预测函数可以直接定义为 $\\hat{r}_{ui}=\\bar{r_{u}}$ 物品评分平均值 物品i的评分平均值$\\bar{r_{i}}$定义为物品i在训练集中所有评分的平均值: $\\bar{r_{i}} = \\frac{\\sum_{i \\in N(i)} r_{ui}}{\\sum_{i \\in N(i)}1}$ 而最终的预测函数可以直接定义为 $\\hat{r}_{ui}=\\bar{r_{i}}$ 用户分类对物品分类的平均值 假设有两个分类函数,一个是用户分类函数$\\phi$,一个是物品分类函数$\\varphi$。$\\phi(u)$定义了用户u所属的类,$\\varphi(i)$定义了物品i所属的类。那么,我们可以利用训练集中同类用户对同类物品评分的平均值预测用户对物品的评分,即: $\\hat{r_{ui}}=\\frac{\\sum_{(v,j)\\in Train,\\phi(u)=\\phi(v),\\varphi(i)=\\varphi(j)}r_{vj}}{\\sum_{(v,j)\\in Train,\\phi(u)=\\phi(v),\\varphi(i)=\\varphi(j)}1}$ 如果定义$\\phi(u)$=0,$\\varphi(i)$=0,那么 $\\hat{r_{ui}}$就是全局平均值。 如果定义$\\phi(u)$ =u, $\\varphi(i)$=0,那么$\\hat{r_{ui}} $就是用户评分平均值。 如果定义$\\phi(u)$=0,$\\varphi(i)$=i ,那么 $\\hat{r_{ui}}$就是物品评分平均值。除了这 3 种特殊的平均值,在用户评分数据上还可以定义很多不同的分类函数。 用户和物品的平均分 对于一个用户,可以计算他的评分平均分。然后将所有用户按照评分平均分从小到大排序,并将用户按照平均分平均分成 N 类。物品也可以用同样的方式分类。 用户活跃度和物品流行度 对于一个用户,将他评分的物品数量定义为他的活跃度。得到用户活跃度之后,可以将用户通过活跃度从小到大排序,然后平均分为 N 类。物品的流行度定义为给物品评分的用户数目,物品也可以按照流行度均匀分成 N 类。 [计算类类平均值的方法代码见书] 在这段代码中, user_cluster.GetGroup 函数接收一个用户 ID ,然后根据一定的算法返回用户的类别。 item_cluster.GetGroup 函数接收一个物品的 ID ,然后根据一定的算法返回物品的类别。 total[gu][gi]/count[gu][gi] 记录了第 gu 类用户给第 gi 类物品评分的平均分。上文提到, user_cluster 和 item_cluster 有很多不同的定义方式。 【详细看书】 基于邻域的方法基于用户的邻域算法和基于物品的邻域算法都可以应用到评分预测中。 基于用户的邻域算法认为预测一个用户对一个物品的评分,需要参考和这个用户兴趣相似的用户对该物品的评分。 基于物品的邻域算法在预测用户 u 对物品 i 的评分时,会参考用户 u 对和物品 i 相似的其他物品的评分。 用到了皮尔逊系数。 隐语义模型和矩阵分解模型在推荐系统领域,提的最多的就是潜语义模型和矩阵分解模型。其实,这两个名词说的是一回事,就是如何通过降维的方法将评分矩阵补全。 用户的评分行为可以表示成一个评分矩阵 R ,其中 R [ u ][ i ] 就是用户 u 对物品 i 的评分。但是,用户不会对所有的物品评分,所以这个矩阵里有很多元素都是空的,这些空的元素称为缺失值( missing value )。因此,评分预测从某种意义上说就是填空,如果一个用户对一个物品没有评过分,那么推荐系统就要预测这个用户是否是否会对这个物品评分以及会评几分。 SVD分解一般认为,如果补全后矩阵的特征值和补全之前矩阵的特征值相差不大,就算是扰动比较小。所以,最早的矩阵分解模型就是从数学上的 SVD (奇异值分解)开始的。 降维分解。 SVD 分解是早期推荐系统研究常用的矩阵分解方法,不过该方法具有以下缺点,因此很难在实际系统中应用。 该方法首先需要用一个简单的方法补全稀疏评分矩阵。一般来说,推荐系统中的评分矩阵是非常稀疏的,一般都有 95% 以上的元素是缺失的。而一旦补全,评分矩阵就会变成一个稠密矩阵,从而使评分矩阵的存储需要非常大的空间,这种空间的需求在实际系统中是不可能接受的。 该方法依赖的 SVD 分解方法的计算复杂度很高,特别是在稠密的大规模矩阵上更是非常慢。一般来说,这里的 SVD 分解用于 1000 维以上的矩阵就已经非常慢了,而实际系统动辄是上千万的用户和几百万的物品,所以这一方法无法使用。如果仔细研究关于这一方法的论文可以发现,实验都是在几百个用户、几百个物品的数据集上进行的。 Simon Funk SVDSimon Funk 提出的矩阵分解方法后来被 Netflix Prize 的冠军 Koren 称为 Latent Factor Model (简称为 LFM ) 令$\\hat{R}=P^TQ,r_{ui}=\\sum_f p_{uf}q_{tf}$ 那么, Simon Funk 的思想很简单:可以直接通过训练集中的观察值利用最小化 RMSE 学习 P 、 Q 矩阵。 【于是得到损失函数$|R-\\hat{R}|^2$】 为了防止过拟合,再加上正则化。然后利用梯度下降求参数。 $\\alpha$是学习速率( learning rate ),它的取值需要通过反复实验获得。 【这块可以看书,和机器学习推导比较像】 LFM 提出之后获得了很大的成功,后来很多著名的模型都是通过对 LFM 修修补补获得的,下面的各节将分别介绍一下改进 LFM 的各种方法。这些改进有些是对模型的改进,有些是将新的数据引入到模型当中。 加入偏置项后的LFM$\\hat{r_{ui}}=\\mu +b_{u}+b_{i}+p_u^T\\cdot q_i$ μ:训练集中所有记录的评分的全局平均数。在不同网站中,因为网站定位和销售的物品不同,网站的整体评分分布也会显示出一些差异。比如有些网站中的用户就是喜欢打高分,而另一些网站的用户就是喜欢打低分。而全局平均数可以表示网站本身对用户评分的影响。 $b_u$用户评分相对所有平均分的误差(有的用户苛刻有的用户宽容。) $b_i$物体平均分相对均分偏差。物体好坏。 其中$b_u,b_i$要通过机器学习训练出来。 考虑邻域影响的LFM SVD++前面的 LFM 模型中并没有显式地考虑用户的历史行为对用户评分预测的影响。为此, Koren在 Netflix Prize 比赛中提出了一个模型,将用户历史评分的物品加入到了 LFM 模型中, Koren 将该模型称为 SVD++ 。 [代码看书] SVD++就是加入了很多边缘信息,在SVD的基础上引入了隐式反馈。 关于公式推导及更多信息参见: https://www.jianshu.com/p/f06860717c9e https://www.cnblogs.com/Xnice/p/4522671.html 加入时间信息利用时间信息的方法也主要分成两种,一种是将时间信息应用到基于邻域的模型中,另一种是将时间信息应用到矩阵分解模型中。下面将分别介绍这两种算法。 基于邻域的模型因为 Netflix Prize 数据集中用户数目太大,所以基于用户的邻域模型很少被使用,主要是因为存储用户相似度矩阵非常困难。因此,本节主要讨论如何将时间信息融合到基于物品的邻域模型中。 也是用一个时间函数。随时间离现在越远,物品影响力越小。 【公式太复杂不想敲了,看书吧】 TitemCF 基于矩阵分解的模型在引入时间信息后,用户评分矩阵不再是一个二维矩阵,而是变成了一个三维矩阵。不过,我们可以仿照分解二维矩阵的方式对三维矩阵进行分解。 TSVD 【公式在书上,可以看一下,也不想敲了】 模型融合模型级联融合假设已经有一个预测器$\\hat{r}^{(k)}$,对于每个用户 — 物品对 (u, i) 都给出预测值,那么可以在这个预测器的基础上设计下一个预测器$\\hat{r}^{(k+1)}$来最小化损失函数: $C=\\sum_{(u,i)\\in Train}(\\hat{r_{ui}}-\\hat{r_{ui}}^{(k)}-\\hat{r_{ui}}^{(k+1) })^2$ 级联融合很像 Adaboost 算法。和 Adaboost 算法类似,该方法每次产生一个新模型,按照一定的参数加到旧模型上去,从而使训练集误差最小化。不同的是,这里每次生成新模型时并不对样本集采样,针对那些预测错的样本,而是每次都还是利用全样本集进行预测,但每次使用的模型都有区别。一般来说,级联融合的方法都用于简单的预测器,比如前面提到的平均值预测器。 模型加权融合假设我们有 K 个不同的预测器{$\\hat{r}^{(1)},\\hat{r}^{(2)},\\dots,\\hat{r}^{(K)}$},本节主要讨论如何将它们融合起来获得最低的预测误差。最简单的融合算法就是线性融合,即最终的预测器$\\hat{r}$是这 K 个预测器的线性加权。 因此,在模型融合时一般采用如下方法。 假设数据集已经被分为了训练集 A 和测试集 B ,那么首先需要将训练集 A 按照相同的分割方法分为 A1 和 A2 ,其中 A2 的生成方法和 B 的生成方法一致,且大小相似。 在 A1 上训练 K 个不同的预测器,在 A2 上作出预测。因为我们知道 A2 上的真实评分值,所以可以在 A2 上利用最小二乘法, 计算出线性融合系数$\\alpha_k$ 。 在 A 上训练 K 个不同的预测器,在 B 上作出预测,并且将这 K 个预测器在 B 上的预测结果按照已经得到的线性融合系数加权融合,以得到最终的预测结果。 除了线性融合,还有很多复杂的融合方法,比如利用人工神经网络的融合算法。其实,模型融合问题就是一个典型的回归问题,因此所有的回归算法都可以用于模型融合。","categories":[{"name":"推荐算法","slug":"推荐算法","permalink":"https://yongbosmart.github.io/categories/推荐算法/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"推荐算法","slug":"推荐算法","permalink":"https://yongbosmart.github.io/tags/推荐算法/"}]},{"title":"菜鸟google colab踩坑笔记","slug":"菜鸟google colab踩坑笔记","date":"2019-03-22T16:00:00.000Z","updated":"2019-03-30T02:14:04.661Z","comments":true,"path":"2019/03/23/菜鸟google colab踩坑笔记/","link":"","permalink":"https://yongbosmart.github.io/2019/03/23/菜鸟google colab踩坑笔记/","excerpt":"","text":"菜鸟google colab踩坑笔记因为想用深度学习,进一步需要GPU, 又因为木钱也木有GPU,所以就只能用Google免费的colab了, 上面还有提供TPU。 具体初步注册呀什么的网上都有,需要注意的有以下几点。 1.开始的时候可能没有colab选项,需要在更多里找。(或者搜一下) 2.colab基于jupyter notebook,所以开始第一眼看是懵逼的。 但是它每一个单元格可以当一个程序运行。 开始的时候我导进去两个python文件(就是把python代码复制到单元格里),还在想怎么import。然后在这里才发现两个python文件放在一起不需要import。 3.TensorFlow预先装好了。直接复制python代码是python代码,代码前加!可以当做Ubuntu控制台用。 比如 1!pip install 包 各个包的具体装法还是看网上吧。 4.数据集。 外部数据集怎么用。 我这里只用的是 先把数据集上传到Google云盘里,然后通过下面的操作,就能在colab右边的文件中看到数据集了。 123456789101112!apt-get install -y -qq software-properties-common python-software-properties module-init-tools!add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null!apt-get update -qq 2>&1 > /dev/null!apt-get -y install -qq google-drive-ocamlfuse fusefrom google.colab import authauth.authenticate_user()from oauth2client.client import GoogleCredentialscreds = GoogleCredentials.get_application_default()import getpass!google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URLvcode = getpass.getpass()!echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} 以上好像是一个验证的过程,出现网址需要前往,然后粘贴验证码贴在网址下面的框里。需要两次。 然后再进行以下操作即可。 12!mkdir -p drive!google-drive-ocamlfuse -o nonempty drive 一个报的错误(在用TensorFlow跑一个demo时出的错误) An exception has occurred, use %tb to see the full traceback. SystemExit: 2 /usr/local/lib/python3.6/dist-packages/IPython/core/interactiveshell.py:2890: UserWarning: To exit: use ‘exit’, ‘quit’, or Ctrl-D. warn(“To exit: use ‘exit’, ‘quit’, or Ctrl-D.”, stacklevel=1) 1234567891011121314151617181920parser=argparse.ArgumentParser()parser.add_argument('--n_epochs',type=int,default=200,help='number of epochs of training')parser.add_argument('--batch_size',type=int,default=64,help='size of the batches')parser.add_argument('--lr',type=float,default=0.0002,help='adam:learning rate')parser.add_argument('--b1',type=float,default=0.5,help='adam:decay of first order momentum of gradient')parser.add_argument('--b2',type=float,default=0.999,help='adam:decay of first order momentum of gradient')parser.add_argument('--n_cpu',type=float,default=4,help='number of cpu threads to use during batch generation')parser.add_argument('--latent_dim',type=int,default=100,help='dimensionality of the latent space')parser.add_argument('--n_classes',type=int,default=10,help='number of classes for dataset')parser.add_argument('--img_size',type=int,default=32,help='size of each image dimension')parser.add_argument('--channels',type=int,default=1,help='number of image channels')parser.add_argument('--sample_interval',type=int,default=400,help='interval between image sampling') opt=parser.parse_args(args=[])#原来是opt=parser.parse_args()--------------------- 作者:FQ_G 来源:CSDN 原文:https://blog.csdn.net/qq_33266320/article/details/81487744 版权声明:本文为博主原创文章,转载请附上博文链接! 解决方法:https://blog.csdn.net/qq_33266320/article/details/81487744","categories":[{"name":"深度学习","slug":"深度学习","permalink":"https://yongbosmart.github.io/categories/深度学习/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"深度学习","slug":"深度学习","permalink":"https://yongbosmart.github.io/tags/深度学习/"}]},{"title":"基于物品的协同过滤","slug":"基于物品的协同过滤","date":"2019-02-19T16:00:00.000Z","updated":"2019-03-03T13:02:03.327Z","comments":true,"path":"2019/02/20/基于物品的协同过滤/","link":"","permalink":"https://yongbosmart.github.io/2019/02/20/基于物品的协同过滤/","excerpt":"","text":"基于物品的协同过滤 参考书本: 项亮, 推荐系统实践. 2012 本文系阅读笔记 。 基于用户的协同过滤的缺点1.网站用户基数增多,矩阵难以构造,时空复杂度增加。 2.难以对推荐结果做出解释。 基于物品的协同过滤ItemCF大致含义该算法会因为你购买过《数据挖掘导论》而给你推荐《机器学习》。不过, ItemCF 算法并不利用物品的内容属性计算物品之间的相似度,它主要通过分析用户的行为记录计算物品之间的相似度。该算法认为,物品 A 和物品 B 具有很大的相似度是因为喜欢物品 A 的用户大都也喜欢物品B 。 基于物品的协同过滤算法可以利用用户的历史行为给推荐结果提供推荐解释,比如给用户推荐《天龙八部》的解释可以是因为用户之前喜欢《射雕英雄传》。 具体步骤基于物品的协同过滤算法主要分为两步。(1) 计算物品之间的相似度。(2) 根据物品的相似度和用户的历史行为给用户生成推荐列表。 具体公式(ij直接的相似度): $w_{ij}=\\frac{|N(i)\\cap N(j)|}{|N(i)|}$ N(i)表示喜欢物品i的用户数。 但是如果物品j很热门,几乎每个人都喜欢,如《哈利波特》与新华字典,则关系度会接近于1。 为了避免推荐热门物品,则有公式: $w_{ij}=\\frac{|N(i)\\cap N(j)|}{\\sqrt{|N(i)||N(j)|}}$ 这个公式惩罚了物品j的权重,减轻了热门物品会和很多物品相似的可能性。 尽管上面的公式分母已经考虑到了 j 的流行度,但在实际应用中,热门的 j 仍然会获得比较大的相似度。因此可在分母上进行惩罚。 $w_{ij}=\\frac{|N(i)\\cap N(j)|}{|N(i)|^{1-\\alpha}|N(j)|^{\\alpha}}$ $\\alpha \\in [0.5,1]$ 但不能完全解决,两个不同领域的最热门物品之间往往具有比较高的相似度。这个时候,仅仅靠用户行为数据是不能解决这个问题的,因为用户的行为表示这种物品之间应该相似度很高。此时,我们只能依靠引入物品的内容数据解决这个问题,比如对不同领域的物品降低权重等。这些就不是协同过滤讨论的范畴了。 实现方法可以首先建立用户-物品倒排表,即每一个用户建立一个包含他喜欢的物品的列表。 比如电影,一般来说,同系列的电影、同主角的电影、同风格的电影、同国家和地区的电影会有比较大的相似度。 在得到物品之间相似度后,itemCF运用以下公式计算用户u对一个物品j的兴趣: $p_{wj}=\\sum_{i\\in N(u)\\cap S(j,K) }w_{ij}r_{wi}$ $S(j,K)$与物品j最相似的K个物品的集合 $w_{ij}$为物品相似度。$r_{wi}$即用户u对物品i的兴趣程度,这里可看做有过行为为1。 对既属于用户喜欢的物品,又在与j物品相似的物品集合内的每一个物品,得到权重相加和(即相似度乘感兴趣程度)。 相关指标 精度(准确率和召回率) 可以看到 ItemCF 推荐结果的精度也是不和 K 成正相关或者负相关的,因此选择合适的 K 对获得最高精度是非常重要的。 流行度 和 UserCF 不同,参数 K 对 ItemCF 推荐结果流行度的影响也不是完全正相关的。随着 K 的增加(流行物品相对增多),结果流行度会逐渐提高,但当 K 增加到一定程度,流行度就不会再有明显变化。 覆盖率 K 增加会降低系统的覆盖率(流行率增加)。 方法改进A. 假设有这么一个用户,他是开书店的,并且买了当当网上 80% 的书准备用来自己卖。那么,他的购物车里包含当当网 80% 的书。假设当当网有 100 万本书,也就是说他买了 80 万本。从前面对 ItemCF 的讨论可以看到,这意味着因为存在这么一个用户,有 80 万本书两两之间就产生了相似度,也就是说,内存里即将诞生一个 80 万乘 80 万的稠密矩阵。 John S. Breese 在论文中提出了一个称为 IUF ( Inverse User Frequence ),即用户活跃度对数的倒数的参数,他也认为活跃用户对物品相似度的贡献应该小于不活跃的用户,他提出应该增加 IUF参数来修正物品相似度的计算公式:ItemCF-IUF算法 $w_{ij}=\\frac{\\sum{u\\in N(i)\\cap N(j)}\\frac{1}{\\log1 +|N(u)|}}{\\sqrt{|N(i)||N(j)|}}$ 同ItemCF相比,降低了流行度,提高了推荐结果的覆盖率。 B. 物品相似度归一化。 Karypis 在研究中发现如果将 ItemCF 的相似度矩阵按最大值归一化,可以提高推荐的准确率。其研究表明,如果已经得到了物品相似度矩阵 w ,那么可以用如下公式得到归一化之后的相似度矩阵 w’ : $w’_{ij}=\\frac{w_{ij}}{\\underset{j}{\\operatorname{max}}w_{ij}}$ 相似度的归一化可以提高推荐的多样性。[解释看原书]","categories":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/categories/推荐系统/"}],"tags":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/tags/推荐系统/"},{"name":"读书笔记","slug":"读书笔记","permalink":"https://yongbosmart.github.io/tags/读书笔记/"}]},{"title":"隐语义模型","slug":"隐语义模型","date":"2019-02-19T16:00:00.000Z","updated":"2019-03-03T13:01:52.067Z","comments":true,"path":"2019/02/20/隐语义模型/","link":"","permalink":"https://yongbosmart.github.io/2019/02/20/隐语义模型/","excerpt":"","text":"隐语义模型 参考书本: 项亮, 推荐系统实践. 2012 本文系阅读笔记 。 LFM ( latent factor model )隐语义模型。该算法最早在文本挖掘领域被提出,用于找到文本的隐含语义。相关的名词有 LSI 、 pLSA 、 LDA 和 Topic Model 。 定义隐语义模型是最近几年推荐系统领域最为热门的研究话题,它的核心思想是通过隐含特征(latent factor) 联系用户兴趣和物品。 eg: 用户 A 的兴趣涉及侦探小说、科普图书以及一些计算机技术书,而用户 B 的兴趣比较集中在数学和机器学习方面。可以对书和物品的兴趣进行分类。对于某个用户,首先得到他的兴趣分类,然后从分类中挑选他可能喜欢的物品。 但是往往来说,编辑的分类不能代表用户的真实意见,或者可能有各种各样的偏差。隐含语义分析技术( latent variable analysis )出现了。隐含语义分析技术因为采取基于用户行为统计的自动聚类。 相关模型: pLSA 、 LDA 、隐含类别模型( latent class model )、隐含主题模型( latent topic model )、矩阵分解( matrix factorization )。 LFM通过如下公式计算用户 u 对物品 i 的兴趣: $Proference(u,i)=r_{ui}=p_u^Tq_i=\\sum^F_{f=1}p_{u,k}q_{i,k}$ $p_{u,k}$度量了用户u和第k个隐类的关系,$q_{i,k}$度量了第k个隐类和物品i直接的关系。 要计算这两个参数,需要一个训练集,对于每个用户 u ,训练集里都包含了用户 u 喜欢的物品和不感兴趣的物品,通过学习这个数据集,就可以获得上面的模型参数。推荐系统的用户行为分为显性反馈和隐性反馈。 LFM 在显性反馈数据(也就是评分数据)上解决评分预测问题并达到了很好的精度。不过本章主要讨论的是隐性反馈数据集,这种数据集的特点是只有正样本(用户喜欢什么物品),而没有负样本(用户对什么物品不感兴趣)。那么,在隐性反馈数据集上应用LFM解决TopN推荐的第一个关键问题就是如何给每个用户生成负样本。 对负样本采样时应该遵循以下原则。 对每个用户,要保证正负样本的平衡(数目相似)。 对每个用户采样负样本时,要选取那些很热门,而用户却没有行为的物品。一般认为,很热门而用户却没有行为更加代表用户对这个物品不感兴趣。因为对于冷门的物品,用户可能是压根没在网站中发现这个物品,所以谈不上是否感兴趣。 经过采样,可以得到一个用户 — 物品集 K={(u,i)},其中如果(u, i)是正样本,则有 $r_{ui}$=1,否则,有$r_{ui}$=0,则优化如下损失函数来得到p,q $C=\\sum_{(u,i\\in K)}(r_{ui}-\\hat{r_{ui}})^2=\\\\ \\sum_{(u,i\\in K)}(r_{ui}-\\sum^k_{k=1}p_{u,k}q_{i,k})^2+\\lambda\\Arrowvert p_u \\Arrowvert^2+\\lambda\\Arrowvert q_i \\Arrowvert^2$ 其中后面两项是用来防止过拟合的正则化项,λ由实验获得。 可以利用随机梯度下降使以上函数最小化。 梯度下降对两个参数求偏导。得到下降公式,学习到学习速率$\\alpha$ 【具体代码和公式见书,这一细节为机器学习的细节】 评测我们同样通过离线实验评测LFM的性能。首先,我们在MovieLens数据集上用LFM计算出用户兴趣向量p和物品向量q,然后对于每个隐类找出权重最大的物品。表中展示了4个隐类中排名最高($q_{ik}$ 最大)的一些电影。结果表明,每一类的电影都是合理的,都代表了一类用户喜欢看的电影。从而说明LFM确实可以实现通过用户行为将物品聚类的功能。 其次,我们通过实验对比了LFM在TopN推荐中的性能。在LFM中,重要的参数有4个: 隐特征的个数 F ; 学习速率 alpha ; 正则化参数 lambda ; 负样本/正样本比例 ratio 。通过实验发现, ratio 参数对LFM的性能影响最大。因此,固定 F =100、 alpha =0.02、lambda =0.01,然后研究负样本/正样本比例 ratio 对推荐结果性能的影响。 随着负样本数目的增加, LFM 的准确率和召回率有明显提高。不过当ratio >10 以后,准确率和召回率基本就比较稳定了。同时,随着负样本数目的增加,覆盖率不断降低,而推荐结果的流行度不断增加,说明 ratio 参数控制了推荐算法发掘长尾的能力。 [在这里的实验,LFM性能优于userIF和itemIF,不过当数据集非常稀疏时, LFM 的性能会明显下降,甚至不如 UserCF 和 ItemCF 的性能。] 实例雅虎的研究人员以 CTR (点击通过率,点击数,点击到达率)作为优化目标,利用 LFM 来预测用户是否会单击一个链接。为此,他们将用户历史上对首页上链接的行为记录作为训练集。其中,如果用户 u 单击过链接 i ,那么就定义 (u, i) 是正样本,即 $r_{ui}$ = 1 。如果链接 i 展示给用户 u ,但用户 u 从来没有单击过,那么就定义 (u, i) 是负样本,即$r_{ui}$ = -1 。然后,雅虎的研究人员利用前文提到的 LFM 预测用户是否会单击链接:$r_{ui}=p_u^T\\cdot q_i$当然,雅虎的研究人员在上面的模型基础上进行了一些修改,利用了一些改进的 LFM 模型。 但是, LFM 模型在实际使用中有一个困难,那就是它很难实现实时的推荐。经典的 LFM 模型每次训练时都需要扫描所有的用户行为记录,这样才能计算出用户隐类向量( $p_u$ )和物品隐类向量( $q_i$ )。而且 LFM 的训练需要在用户行为记录上反复迭代才能获得比较好的性能。因此, LFM的每次训练都很耗时,一般在实际应用中只能每天训练一次,并且计算出所有用户的推荐结果。从而 LFM 模型不能因为用户行为的变化实时地调整推荐结果来满足用户最近的行为。 but新闻need实时性,首先,他们利用新闻链接的内容属性(关键词、类别等)得到链接 i 的内容特征向量 $y_i$ 。其次,他们会实时地收集用户对链接的行为,并且用这些数据得到链接 i 的隐特征向量$q_i$ 。然后,他们会利用如下公式预测用户 u 是否会单击链接 i $r_{ui}=x_u^T\\cdot y_i+p_u^T\\cdot q_i$ y:根据内容属性直接生产 x:用户u对内容特征k的兴趣程度,根据历史记录获得。x每天只需要训练一次。 p/q根据用户最近几小时行为训练LFM获得。 对于一个新加入的物品i,先通过x,y估计用户对物品的兴趣,几个小时后通过计算LFM得到更加准确的值。 【可参考雅虎论文】 比较和基于邻域的方法(比如 UserCF 、 ItemCF )相比 理论基础 LFM 具有比较好的理论基础,它是一种学习方法,通过优化一个设定的指标建立最优的模型。基于邻域的方法更多的是一种基于统计的方法,并没有学习过程。 离线计算的空间复杂度 基于邻域的方法需要维护一张离线的相关表。在离线计算相关表的过程中,如果用户 / 物品数很多,将会占据很大的内存。假设有 M 个用户和 N 个物品,在计算相关表的过程中,我们可能会获得一张比较稠密的临时相关表(尽管最终我们对每个物品只保留 K 个最相关的物品,但在中间计算过程中稠密的相关表是不可避免的) 在 Netflix Prize 中,因为用户数很庞大( 40 多万),很少有人使用 UserCF 算法(据说需要 30 GB 左右的内存),而 LFM 由于大量节省了训练过程中的内存(只需要 4 GB ),从而成为 Netflix Prize 中最流行的算法。 离线计算的时间复杂度 在一般情况下, LFM 的时间复杂度要稍微高于 UserCF 和 ItemCF ,这主要是因为该算法需要多次迭代。但总体上,这两种算法在时间复杂度上没有质的差别 在线实时推荐 UserCF 和 ItemCF 在线服务算法需要将相关表缓存在内存中,然后可以在线进行实时的预测。以 ItemCF 算法为例,一旦用户喜欢了新的物品,就可以通过查询内存中的相关表将和该物品相似的其他物品推荐给用户。因此,一旦用户有了新的行为,而且该行为被实时地记录到后台的数据库系统中,他的推荐列表就会发生变化。而从 LFM的预测公式可以看到, LFM 在给用户生成推荐列表时,需要计算用户对所有物品的兴趣权重,然后排名,返回权重最大的 N 个物品。那么,在物品数很多时,这一过程的时间复杂度非常高。 因此, LFM 不太适合用于物品数非常庞大的系统,如果要用,我们也需要一个比较快的算法给用户先计算一个比较小的候选列表,然后再用LFM 重新排名。另一方面, LFM 在生成一个用户推荐列表时速度太慢,因此不能在线实时计算,而需要离线将所有用户的推荐结果事先计算好存储在数据库中。因此, LFM 不能进行在线实时推荐,也就是说,当用户有了新的行为后,他的推荐列表不会发生变化。 推荐解释 ItemCF 算法支持很好的推荐解释,它可以利用用户的历史行为解释推荐结果。但 LFM 无法提供这样的解释,它计算出的隐类虽然在语义上确实代表了一类兴趣和物品,却很难用自然语言描述并生成解释展现给用户。","categories":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/categories/推荐系统/"}],"tags":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/tags/推荐系统/"},{"name":"读书笔记","slug":"读书笔记","permalink":"https://yongbosmart.github.io/tags/读书笔记/"}]},{"title":"基于用户的协同过滤算法","slug":"基于用户的协同过滤算法","date":"2019-02-15T16:00:00.000Z","updated":"2019-02-19T13:33:26.191Z","comments":true,"path":"2019/02/16/基于用户的协同过滤算法/","link":"","permalink":"https://yongbosmart.github.io/2019/02/16/基于用户的协同过滤算法/","excerpt":"","text":"协同过滤算法参考书本: 项亮, 推荐系统实践. 2012 基于邻域的算法是推荐系统中最基本的算法,不仅在学术界得到深入研究,而且在业界得到了广泛应用。 基于用户的协同过滤算法本质:根据用户之间兴趣相似性。 (1) 找到和目标用户兴趣相似的用户集合。(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。 给定用户 u 和用户 v ,令 N(u) 表示用户 u 曾经有过正反馈的物品集合,令 N(v)为用户 v 曾经有过正反馈的物品集合。那么,我们可以通过如下的 Jaccard 公式简单地计算 u 和 v 的兴趣相似度 $w_{uv}=\\frac{|N(u)\\cap N(v)|}{|N(u)\\cup N(v)|}$ 或者通过余弦相似度 $w_{uv}=\\frac{|N(u)\\cap N(v)|}{\\sqrt {|N(u)|| N(v)|}}$ but,利用余弦相似度时间复杂度较高,且很多用户没有相似点,即分子为0,因此可以先计算出分子不为0的用户。 因此可先建立关于物品到用户的倒排表,得到对物品产生过行为的用户列表。令稀疏矩阵C[u][v]=|N(u)$\\cap$N(v)|。假设用户u,v同时属于倒排表中K个物品的用户列表,则有C[u][v]=C[v][u]=K。最后得到用户之间不为0的C[u][v]。得到的矩阵为余弦相似度中的分子。 最后可以得到用户之间的兴趣相似度。 得到用户之间的兴趣相似度后, UserCF 算法会给用户推荐和他兴趣最相似的 K 个用户喜欢的物品。 以下公式度量了用户u对物体i的感兴趣程度。 $p(u,i)=\\sum_{v\\in S(u,K)\\cap N(i)}w_{uv}r_{vi}$ S包含与u兴趣最先进的K个用户。N(i)表示对物品i有过行为的用户集合。$w_{uv}$表示u与v的兴趣相似度。$r_{vi}$表示用户v对物品i的兴趣程度,这里因为是单一行为所以取1。 这里意思即是,求用户u对物品i的感兴趣程度: 对与用户u最相似的K个用户 1.对每一个用户v,得到uv相似度与v对物品感兴趣程度的积。 2.把k个用户的这个值加起来。 性能性能参数:准确率 召回率 覆盖度 流行度 性能评估:Random 算法每次都随机挑选 10 个用户没有产生过行为的物品推荐给当前用户, MostPopular 算法则按照物品的流行度给用户推荐他没有产生过行为的物品中最热门的 10 个物品。 这两种算法都是非个性化的推荐算法,但它们代表了两个极端。如表 2-5 所示, MostPopular 算法的准确率和召回率远远高于 Random 算法,但它的覆盖率非常低,结果都非常热门。可见, Random 算法的准确率和召回率很低,但覆盖度很高,结果平均流行度很低。 指标分析: 准确率和召回率 可以看到,推荐系统的精度指标(准确率和召回率)并不和参数 K 成线性关系。在 MovieLens 数据集中,选择 K=80 左右会获得比较高的准确率和召回率。因此选择合适的 K 对于获得高的推荐系统精度比较重要。当然,推荐结果的精度对 K 也不是特别敏感,只要选在一定的区域内,就可以获得不错的精度。 流行度 可以看到,在 3 个数据集上 K 越大则 UserCF 推荐结果就越热门。这是因为 K 决定了 UserCF 在给你做推荐时参考多少和你兴趣相似的其他用户的兴趣,那么如果 K 越大,参考的人越多,结果就越来越趋近于全局热门的物品。 覆盖率 可以看到,在 3 个数据集上, K 越大则 UserCF 推荐结果的覆盖率越低。覆盖率的降低是因为流行度的增加,随着流行度增加, UserCF 越来越倾向于推荐热门的物品,从而对长尾物品的推荐越来越少,因此造成了覆盖率的降低。 改进两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。 添加惩罚后的公式: $w_{uv}=\\frac{\\sum_{i\\in N(u)\\cap N(v)}\\frac{1}{\\log 1+|N(i)}}{\\sqrt {|N(u)|| N(v)|}}$","categories":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/categories/推荐系统/"}],"tags":[{"name":"推荐系统","slug":"推荐系统","permalink":"https://yongbosmart.github.io/tags/推荐系统/"}]},{"title":"用pkuseg分词并制作词云","slug":"用pkuseg分词并制作词云","date":"2019-02-08T16:00:00.000Z","updated":"2019-03-30T02:09:54.717Z","comments":true,"path":"2019/02/09/用pkuseg分词并制作词云/","link":"","permalink":"https://yongbosmart.github.io/2019/02/09/用pkuseg分词并制作词云/","excerpt":"","text":"用pkuseg分词并制作词云参考:https://www.jb51.net/article/146986.htm之前参考以上博客,利用jieba分词并制作了《格林德沃之罪》的词云。但是不太理想,这一次刚好看到了新发布的pkuseg,利用这个新的分词工具做词云。这一次加入了自己的词典 1234567891011盖勒特格林德沃邓布利多霍格沃茨霍格沃兹哈利波特哈里波特罗琳纽特蒂娜火蜥蜴 主要代码,参考见置顶网址 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960import re#正则表达式import pandas as pdfrom scipy.misc import imread # 这是一个处理图像的函数from wordcloud import WordCloud, STOPWORDS, ImageColorGeneratorimport matplotlib.pyplot as pltimport pkusegdef wordCount(segment_list): word_dict = {} for item2 in segment_list: if item2 not in word_dict: word_dict[item2] = 1 else: word_dict[item2] += 1 return word_dict#词频统计 代码参考:https://blog.csdn.net/fengjianc/article/details/78929121f = open(\"哈工大停用词表.txt\",encoding='utf-8')stopword_list = [line.strip() for line in f.readlines()]new_comments = []data = pd.read_csv(\"sqdw影评.csv\",engine='python',encoding ='utf-8')dataset = data.valuesList = []for k in dataset: List.append(k[1])seg=pkuseg.pkuseg(user_dict='mydict.txt')for comment in List: new_comment = re.sub(r'[^\\u4e00-\\u9fa5]', '', comment) #去标点 https://github.com/fxsjy/jieba/issues/528 new_comments.append(new_comment)word_list = [word for word in seg.cut(''.join(new_comments)) if word not in stopword_list]back_color = imread('1.png') # 解析该图片wc = WordCloud(background_color='black', # 背景颜色 max_words=2000, # 最大词数 mask=back_color, # 以该参数值作图绘制词云,这个参数不为空时,width和height会被忽略 collocations=False, #max_font_size=100, # 字体最大值 #min_font_size=20, # 字体最小值 font_path=\"C:/Windows/Fonts/SIMYOU.ttf\", # 解决显示口字型乱码问题,可进入C:/Windows/Fonts/目录更换字体 # random_state=42, # 为每个词返回一个PIL颜色 )wc.generate_from_text(\" \".join(word_list))#根据文本绘制云图# wc.generate_from_frequencies(wordCount(word_list)) #使用频率绘制云图print(wordCount(word_list))#得到分词各个词频# 基于彩色图像生成相应彩色image_colors = ImageColorGenerator(back_color)# 显示图片#plt.imshow(wc)# 关闭坐标轴#plt.axis('off')# 绘制词云plt.figure()# plt.imshow(wc, interpolation=\"bilinear\")plt.imshow(wc.recolor(color_func=image_colors))plt.axis('off')# 保存图片wc.to_file('pku2.png') 词云结果 十分满意的一个结果,利用文本制作词云 利用频率制作词云,可以看到停用词去除的不太完善,不如文本制作词云。 重新用结巴分词,加入词典,可以看到结果也是好了很多。 pkuseg分词结果一览(部分)12{'邓布利多': 84, '格林德': 3, '沃领': 1, '结婚证': 3, '伤害': 4, '对方': 4, '后来': 3, '嗅嗅': 14, '帮': 2, '偷到': 1, '准备': 5, '销毁': 1, '格林德沃': 86, '离婚': 3, '故事': 99, '强大': 5, '年轻': 12, '脱发': 1, '老': 5, '却': 26, '长': 13, '出': 33, '浓密': 2, '秀发': 1, '非常': 33, '值得': 5, '上班族': 1, '中': 52, '普及': 1, '克雷登斯': 7,'格林德沃法': 1, '力不及伏': 1, '魔但': 1,'普老年': 1}# 以上截取的部分可以看出还是有缺陷。","categories":[{"name":"数据分析","slug":"数据分析","permalink":"https://yongbosmart.github.io/categories/数据分析/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"数据分析","slug":"数据分析","permalink":"https://yongbosmart.github.io/tags/数据分析/"}]},{"title":"朴素贝叶斯分类算法","slug":"朴素贝叶斯","date":"2018-12-19T16:00:00.000Z","updated":"2018-12-20T14:11:56.257Z","comments":true,"path":"2018/12/20/朴素贝叶斯/","link":"","permalink":"https://yongbosmart.github.io/2018/12/20/朴素贝叶斯/","excerpt":"","text":"朴素贝叶斯分类算法定义朴素贝叶斯(naive Bayes)法是基于贝叶斯定理与特征条件独立假设的分类方法。 对于给定的训练数据集,首先基于特征条件独立假设 学习输入/输出联合概率分布;然后基于此模型,对于给定的输入x,利用贝叶斯定理求出后验概率的最大输出y。 原理分析基本原理它的本质是将实例分到后验概率最大的类中,等价于期望风险最小化。这里假设选择了0-1损失函数。 L(Y,f(X))=\\begin{cases}0, Y=f(X)【无损失,分类正确】\\\\1,Y\\neq f(X)【损失】\\end{cases}其中,f(X)为分类决策函数,期望风险函数为$R_{exp}(f)=E_p[L(Y),f(x))]$ 期望是对联合分布P(X,Y)取的,由此可得 R_{exp}(f)=E_x\\sum_{k=1}^K[L(c_k,f(X))]P(c_k|X)需要使期望风险最小化,化简后可得到 $f(x)=\\underset{y\\in Y}{\\operatorname{argmax}}P(y=c_k|X=x)$[后验概率] 【不过我之前一直理解的是,后验概率的意思是,求测试案例可能在哪个类别的概率比较大。似乎不严谨】 公式推理[这里考虑的是简单的离散的情况] 若求x在哪一个类别(假设类别是$c_1、c_2$等),则也就是等价于求后验概率 \\underset{c_{k}}{\\operatorname{argmax}}P(Y=c_k|X=x)又根据公式,有$P(Y|X)=\\frac{P(X,Y)}{P(X)}$和$P(X|Y)=\\frac{P(X,Y)}{P(Y)}$ 则P(Y|X)=\\frac{P(Y)P(X|Y)}{P(X)}【可认为后验概率=先验概率×调整因子】 先验概率分布 $P(Y=c_k),k=1,2,…,K$ 条件概率分布 P(X=x|Y=c_k)=P(X^1=x^1,…,X^n=x^n|Y=c_k),\\\\k=1,2,…,K因为是条件独立性假设,认为每个属性独立地对分类结果发生影响。 P(X=x|Y=c_k)=P(X^1=x^1,…,X^n=x^n|Y=c_k)=\\prod^n_{j=1}P(X^j=x^j|Y=c_k) 全概率公式 P(X=x)=\\sum_{k}P(Y=c_k)P(X=x|Y=c_k)\\\\= \\sum_kP(Y=c_k)\\prod_jP(X^j=x^j|Y=c_k) 因此,将以上公式代入,有 P(Y=c_k|X=x)=\\frac{P(Y=c_k)\\prod_jP(X^j=x^j|Y=c_k)}{\\sum_kP(Y=c_k)\\prod_jP(X^j=x^j|Y=c_k)}又因为,分母对所有$c_k$都是相同的,所以实际求 \\underset{c_{k}}{\\operatorname{argmax}}P(Y=c_k)\\prod_jP(X^j=x^j|Y=c_k)由于连乘操作易造成溢出,往往使用log计算,相关计算可参考“极大似然估计” 【具体计算过程、案例可参考《统计学习方法》】 拉普拉斯平滑若某个属性值在训练集中没有与某个类同时出现过,直接进行概率估计,由于连乘的存在,很可能出现零。 为了避免其他属性携带的信息被训练集中未出现的属性值“抹去”,在估计概率值时通常要进行平滑,常用拉普拉斯修正。具体来说,令N表示训练集D中可能的类别数,$N_i$表示第i个属性可能的取值数目,则有 \\hat{P}(Y=c_k)=\\frac{|D_k|+1}{|D|+N}\\hat{P}(X=x^j|Y=c_k)=\\frac{|D_{x^j,c_k}|+1}{|D_k|+N_i}用途用法朴素贝叶斯分类常用于文本分类等应用,对字词处理有比较大的作用。 在文档分类中,整个文档(如一封电子邮件)是实例,而电子邮件中的某些元素则构成特征。虽然电子邮件是一种会不断增加的文本,但我们同样也可以对新闻报道、用户留言、政府公文等其他任意类型的文本进行分类。我们可以观察文档中出现的词,并把每个词的出现或者不出现作为一个特征,这样得到的特征数目就会跟词汇表中的词目一样多。 【《机器学习实战》中有详细介绍如何用它进行垃圾邮件分类。】 朴素贝叶斯分类也可以通过调用sklearn直接使用。 优缺点优点:在数据较少的情况下仍然有效,可以处理多类别问题。缺点:对于输入数据的准备方式较为敏感。适用数据类型:标称型数据。 参考资料[1] P. 哈林顿 (Harrington and 李锐, 机器学习实战. 2013. [2] 李航, 统计学习方法. 2012. [3] 周志华, 机器学习. 2016.","categories":[{"name":"机器学习","slug":"机器学习","permalink":"https://yongbosmart.github.io/categories/机器学习/"}],"tags":[{"name":"机器学习","slug":"机器学习","permalink":"https://yongbosmart.github.io/tags/机器学习/"},{"name":"笔记","slug":"笔记","permalink":"https://yongbosmart.github.io/tags/笔记/"}]},{"title":"KNN算法","slug":"KNN算法","date":"2018-12-18T16:00:00.000Z","updated":"2018-12-20T06:36:11.806Z","comments":true,"path":"2018/12/19/KNN算法/","link":"","permalink":"https://yongbosmart.github.io/2018/12/19/KNN算法/","excerpt":"","text":"KNN算法概述KNN算法,即K近邻算法,是一个有标签的分类算法。(不同于K-means,是一个无标签的聚类算法) KNN的思想可以简单描述为,将测试数据映射到样本空间,测试数据周围是什么类别的居多,则测试数据本身可认为是什么类别的。这里的K代表的是依据测试数据周围最近的样本的数目。 比如常见的一个例子,如下图(图源于网络) k=3时,观察绿色周围最近的3个图案,则绿色图形归于红色一类。 k=5时,观察绿色周围最近的5个图案,则绿色图形归于蓝色一类。 具体阐释定义它的工作原理是:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。 一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。 最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。 主要流程K近邻主要是将测试数据与已知标签数据进行相似性比较,因此需要对数据进行规范化处理,以便比较。 关于特征空间距离的度量(两向量相似度的测量)有多种方式。常用的有欧式距离、曼哈顿距离等。 (以下代码来自《机器学习实战》,采用欧氏距离) 欧式距离公式 向量A、B的距离为: dist(A,B)=\\sqrt{\\sum^{n}_{i=1}(a_i-b_i})^21234567891011121314151617#inX - 用于要进行分类判别的数据(来自测试集) dataSet - 用于训练的数据(训练集)#labes - 分类标签 k - kNN算法参数,选择距离最小的k个点def classify0(inX,dataSet,labels,k): m=dataSet.shape[0] #返回dataset的行数,即已知数据集中所有点的数量 diffMat=np.tile(inX,(m,1))-dataSet #行向量方向上将inX复制m次 sqDiffMat=diffMat**2 #减完后,对每个数做平方 sqDistances=sqDiffMat.sum(axis=1) #平方后按行求和 distances=sqDistances**0.5 #开方算出欧式距离 sortedDistIndicies=distances.argsort()#对距离从小到大排序,并把索引输出 classCount={} #用于类别/次数的字典,key为类别,value为次数 for i in range(k): voteIlabel = labels[sortedDistIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 把计数最大的值所对应的标签返回出去 return sortedClassCount[0][0] 也可以使用sklearn实现,比较方便。 k值的选择k值的选择会对k近邻法的结果产生重大影响。 如果选择较小的k值,就相当于用较小的邻域中的训练实例进行预测,“学习”的近似误差(approximation error)会减小,只有与输入实例较近的(相似的)训练实例才会对预测结果起作用。但缺点是学习误差会增大。如果近邻点刚好是噪声,预测就会出错。换句话说,容易产生过拟合现象。 如果选择较大的k值,就相当于用较大邻域中的训练实例进行预测。其优点是可以减少学习的估计误差。但缺点是学习的近似误差会增大。这时与输入实例较远的不太相似的实例也会起预测作用,使预测出错。 优缺点优点:精度高、对异常值不敏感、无数据输入假定。缺点:计算复杂度高、空间复杂度高。适用数据范围:数值型和标称型。 参考资料[1] P. 哈林顿 (Harrington and 李锐, 机器学习实战. 2013. [2] 李航, 统计学习方法. 2012.","categories":[{"name":"机器学习","slug":"机器学习","permalink":"https://yongbosmart.github.io/categories/机器学习/"}],"tags":[{"name":"机器学习","slug":"机器学习","permalink":"https://yongbosmart.github.io/tags/机器学习/"},{"name":"笔记","slug":"笔记","permalink":"https://yongbosmart.github.io/tags/笔记/"}]},{"title":"机器学习基本概念","slug":"统计学习方法","date":"2018-10-05T16:00:00.000Z","updated":"2018-12-20T14:07:06.536Z","comments":true,"path":"2018/10/06/统计学习方法/","link":"","permalink":"https://yongbosmart.github.io/2018/10/06/统计学习方法/","excerpt":"","text":"统计学习方法概论阅读笔记统计学习:对象是数据,从数据出发,提取数据的特征,抽象出数据的模型,发现数据中知识,又回到对数据的分析预测中去。 方法:监督学习,非监督学习,半监督学习,强化学习。 监督学习概述输入空间,特征空间与输出空间。 在监督学习中,将输入与输出所有可能取值的集合分别称为输入空间与输出空间。输入空间与输出空间可以是同一个空间,也可以是不同的空间,但通常输出空间远远小于输出空间。 每一个具体的输入是一个实例,通常由特征向量表示。这时,所有特征向量存在的空间称为特征空间。特征空间的每一维对应于一个特征。(输入空间有时=特征空间,也可以看做是实例从输入空间映射到特征空间) 回归问题【输出变量为连续变量的预测问题】 分类问题 【输出变量为离散变量的预测问题】 标注问题【输入输出变量均为变量序列的预测问题】 联合概率分布[我的理解就是,x,y同时发生的概率,即输入为x输出为y同时出现的概率] 监督学习假设输入与输出的随机变量X和Y遵循联合概率分布P(X,Y)。但对学习系统来说,联合概率分布的具体定义是未知的,训练数据与测试数据被看做是依联合概率分布P(X,Y)独立同分布产生的。 统计学习假设数据存在一定的统计规律,X和Y具有联合概率分布的假设就是监督学习关于数据的基本假设。 维基百科【联合概率分布】 对离散随机变量而言,联合分布概率质量函数为Pr(X = x & Y = y),即 $P(X=x\\ and \\ Y=y)=P(Y=y|X=x)P(X=x)\\\\=P(X=x|Y=y)P(Y=y)$ 因为是概率分布函数,所以必须有 $\\sum_{x}\\sum_{y}P(X=x\\ and\\ Y=y)=1$ 类似地,对连续随机变量而言,联合分布概率密度函数为$f_{X,Y}(x, y)$,其中$f_{Y|X}$(y|x)和$f_{X|Y}$(x|y)分别代表X = x时Y的条件分布以及Y = y时X的条件分布;$f_X(x)$和$f_Y(y)$分别代表X和Y的边缘分布。 同样地,因为是概率分布函数,所以必须有 $\\int_{x}\\int_y\\ f_{X,Y}(x,y)dy\\ dx=1$ 对于两相互独立的事件P(X)及P(Y),任意x和y而言有离散随机变量$P(X=x\\ and Y=y)=P(X=x) \\cdot P(Y=y)$,或者有连续随机变量$P_{X,Y}(x,y)=p_X(x)\\cdot \\ p_Y(y)$。 假设空间监督学习的目的在于学习一个由输入到输出的映射,这一映射由模型来表示。换句话说,学习的目的在于找到最好的这样的模型。模型属于由输入空间到输出空间的集合,这个集合就是假设空间。 监督学习的模型可以是概率模型或非概率模型,由条件概率分布P(Y|X)或决策函数Y=f(X)表示,随具体学习方法而定。对具体的输入进行相应的输出预测时,写作P(y|x)或y=f(x) 则得到的预测结果$y_{N+1}=\\underset{y_{N+1}}{\\operatorname{argmax}} \\hat{P}(y_{N+1}|x_{N+1})$或$y_{N+1}=\\hat{f}(x_{N+1})$ 损失函数与风险函数统计学习的目标在于从假设空间中选取最优模型。 首先引入损失函数与风险函数的概念。损失函数度量模型一次预测的好坏,风险函数度量平均意义下模型预测的好坏。 损失函数有四种。损失函数L(Y,f(X)),一共有四种P23 又由于X,Y是随机变量,遵循联合分布P(X,Y),所以损失函数期望可以得到。为理论上模型f(X)关于联合分布P(X,Y)的平均意义下的损失,称为风险函数或期望损失。$R_{exp}(f)=E_p[L(Y,f(x))]=\\int_{x \\times y}L(y,f(x))P(x,y)dxdy$ 模型f(x)关于训练数据集的平均损失称为为经验风险或经验损失: $R_{emp}(f)=\\frac{1}{N}\\sum_{i=1}^{N}L(y_i,f(x_i))$ 期望风险是模型关于联合分布的期望损失,经验风险是模型关于训练样本集的平均损失,当样本容量N趋近无穷时,经验风险趋近于期望风险。 so一般使用经验风险估计期望风险。要矫正:经验风险最小化,结构风险最小化。 经验风险最小化,认为经验风险最小即为最优模型,转化为最优化问题,在样本容量足够大是,能保证有比较好的学习效果。eg:极大似然估计,即当模型为条件概率分布,损失函数是对数损失函数时,经验风险最小化就等价于极大似然估计。 样本容量很小时,经验风险最小化学习的效果未必很好,会产生“过拟合”现象。 结构风险最小化(SRM)是为了防止过拟合而提出来的策略,结构风险最小化等价于正则化。结构风险在经验风险上加上表示模型复杂度的正则化项或罚项。在假设空间、损失函数以及训练数据集确定的情况下,结构风险的定义是: $R_{srm}(f)=\\frac{1}{N}\\sum^{N}_{i=1}L(y_{i},f(x_{i}))+\\lambda J(f)$ 其中J(f)为模型的复杂度。是定义在空间$\\mathcal{F}$上的泛函。模型f越复杂,复杂度J(f)就越大;反正,模型f越简单,复杂度J(f)就越小。$\\lambda \\geq 0$是系数,用以权衡经验风险和模型复杂度。结构风险小的模型,需要经验风险与模型复杂度同时小。对未知的测试数据有较好的预测。 例如:贝叶斯估计中的最大后验概率估计。当模型是条件概率分布、损失函数是对数损失函数、模型复杂度由模型的先验概率表示时,结构风险最小化就等价于最大后验概率估计。 结构风险最小化认为结构风险最小的模型是最远模型-》最优化问题。 模型评估与模型选择训练误差与测试误差训练误差,针对训练数据集。测试误差,针对测试数据集。公式如下: $\\frac{1}{N}\\sum^{N}_{i=1}L(y_i,\\hat{f}(x_i))$ 过拟合与模型选择当假设空间含有不同复杂度(例如,不同的参数个数)的模型时,就要面临模型旋转的问题。 【在多项式函数拟合中,随着多项式次数(模型复杂度)的增加,训练误差会减小,直至趋向于0,但是测试误差却不如此,它会随着多项式次数(模型复杂度)的增加先减小而后增大(因为出现过拟合现象)。最终目的是使测试误差达到最小。】 正则化与交叉验证模型选择的典型方法,正则化。【比如加个范数】 选择模型:1.简单 2.很好地解释已知数据。 交叉验证:切分数据,重复使用,进行验证 生成模型与判别模型监督学习方法可以分为生成方法和判别方法。所学到的模型为生成模型和判别模型。 生成方法,数据学习联合概率分布P(X,Y),然后求出P(Y|X) 生成模型: $P(Y|X)=\\frac{P(X,Y)}{P(X)}$ 模型表示了给定输入X产生输出Y的生成关系。典型的生成模型有:朴素贝叶斯法和隐马尔克夫模型。 判别方法,数据直接学习决策函数f(X)或者P(Y|X)作为预测的模型。关系的是对给定的输入X,应该预测怎样的输出Y典型的判别模型包括:感知机,决策树,支持向量机,条件随机场,最大熵模型等。 分类模型","categories":[{"name":"机器学习","slug":"机器学习","permalink":"https://yongbosmart.github.io/categories/机器学习/"}],"tags":[{"name":"统计学习方法","slug":"统计学习方法","permalink":"https://yongbosmart.github.io/tags/统计学习方法/"},{"name":"阅读笔记","slug":"阅读笔记","permalink":"https://yongbosmart.github.io/tags/阅读笔记/"}]},{"title":"进制转换&字符串","slug":"进制转换&字符串","date":"2018-08-17T16:00:00.000Z","updated":"2018-08-24T08:51:07.566Z","comments":true,"path":"2018/08/18/进制转换&字符串/","link":"","permalink":"https://yongbosmart.github.io/2018/08/18/进制转换&字符串/","excerpt":"","text":"(节选自《算法笔记》) 进制转换P进制数转十进制数y123456int y=0,product=1;while(x!=0){ y=y+(x%10)*product;//y是结果,x%10为了得到个位数 x=x/10; product=prodcut*P;//product的值会分别变成1,p,p^2,p^3,p^4…} 十进制数y转换成Q进制数z12345int z[40],num=0;do{ z[num++]=y%Q;//除基取余 y=y/Q;}while(y!=0); 这样z数组从高位z[num-1]到低位z[0]即为Q进制z,进制转换完成。 字符串回文串回文串是常常出现的一种考题。 对于回文串的处理:1.根据对称性;2.利用for循环,从后向前遍历(逻辑性比较简单) 思路1. 假设字符串str的下标从0开始,由于“回文串”是正读和反读都一样呃字符串,因此只需要遍历字符串的前一半(注意:不需要取到i==len/2),如果出现字符str[i]不等于其对称位置str[len-1-i],就说明这个字符串不是“回文串”;如果前一半的所有字符str[i]都等于对称位置 str[len-1-i],则说明这个字符串是回文串。 1234567891011121314#include <cstdio>#include <cstring>const int maxn=256;//判断字符串str是否是“回文串”bool judge(char str[]){ int len =strlen(str);//字符串长度 for(int i=0;i<len/2;i++){//i枚举字符串的前一半 if(str[i]!=str[len-1-i]){ return false; } } return true;} 例题 百练 2017大数据研究中心夏令营上机考试 B:单词倒排 & PAT B1009 说反话 比较简单的一道题,输入输出格式大概如下: 样例输入 1I am a student 样例输出 1student a am I 总之要求是倒序输出。 主要思路,get到一串字符串(包括换行符) 我开始的思路:用string getline(cin,str); 然后根据空格截取字符串,然后倒序输出字符串。 这里最后多输出空格可能会导致格式错误 在网上看到另一个代码感觉也很机智了,直接输入字符存储。(https://blog.csdn.net/vivaespana51/article/details/79742003): 123456789101112131415#include <iostream>#include <string.h>using namespace std;int main() { char t[100][100] = {\" \"}; int n = 0; while (cin >> t[n]) { n++; } for (int i = n - 1; i >= 0; i--)//逆序输出字符串 printf(\"%s \", t[i]); cout << endl; return 0;}","categories":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/categories/日常练习/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"PKU2018计算机机试 & 日期天数计算","slug":"日期处理","date":"2018-08-16T16:00:00.000Z","updated":"2018-08-18T00:22:40.263Z","comments":true,"path":"2018/08/17/日期处理/","link":"","permalink":"https://yongbosmart.github.io/2018/08/17/日期处理/","excerpt":"","text":"题目描述百练A:计算两个日期之间的天数 总时间限制: 1000ms 内存限制: 65536kB 描述 给定两个日期,计算相差的天数。比如2010-1-1和2010-1-3相差2天。 输入 共两行: 第一行包含三个整数startYear,startMonth,startDay,分别是起始年、月、日。 第二行包含三个整数endYear,endMonth,endDay,分别是结束年、月、日。 相邻两个整数之间用单个空格隔开。 年份范围在1~3000。保证日期正确且结束日期不早于起始日期。 输出 输出一个整数,即是两个日期相差的天数。 样例输入 122018 1 12019 1 1 样例输出 366 提示 闰年被定义为能被4整除的年份,但是能被100整除而不能被400整除的年是例外,它们不是闰年。闰年的2月份有29天。 codeup相同题目日期差值 题目描述 有两个日期,求两个日期之间的天数,如果两个日期是连续的我们规定他们之间的天数为两天。 输入 有多组数据,每组数据有两行,分别表示两个日期,形式为YYYYMMDD 输出 每组数据输出一行,即日期差值 样例输入 122013010120130105 样例输出 15 题目分析思路:在开始的时候,我考虑了各种情况,比如前一年为闰年,后一年为平年,比如月份区间包含不包含2月。总之脑子都快炸了也没想出所有的情况,以至于一直AC不了,心态崩溃。 实际上,这个题一个简单的思路:天数相加,知道加到目的天数为止。 //notice,这里用字符串处理截取超级麻烦,不如用int求余算的比较快。 两个坑: codeup那个需要比较日期大小 codeup那个还需要注意是多组数据。(如何输入多组数据) 如何方便快捷地实现,如何利用数组。 AC代码(codeup版)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869#include <stdio.h>#include <iostream>#include <cstring>#include <stdlib.h>using namespace std;bool issun(int year) { if ((year % 100 != 0 &&year % 4 == 0)||year%400==0) {//判断 闰年 的方法!! return true; } else{ return false; }}int main(){ int a1, a2; int year1, year2, mon1, mon2, day1, day2; while(scanf(\"%d%d\",&a1,&a2)!=EOF){ //没有强调,需要保证前者早于后者。 if (a1 > a2) { int tmp = a1; a1 = a2; a2 = tmp; } day1 = a1 % 100; a1 = a1 / 100; mon1 = a1 % 100; a1 = a1 / 100; year1 = a1 % 10000; day2 = a2 % 100; a2 = a2 / 100; mon2 = a2 % 100; a2 = a2 / 100; year2 = a2 % 10000; int days = 1; int mon[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; int mons[13] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 }; while (!(year1 == year2&&mon1 == mon2&&day1 == day2)) { day1++; if (issun(year1)) { if (day1 == mons[mon1] + 1) { day1 = 1; mon1++; } } else { if (day1 == mon[mon1] + 1) { day1 = 1; mon1++; } } if (mon1 == 13) { mon1 = 1; year1++; } days++; } printf(\"%d\\n\", days); } return 0;}","categories":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/categories/日常练习/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"networking&百练1287","slug":"networking","date":"2018-08-10T16:00:00.000Z","updated":"2018-08-11T08:19:09.977Z","comments":true,"path":"2018/08/11/networking/","link":"","permalink":"https://yongbosmart.github.io/2018/08/11/networking/","excerpt":"","text":"G:Networking软工夏令营上机题目G & 百练1287 题目描述 总时间限制: 1000ms 内存限制: 65536kB 描述 You are assigned to design network connections between certain points in a wide area. You are given a set of points in the area, and a set of possible routes for the cables that may connect pairs of points. For each possible route between two points, you are given the length of the cable that is needed to connect the points over that route. Note that there may exist many possible routes between two given points. It is assumed that the given possible routes connect (directly or indirectly) each two points in the area. Your task is to design the network for the area, so that there is a connection (direct or indirect) between every two points (i.e., all the points are interconnected, but not necessarily by a direct cable), and that the total length of the used cable is minimal. 输入 The input file consists of a number of data sets. Each data set defines one required network. The first line of the set contains two integers: the first defines the number P of the given points, and the second the number R of given routes between the points. The following R lines define the given routes between the points, each giving three integer numbers: the first two numbers identify the points, and the third gives the length of the route. The numbers are separated with white spaces. A data set giving only one number P=0 denotes the end of the input. The data sets are separated with an empty line. The maximal number of points is 50. The maximal length of a given route is 100. The number of possible routes is unlimited. The nodes are identified with integers between 1 and P (inclusive). The routes between two points i and j may be given as i j or as j i. 输出 For each data set, print one number on a separate line that gives the total length of the cable used for the entire designed network. 样例输入 12345678910111213141516171819202122232425261 02 31 2 372 1 171 2 683 71 2 192 3 113 1 71 3 52 3 893 1 911 2 325 71 2 52 3 72 4 84 5 113 5 101 5 64 2 120 样例输出 12340171626 主要思路1.构造无向图。(这里利用矩阵的方法) 2.找出无向图中的最小生成树。 这里使用Kruskal算法,利用并查集写。 (就是做这道题时发现我对Kruskal的理解有一丢丢的失误= =,可见还是码出来最重要) 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130//============================================================================// Name : ex3.cpp// Author : yongbo// Version :// Copyright : Your copyright notice// Description : Hello World in C++, Ansi-style//============================================================================#include<stdio.h>#include<queue>#include <iostream>#include <cstdio>#include <algorithm>#include <cmath>#include <cstring>using namespace std;int point[55];int findfather(int a){ if(point[a]!=a){ return findfather(point[a]); }else{ return a; }}void unionab(int a,int b){ if(findfather(a)!=findfather(b)){ point[a]=b; }}bool issame(int a,int b){ if(findfather(a)==findfather(b)) return true; else return false;}int main(){ int m; queue<int> answer; scanf(\"%d\",&m); while(m!=0){ int n; scanf(\"%d\",&n); if(n==0){ printf(\"%d\",0); printf(\"%s\",\"\\n\");// string s;// getline(cin,s); scanf(\"%d\",&m); continue; }else{ for(int i=0;i<55;i++){ point[i]=i; } int line[n][3]; for(int i=0;i<n;i++){ for(int j=0;j<3;j++){ scanf(\"%d\",&line[i][j]); } } int graph[m+1][m+1]; int ans=0; int zuxian=-1; memset(graph,-1,sizeof(graph)); for(int i=0;i<n;i++){ int x=line[i][0],y=line[i][1],len=line[i][2]; if(graph[x][y]==-1||graph[x][y]>len){ graph[x][y]=len; graph[y][x]=len; } } int edge=0; for(int i=0;i<n;i++){ int minx=0,miny=0;// int min=105; for(int i=1;i<=m;i++){ for(int j=i+1;j<=m;j++){ //每次找最小的,找到后可以赋一个较大值 if(graph[i][j]<min&&graph[i][j]!=-1){ minx=i; miny=j; min=graph[i][j]; } } } if(!(minx==0&&miny==0)){ if(!issame(minx,miny)){//怎么保证单向呢 unionab(findfather(minx),findfather(miny)); ans+=min; edge++; } graph[minx][miny]=105; graph[miny][minx]=105; } answer.push(ans); }// string s;// getline(cin,s); scanf(\"%d\",&m); } while(!answer.empty()){ printf(\"%d%s\",answer.front(),\"\\n\"); answer.pop(); }// int m; return 0;}","categories":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/categories/算法/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"信息安全学习总结&复习纲要","slug":"信息安全","date":"2018-07-21T16:00:00.000Z","updated":"2019-06-14T07:51:31.398Z","comments":true,"path":"2018/07/22/信息安全/","link":"","permalink":"https://yongbosmart.github.io/2018/07/22/信息安全/","excerpt":"","text":"信息安全学习总结&复习纲要by yongbo 基本概念 密码算法和协议又可以分为4个主要的领域: 对称加密:用于加密任意大小的数据块或数据流的内容,包括消息、文件、加密密钥、口令。 非对称加密:用户加密小的数据库,如原来加密密钥或者hash值。 数据完整性算法:用于保护数据块(如一条消息)的内容免于修改。 认证协议:有许多基于密码算法的认证方案,用于认证实体的真实性。 CIA三元组,数据、信息和计算服务的基本安全目标: 保密性:对信息的访问和公开进行授权限制,包括保护个人隐私和秘密信息。保密性缺失的定义是信息的非授权泄露。 完整性:防止对信息的不恰当修改或破坏,包括确保信息的不可否认性和真实性。完整性缺失的定义是对信息的非授权修改和毁坏。 可用性:确保对信息的及时和可靠的访问和使用。可用性的缺失是对信息和信息系统访问和使用的中断。 (真实性,可追溯性(追溯到负有安全责任的一方)) passive attacks(被动攻击) 对传输进行窃听和检测。攻击者的目标是获得传输的信息。(信息内容的泄露,流量分析)(难以检测可防止) active attacks(主动攻击) 对数据流进行修改或伪造数据流。(伪装,重播(再次发送),信息修改,拒绝服务)(难以预防可检测) 访问控制:限制和控制那些通过通信连接对主机和应用进行访问的能力。 数据保密性:防止传输的数据遭到被动攻击。 数据完整性:与主动攻击有关 不可否认性:接收方能证明消息是由发送方发出,发送方也能证明消息是被接收方收到。 传统加密技术 对称加密:至今仍是最为广泛的加密模型,代表:DES和AES。 采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。 5个基本模型 plaintext 明文:原始可理解的消息或数据,是算法的输入。 加密算法:加密算法对明文进行各种替代和变换。 密钥:加密算法的输入。密钥独立于明文和算法。算法根据所用的特定密钥而产生不同的输出。算法所用的确切代替和变换也依靠密钥。 密文:作为算法的输出,看起来完全随机而杂乱的消息,依赖于明文和密钥。 解密算法:本质上是加密算法的逆运算。输入密文和密钥,输出原始明文。 •plaintext - the original message •ciphertext - the coded message •cipher - algorithm for transforming plaintext to ciphertext •key - info used in cipher known only to sender/receiver •encipher (encrypt)(加密) - converting plaintext to ciphertext •decipher (decrypt) (解密)- recovering ciphertext from plaintext •cryptography (密码学)- study of encryption principles/methods •cryptanalysis (code-breaking) - the study of principles/ methods of deciphering ciphertext without knowing key •cryptology - the field of both cryptography and cryptanalysis $Y=E(K,X), X=D(K,Y)$ 对加密信息的攻击类型 •ciphertext only (唯密文攻击,最容易防范) •only know algorithm / ciphertext, statistical, can identify plaintext •known plaintext (已知明文) •know/suspect plaintext & ciphertext to attack cipher •chosen plaintext (选择明文) •select plaintext and obtain ciphertext to attack cipher •chosen ciphertext •select ciphertext and obtain plaintext to attack cipher •chosen text •select either plaintext or ciphertext to en/decrypt to attack cipher 加密算法满足以下两点,可以认为是计算上安全的: 破译密码的代价超出密文信息的价值 破译密码的时间超出密文信息的有效期 代替技术将明文字母替换成其他字母、数字和符合的方法。 Caesar密码对字母表中的每个字母,用它之后的第三个字母来代替。eg:a->d 可以扩展到移n位,有25种可能的密钥。公式(将字母对应成数字)$C=E(k,p)=(p+k)\\mod 26\\\\ p=D(k,C)=(C-k)\\mod26$ caesar密码的三个重要特征使得可以采用穷举攻击分析方法: 1.已知加密和解密算法 2.需测试的密钥只有25个 3.明文所用的语言是已知的,且其意义易于识别。 Monoalphabetic Cipher 单表替代密码替代密码是指先建立一个替换表,加密时将需要加密的明文依次通过查表,替换为相应的字符,明文字符被逐个替换后,生成无任何意义的字符串,即密文,替代密码的密钥就是其替换表 。 (单表有一个固定的替换表) 将得到的密文上26个字母的任意置换。(一个具有n个元素的集合有n!个置换。)密钥数目大大增加(eg,26!>4×$10^{26}$) 但是仍不安全,问题是语言特征规律。 •human languages are redundant •letters are not equally commonly used 单表代替密码,带有原始字母使用频率的一些统计学特征, 有两种方法可以减少代替密码里明文结构在密文中的残留度: 1.对明文中的多个字母一起加密。 2.采用多表代替密码 playfair 密码(多字母代替密码)最著名的多字母代替密码,它把明文中的双字母音节作为一个单元并将其转换成密文的“双字母音节”。 基于一个由密钥词构成的5×5字母矩阵。 本例使用的密钥词是monarchy。填充矩阵的方法是:首先将密钥词(去掉重复字母)从左向右,从上到下填在矩阵格子里。再将剩余的字母(除去密钥词里的字母)一次填在矩阵的格子里。I/J认为是一个字母。 (如果一种语言字母超过25个,可以去掉使用频率最少的一个。如,法语一般去掉w或k,德语则是把i和j合起来当成一个字母看待。英语中z使用最少,可以去掉它。 ) (1)如果该字母对的两个字母是相同的,那么在它们之间加一个填充字母,比如x。balloon$\\to$ba lx lo on (2)落在矩阵同一行的明文字母对中的字母由其右边的字母来代替。每行最右边的一个字母就用该列最左边的字母来代替。ar->RM (3) 落在矩阵同一列的明文字母对中的字母由其下面的字母来代替,每行最下面的一个字母,就该用该列最上面的字母来代替。比如mu$\\to$CM (4) 其他的每组明文字母对中的字母,按如下方式代替,该字母所在行为(该字母的)密文所在行,另一个字母所在列为(该字母的)密文所在列。hs$\\to$BP,ea$\\to$IM/JM (5)•如果最后剩下一个单个的不成对的字母,不加密 b\u0002 有26×26=675个字母对 认为比较安全,但仍然相对容易攻破。因为它的密文仍然完好地保留了明文语言的大部分结构特征。 多表代替密码(Vigenere密码)对简单单表代替的改进方法是在明文消息中采用不同的单表代替。这种方法一般称之为多表代替密码。所有这些方法都有以下的共同特征: (1)采用相关的单表代替规则集。 (2)密钥决定给定变换的具体规则。 最简单最著名的多表加密算法:Vigenere密码。在单一恺撒密码的基础上扩展出多表密码 如何使用字母表加解密,如果知道密钥长度,能否进行解密 TO BE OR NOT TO BE THAT IS THE QUESTION 当选定RELATIONS作为密钥时,加密过程是:明文一个字母为T,第一个密钥字母为R,因此可以找到在R行中代替T的为K,依此类推,得出对应关系如下: 密钥:RELAT IONSR ELATI ONSRE LATIO NSREL 明文:TOBEO RNOTT OBETH ATIST HEQUE STION 密文:KSMEH ZBBLK SMEMP OGAJX SEJCS FLZSY 又如: 在维吉尼亚(Vigenère)的密码中,发件人和收件人必须使用同一个关键词或者同一文字章节,即密钥。这个关键词或文字章节中的字母告诉他们怎么样才能前后改变字母的位置来获得该段信息中的每个字母的正确对应位置。比如如果关键字“BIG”被使用了,发件人将把信息按三个字母的顺序排列。第一个三字母单词的第一个字母将应当向前移动一个位置(因为B是排在A后面的字母),第二个字母需要向前移动8位(I是A后面第8个字母),而第三个字母需要向前移动6位(G是A后面第6个字母)。然后,文字就可以按下面的顺序来进行加密了: 未加密文字:THE BUTCHER THE BAKER AND THE CANDLESTICK MAKER。(屠夫、面包师和蜡烛匠)。 关键密钥:BIG BIGBIGB IGB IGBIG BIG BIG BIGBIGBIGBI GBIGB 加密文字:UPK CCZDPKS BNF JGLMX BVJ UPK DITETKTBODS SBSKS 密钥 如果知道“BIG”就是密钥,收件人就可以很容易地通过相应的位置改变字母位置,从而译出经过加密的文字。 每个明文都有多个密文字母,因此,字母的频率不太明显 但不是完全失去了 1)可以通过字母频率来确定使用的方法:单表法还是Vigenere密码 2)有时可以推断出key大小(破译能否取得进展将取决于能否判定密钥词的长度)如果两个相同的明文序列之间的距离是密钥词长度的整数倍,那么产生的密文序列也是相同的。 一次一密不可被攻破的,使用与消息一样长且无重复的随机密钥来加密消息。加密一次后丢弃不用。它产生的随机输出与明文没有任何关系,因为密文不包含明文的任何信息,所以无法可破。 (无条件安全,不可破译) 两大难点:a.产生大规模的随机密钥有实现困难。b.更令人担忧的是密钥的分配和保护。 一次一密是唯一的具有完善保密的密码体制。 其它(置换密码,轮转机,隐写术)置换密码是一种通过一定规则改变字符串中字符的顺序从而实现加密的密码算法。常见的是将明文字符串按照n个一行形成矩阵,然后再按列读出 。(可以双重,多重置换) 块密码和数据加密标准(DES)流密码和块密码(分组密码) 流密码每次加密数据流的一位或一个字节。分组密码是将一个明文分组作为整体加密,通常得到与明文等长的密文分组,一般分组大小是64位或128位(高级加密标准)。 分组越长意味着安全性越高。 现代块密码 用途广泛,包括保密和身份验证,主要是DES(分组密码设计准则) 一种思想(加解密过程):A$\\oplus B =C$ $C\\oplus B=A$(根据异或的性质) •LS-1: cycled left shift 1 bit •LS-2: cycled left shift 2 bit •IP = (n2, n6 , n3 , n1 , n4 , n8 , n5 , n7 )n1放在了第四个位置 1 2 3 4 5 6 7 8 •$IP^{-1}$=(n4, n1 , n3 , n5 , n7 , n2 , n8 , n6 )n4就放在第一个位置 1 2 3 4 5 6 7 8 (n6放在第8个位置,n1放在第二个位置,与上面对应) •$IP^{-1}$( IP( X ) )=X 代替:明文被密文代替 置换:明文元素序列置换。 扩散:明文 和 密文之间统计关系变得复杂 混淆: 密文和 加密密钥之间 统计关系变复杂。 DES算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位(实际用到了56位,第8、16、24、32、40、48、56、64位是校验位, 使得每个密钥都有奇数个1) DES是以64bit明文为一个单位来进行加密的,这个64bit的单位称为分组。 上一轮如何加密得到下一轮64bit,函数怎么来的,加完密如何解密。不一定考怎么设计,但是最关键结构!! Feistel 密码(DES基本结构)基本公式:$f_{k}=(L\\oplus F(R,SK),R)$ L:左四位输入;R:右四位输入;SK:8位子密钥;F:映射函数 本质上实现了输入和输出的非线性 在这里,加密的各个步骤称为轮,整个加密步骤就是进行若干次轮的循环。(多轮网络,难以破解)DES是一种16轮循环的Feistel网络。 Feistel 密码的流程,也是DES的一次循环过程。 输入分为左边和右边各32bit,子密钥是本轮加密使用的密钥,子密钥是一个局部密钥,仅在一轮中使用。 轮函数根据右侧和子密钥生成对左侧的加密序列,它是密码系统的核心,将轮函数的输出(用于加密的序列),与左侧进行xor,从而得到加密的效果。而输入的右侧原样输出。 这样右侧没有进行加密,因此需要用不同的子密钥对一轮进行处理,并在两轮处理之间,将左侧和右侧数据对调,最后一轮结束之后不需要对调。(课本得到LE16,Re16之后,进行对调后再输出了LE17,RE17,但是在解密时依旧用的是LE16,RE16) 解密:只要按照相反的顺序使用子密钥就可以了。(用相同的子密钥运行两次Feistel就能将数据还原(左右侧不对调的情况下),根据$\\oplus$的相关性质) 特点:feistel轮数可以任意叠加;轮函数使用任何函数都可以正确解密;加密和解密可以用完全相同的结构实现。 DES加密 事实上,明文处理经历了3个阶段: 64位明文经过初始置换Ip被重新排列。然后进入16轮相同函数的作用,每轮作用都有置换和代替。最后一轮输出有64位,左半部分和右半部分对调后输出。最后预输出再被与初始置换IP互逆的置换$IP^{-1}$作用产生64位密文。除了初始与末尾的置换,DES结构与Feistel结构相同。 图中右半部分给出了使用56位密钥的过程,密钥经过一个置换后,在经过循环左移和一个置换分别的都各轮的子密钥$K_{i}$用于各轮的迭代。每轮置换函数一样,但由于循环移位使子密钥不同。 雪崩效应:明文或密钥的微小改变将对密文产生很大的影响是任何加密算法需要的一个好性质。明文密钥某一位变化$\\to$密文很多位变化 DES的强度: 56位密钥一共有$2^{56}种情况,7.2\\times10^{16}$ DES过程初始置换IP及其逆置换$IP^{-1}$ IP置换就是将一个分组的64bits按照IP表按位重排。看第一个ip表,表示的意思是将64位明文P的第58位放在第1位,把原来的第50位调到第2位。$IP^{-1}$正好将IP调整的位置调回原位 函数F(·,·)的细节 E置换-》XOR-》S盒-》P置换 扩展置换E与P置换 扩展置换的功能是把$R_{i-1}$传来的32bits扩展为48bits,以便与48bits的子密钥$K_{i}$进行异或运算。 P盒置换是将S盒输出的32位结果又来一次置换。【置换规则和IP相同】 S盒变换 S盒变换又称压缩替换,通过S盒将48位输入变换成32位输出。是DES中唯一的非线性结构。DES算法中用了8个结构相似的S盒,每个S盒能够将6位的输入变成4位的输出。8个则将48位-》32位。 【6位数字,取第一位和最后一位作为行号,取中间四位作为列号,找到S盒中对应的<16的数,转化为4位2进制】 轮密钥是怎样生成的初始密钥:56bit+8bit校验 置换PC-1:将初始密钥中不在8的整数倍位置上的数,置换到56位上,置换规则同IP等相似,有置换表。 置换PC-2:一个压缩置换,将56位的输入压缩变换成48位。有置换表。 附:3DES 加密$C=E_{k1}(D_{k2}(E_{k1}(P)))$ 解密$P=D_{k1}(E_{k2}(D_{k1}(C)))$ 分组密码的模式模式分组密码只能加密固定长度的明文。如果需要加密任意长度的明文,就需要对分组密码进行迭代,而分组密码的迭代方法就称为分组密码的“模式”。 ECB,CBC,CFB,OFB,CTR 明文分组:作为加密对象的明文,=分组密码算法的分组长度 密文分组:分组密码算法将明文分组加密之后所产生的密文。 ECBECB:电子密码本。直接分块加密。在ECB模式中,将明文分组加密之后的结果将直接成为密文分组。 快速高效,可以同时处理。 信息的重复会显示出来 不安全:攻击者可以改变密文分组的顺序,当接受者对密文进行解密时,由于密文分组的顺序变了,解密出的明文顺序也会不对。这时,攻击者无需破译密码,既可以操纵明文。除此之外,攻击者还可以将密文分组进行删除、复制等操作。(if有消息认证码可以检测,but用其它模式不会出现这样的问题) CBC模式(密文分组链接模式)密文分组像链条一样相互连接在一起。 首先将明文分组与前一个密文分组进行XOR运算,然后再进行加密。 前面的块作为后面的块的输入——连在一起 • 需要一个初始的64位数据(向量)即IV 一般来说,每次加密时都会随机产生一个不同的比特序列来作为初始化向量。 与ECB区别:ECB模式只进行了加密,而CBC模式则在加密前进行了一次XOR 特点: 安全性较好,加密过程是串行的,无法被并行化。解密可以并行化。 每个密文块都依赖于所有的消息块,因此,消息中的更改会影响更改后的所有密文块以及原始块。 但是,如果IV是明文发送的,攻击者可以更改第一个块的位,并更改IV以补偿,因此IV必须是一个固定值(如EFTPOS),或者必须在消息其余部分之前以ECB模式加密发送 【???即可对初始化向量进行攻击】 在消息的末尾,通过填充已知的非数据值(如null)或使用填充大小为pad size的pad last块来处理可能的最后短块 如。[b1 b2 b3 0 0 0 0 0 0 5] <- 3数据字节,然后5字节pad+计数 数论和有限域b|a: b整除a,b是a的因子。 a|b,b|c,则a|c。 欧几里得算法 Euclid & GCD:arrow_forward: 两个整数称为互素的,如果它们唯一的正整数公因子为1. ▶️ gcd(a,b):a,b的最大公因子。gcd(0,0)=0. gcd(a,b)=max[k,其中k|a且k|b] 所求最大公因子为正数,一般来说gcd(a,b)=gcd(|a|,|b|),同样,因为0可被所有非零整数整除,所以gcd(a,0)=|a|。 如果gcd(a,b)=1,那么a和b互素 ▶️ 【基于java】: a mod b(仅正数)和a%b(结果有正有负)相差不多,均表示求余数。$a\\equiv b \\mod c$ (a,b模c同余) ▶️ 欧几里得算法 d= GCD(a,b) = GCD(b, a mod b) (b ,a和b的余数) 1234567891011121314151617A=a, B=bwhile B>0 R = A mod B A = B, B = Rreturn A (B=0,返回A)Example GCD(1970,1066)1970 = 1 x 1066 + 904 gcd(1066, 904)1066 = 1 x 904 + 162 gcd(904, 162)904 = 5 x 162 + 94 gcd(162, 94)162 = 1 x 94 + 68 gcd(94, 68)94 = 1 x 68 + 26 gcd(68, 26)68 = 2 x 26 + 16 gcd(26, 16)26 = 1 x 16 + 10 gcd(16, 10)16 = 1 x 10 + 6 gcd(10, 6)10 = 1 x 6 + 4 gcd(6, 4)6 = 1 x 4 + 2 gcd(4, 2)4 = 2 x 2 + 0 gcd(2, 0)[一直到余数=0,这里结果为2] 模运算:arrow_forward:a模n:a除以n所得的余数。 $11\\mod 7=4, \\quad-11\\mod 7=3; \\\\ 73\\equiv 4(mod23) \\to 73-4=23\\times3 \\\\ 21\\equiv -9(\\mod 10) \\to 21-(-9)=10\\times 3 $ 如果$a\\equiv 0(\\mod n)$,则n|a。 :arrow_forward:(a+b) mod n = [a mod n + b mod n] mod n (a-b) mod n = [a mod n - b mod n] mod n (a×b) mod n = [a mod n × b mod n] mod n 乘法逆a的乘法逆元:与a相乘得到单位元的值。 群、环、域:arrow_forward:群,二元运算的集合。满足1封闭性,2结合律,3单位元,4逆元。如果群的元素个数有限,则成为有限群。群的阶就等于群中元素的个数。否则,称为该群为无限群。 如果群满足5交换律,称为交换群,即阿贝尔群。 求幂运算为重复运用群中的运算。 :arrow_forward:环{R,+,×}一个有两个二元运算的集合。a封闭率b结合律c分配率。 乘法交d换律和乘法e单位元。f无零因子:如果R中有元素a、b,且ab=0,则必有a=0或b=0。 :arrow_forward:域,整环,满足12345+abcdef, 存在乘法逆元,$aa^{-1}=e$ 有限域$GF(大素数),GF(2^{n})$ 有限域的阶(元素的个数)必须是一个素数的幂$p^{n}$,n为正整数。阶为$p^{n}$的有限域一把记为$GF(p^{n})$,n=1,GF(p),与n>1时有着不同的结构。 Galois fields(伽罗瓦域) –GF(p) ( for n=1) –$–GF(2^n) (for \\quad p=2 )$ (for p=2 ) GF(p)是整数集合{0,1, … , p-1} (整数集合$Z_p$) ,运算是模p的算术运算 如果a和b互素,则b有模a的乘法逆元。【注意:互素是前提】 即如果gcd(a,b)=1,那么b有模a的乘法逆元。对于b<a,存在$b^{-1}<a$,使$bb^{-1}=1\\mod a$ (ax+by) mod a = [ax mod a + by moda ] mod a =by mod 1 $y=b^{-1}$,因此用扩展欧几里得算法。 扩展的欧几里得算法及求乘法逆元不仅计算出最大公因子d,还应该计算出另外两个整数x,y,它们满足如下方程: $ax+by=d=gcd(a,b)$(其中,x和y具有相反的正负号。) 递推公式:$x_{i}=x_{i-2}-q_{i}x_{i-1}$和$y_{i}=y_{i-2}-q_{i}y_{i-1}$ 余数 公式 x,y 满足 $x_{-1}=1;y_{-1}=0$ $a=ax_{-1}+by_{-1}$ $x_{0}=0;y_{0}=1$ $b=ax_{0}+by_{0}$ $r_{1}=a\\mod b$ $a=q_{1}b+r_{1}$ $x_{1}=x_{-1}-q_{1}x_{0}\\\\y_{1}=y_{-1}-q_{1}y_{0}$ $r_{1}=ax_{1}+by_{1}$ $r_{n}=r_{n-2}\\mod r_{n-1}$ $r_{n-2}=q_{n}r_{n-1}+r_{n}$ $x_{n}=x_{n-2}-q_{n}x_{n-1}\\\\y_{n}=y_{n-2}-q_{n}y_{n-1}$ $r_{n}=ax_{n}+by_{n}$ $r_{n+1}=r_{n-1}\\mod r_{n}=0$ $r_{n-1}=q_{n+1}r_{n}+0$ $d=gcd(a,b)=r_{n}\\\\x=x_{n}\\quad y=y_{n}$ 对于求乘法逆的例子,由于要求a,b为素数,最后的结果一般如下: 待处理余数 公式 x y $r_{n}=1$ $r_{n-2}=q_{n}r_{n-2}+1$ $x_{n}=x_{n-2}-q_{n}x_{n-1}$ $y_{n}=y_{n-2}-q_{n}y_{n-1}$ d=1 $x=x_n$ ✔️ $y=y_n$✔️ 对于任意的n,如果运用扩展欧几里得算法可以用于求取$Z_{n}$内的乘法逆元。如果运用扩展欧几里得算法于方程nx+by=d,并且得到d=1,则在$Z_{n}$内有$y=b^{-1}$ 公钥密码学与RSA解决密钥分发的几种方法: 1.通过事先共享密钥解决 2.通过密钥分配中心(KDC) 3.通过Diffie-Hellman 4.通过公钥密码来解决 轮转机/DES基于替换,置换方法上。 公钥密码非对称,使用两个独立的密钥。公钥密码的处理过程不比传统密码中的那些过程更简单,也并不比之更有效。 【公钥密码学-》密码学界唯一&最伟大的一次革命】 基本概念: 非对称密钥:两个密钥:公钥和私钥,用来实现互补运算,即加密和解密,或者生成签名与验证签名。 公钥证书:认证机构将用户的姓名和公钥绑定在一起,用户用自己的私钥对数字文件签名后,可以通过证书识别签名者。签名者是唯一拥有与证书上对应的私钥的用户。 公钥密码(非对称密码)算法:公钥,私钥。从公钥中推出私钥在计算上不可行。 公钥(Public Key)与私钥(Private Key)是通过一种算法得到的一个密钥对(即一个公钥和一个私钥),公钥是密钥对中公开的部分,私钥则是非公开的部分。 公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。通过这种算法得到的密钥对能保证在世界范围内是唯一的。使用这个密钥对的时候,如果用其中一个密钥加密一段数据,必须用另一个密钥解密。 特定:加解密算法相同,密钥不同;发送方和接收方拥有不同密钥,两密钥之一必须保密,知道算法和其中一个密钥不能推出另一个密钥 :arrow_forward:加密:公钥加密,私钥解密【消息】 :arrow_forward:签名:私钥加密,公钥认证【解密】 可以既保密又认证,eg:a -> b, PUb{s,PRa}. 【加密,解密,密钥交换】 寻找合适的单向陷门函数是公钥密码体制应用的关键。 对公钥密码的要求公钥对的生成很容易。 加密在计算上很容易(知道明文M和KU)。 解密在计算上很容易(知道密文C和KR)。 从KU中找到KR是不可行的。 从KU和密文中找到明文是不可行的。 加密和解密函数顺序是可交换的 【为防止穷举攻击,用较长的密钥,但是加解密比较慢】 RSA算法明文和密文均是0至n-1之间的证书,n的大小通常为1024位的2进制,或309位的十进制。 生成RSA密钥每个用户生成一个私钥/公钥对,通过: 随机选择两个大素数p,q;【保密的,选定的】 计算系统的模量:N=pq 【公开的,计算得到的】 注意有$\\phi(N)$ =(p-1)(q-1)【比N小又与N互素的数的个数】 随机选择e,满足gcd($\\phi(N),e$)=1;1<e<$\\phi(N)$ 【公开的,选定的】 解出d,d满足$d\\equiv e^{-1}(\\mod \\phi(N))$【保密的,计算得出的(扩展欧几里得算法?)】 即 ed=1mod $\\phi(N)$且1<d<$\\phi(N)$ 【ed互为模$\\phi(N)$的乘法逆】 公布公钥{e,N};保存私钥{d,N} 【乘法幂回顾】 如果a和b互素,则b有模a的乘法逆元。【注意:互素是前提】 即如果gcd(a,b)=1,那么b有模a的乘法逆元。对于b<a,存在$b^{-1}<a$,使$bb^{-1}=1\\mod a$ RSA的使用明文分组M和密文分组C: $C=M^e \\mod n;(0\\leq M<N) \\\\ M=C^d \\mod n =(M^e \\mod n)^d \\mod n =M^{ed} \\mod n (0\\leq C<N)$ 因此,有$M^{ed} \\mod n =M$ PPT:$C^d = (M^e)^d = M^{1+k.ø(N)} = M^1.(M^{ø(N)})^{k} = M^1.(1)^k = M1 = M mod N =M $ RSA浅析【欧拉定理】数论中的欧拉定理(费马-欧拉定理),表明,若n,a为正整数,且n,a互质,则$a^{\\phi(n)}\\equiv 1 \\mod n$ 在RSA中,N=pq,$\\phi(N)=(p-1)(q-1)$选择mod N互逆的ed,得到$ed=1+k\\phi(N)$ RSA证明$M^{ed} \\mod n =M$ 1.M和p不互素,p可以整除M。则M mod p=0,$M^{ed}\\mod p=0$ 2.M和p互素,根据欧拉公式,$M^{\\phi(p)} \\mod p=1$ $M^{ed}\\mod p=M^{1+k\\phi(p)}\\mod p\\\\=(M\\mod p)\\times ((M^{(p-1)})^{k(q-1)}\\mod p)\\\\=(M\\mod p)\\times ((M^{\\phi(p)})^{k(q-1)}\\mod p)\\\\=(M\\mod p)\\times 1^{k(q-1)}=M\\mod p$ 由于p和q对称,且p,q均为素数。 则同样有$M^{ed}\\mod q=M \\mod q$ 由于pq互质,pq=N,则根据中国余数定理:$M^{ed} \\mod N=M$ (0$\\leq$M<N,大于n(1024bit)可能会有部分解不出来) 【中国余数定理】$如果n_1,n_2,\\cdots n_k两两互质,n=n_1n_2n_3\\cdots n_k,则对于所有的整数x和a,\\\\x\\equiv a(\\mod n_i)当且仅当 x\\equiv a(\\mod n)$ RSA计算及技巧 RSA安全性关键在于大数的分解,当N(1024bit)很大时,分解成两个素数很难 如何选择素数pq? pq最好不相差太大【PPT P30,米勒什么什么算法检验】 (求d和求pq是否等价暂无定论) 3种攻击:1.暴力密钥搜索:(找密钥d),但是e,dN位数较高,难以实现 2.数学攻击,分解N,暂无对大整数进行质因数分解的高效算法。【指在求pq】 3.时间攻击【通过隐藏时间信息防范】 (不知道P和Q,只知道ø(N)也是可以破解的 ??) RSA明文具备的性质 证明RSA,可恢复 在 mod n范围内进行计算 密钥管理及其它公钥密码体制离散对数问题在整数中,离散对数是一种基于同余运算和原根的一种对数运算。而在实数中对数的定义 $log_b a$ 是指对于给定的 a 和 b,有一个数 x,使得$b^{x}$ = a。相同地在任何群 G中可为所有整数 k定义一个幂数为$b^{k}$,而离散对数是 $log_b a$指使得$b^{x}$ = a的 整数 k。 离散对数在一些特殊情况下可以快速计算。然而,通常没有具非常效率的方法来计算它们。公钥密码学中几个重要算法的基础,是假设寻找离散对数的问题解,在仔细选择过的群中,并不存在有效率的求解算法。 Diffie-Hellman 密钥交换Diffie-Hellman是一种建立密钥的方法,而不是加密方法。然而,它所产生的密钥可用于加密、进一步的密钥管理或任何其它的加密方式。Diffie-Hellman密钥交换算法及其优化首次发表的公开密钥算法出现在Diffie和Hellman的论文中,这篇影响深远的论文奠定了公开密钥密码编码学。 【第一个公钥密码系统,可以进行密钥交换】 安全性这种密钥交换技术的目的在于使得两个用户安全地交换一个秘密密钥以便用于以后的报文加密. Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度 。 :aries: 【单纯D-H依旧难以预防中间人攻击,仍需要对公钥拥有者的身份进行验证】 :star:PPT例题【P21】 主要过程 所有用户都知道的全局参数 大素数q g : 用来模q的原始根 每个用户生成自己的公钥(对于用户A) 选择一个密钥,(私钥):数字 $x_{A}$<q 计算公钥:$y_{A}=g^{x_{A}}\\mod q$ 每个用户公开公钥 则用户A&B共享的会话密钥是$K_{AB}$ K_{AB}=g^{x_{A}· x_{B}} \\mod q =y_{A}^{x_{B}}\\mod q (B可以计算出来) =y_{B}^{x_{A}}\\mod q (A可以计算出来) From WIKI: 最简单,最早提出的这个协议使用一个质数p的整数模n乘法群)以及其原根g。下面展示这个算法,绿色表示非秘密信息, 红色粗体表示秘密信息 爱丽丝和鲍伯写上一个有限循环群 G 和它的一个生成元 g。 (这通常在协议开始很久以前就已经规定好; g是公开的,并可以被所有的攻击者看到。) 爱丽丝选择一个随机自然数 a(很大) 并且将 $g^{a}\\mod p$(大素数)发送给鲍伯。 鲍伯选择一个随机自然数 b (很大)并且将 $g^{b}\\mod p$发送给爱丽丝。 爱丽丝 计算 $(g^{b})^{a} \\mod p$ 。 鲍伯 计算 $(g^{a})^{b} \\mod p$ 。 爱丽丝和鲍伯就同时协商出群元素$g^{ab}$,它可以被用作共享秘密。$(g^{b})^{a}$和$(g^{a})^{b}$因为群乘法交换的。 算法解释爱丽丝和鲍伯最终都得到了同样的值,因为在模p下$g^{ab}$和 $g^{ba}$ 相等。 注意a, b 和 $g^{ab}= g^{ba} \\mod p$ 是秘密的。 其他所有的值 – p, g, $g^{a} \\mod p$, 以及 $g^{b}\\mod p $– 都可以在公共信道上传递。 一旦爱丽丝和鲍伯得出了公共秘密,他们就可以把它用作对称密钥,以进行双方的加密通讯,因为这个密钥只有他们才能得到。 Elgamal密码体制1984年,T.Elgamal提出了一种基于离散对数的公开密钥体制,是一个基于迪菲-赫尔曼密钥交换的非对称加密算法。ElGamal密码体系应用于一些技术标准中,如数字签名标准(DSS)和S/MIME电子邮件标准。与Diffie-Hellman一样,ElGamal的系统用户也是共同选择一个素数q,$g$是q的素跟。 算法描述ElGamal加密算法由三部分组成:密钥生成、加密和解密。 密钥生成密钥生成步骤如下: Alice利用生成元 $g$ 产生一个 q,阶循环群 $G$,的有效描述。该循环群需要满足一定的安全性质。 Alice从 $\\{1,\\cdots,q-1\\}$中随机选择一个 $x$。 Alice计算 $h:=g^{x} \\mod q $。 Alice公开$h$,以及 $G,q,g$ 的描述作为其公钥,并保留 $x$ 作为其私钥。私钥必须保密。 加密其他用户可以通过Alice的公钥进行加密。 用Alice的公钥 $(G,q,g,h)$向她加密一条消息 m的加密算法工作方式如下: 将信息表示成一个整数M,其中$1\\leq M \\leq q-1$,以分组密码序列的方式来发送信息,其中每个分块的长度不小于整数q。 Bob从 $\\{1,\\cdots,q-1\\}$ 随机选择一个 $y$。(私钥) 然后计算 密钥$K=h^{y} \\mod q$。(会话密钥) 将M加密成明文对$(C_{1},C_{2})$,其中 $C_{1}=g^{y} \\mod q ; C_{2}=K\\cdot M \\mod q $(公钥,加密的信息) 解密Alice利用自己的私钥进行解密。 得到密钥:$K=(C_{1})^{x} \\mod q$ 得到消息$M = (C_{2}K^{-1}) \\mod q$ 如果信息必须分组,然后以加密的密钥块序列发送,那么每个分块要有唯一的x(私钥)。如果x用于多个分块,则利用信息的分块$M_{1}$,攻击者会计算出其他块。 ElGamal的安全性是基于计算离散对数的困难性之上。 椭圆曲线密码【本章节很多内容已经在课本上圈圈画画了,包括比较重要的计算】 Elliptic Curve Cryptography 什么样的公式可以被称为椭圆曲线,ECC加解密过程,ECC与RSA的对比,ECC的相关计算 我们将椭圆曲线的方程限制为以下形式: $y^{2}=x^{3}+ax+b$ 加密解密[课本230]考虑如下等式: K=kG [其中 K,G为Ep(a,b)上的点,k为小于n(n是点G的阶)的整数] 给定k和G,根据加法法则,计算K很容易;但 给定K和G,求k就相对困难了。 这就是椭圆曲线加密算法采用的难题。我们把点G称为基点(base point),k(k<n,n为基点G的阶)称为私有密钥(privte key),K称为公开密钥(public key)。 现在我们描述一个利用椭圆曲线进行加密通信的过程:1、用户A选定一条椭圆曲线Ep(a,b),并取椭圆曲线上一点,作为基点G。2、用户A选择一个私有密钥k,并生成公开密钥K=kG。3、用户A将Ep(a,b)和点K,G传给用户B。4、用户B接到信息后 ,将待传输的明文编码到Ep(a,b)上一点M(编码方法很多,这里不作讨论),并产生一个随机整数r(r<n)。5、用户B计算点$C_1 =M+rK;C_2 =rG$。6、用户B将$C_1 、C_2 $传给用户A。7、用户A接到信息后,计算$C_1 -kC_2$ ,结果就是点M。因为 $ C_1 -kC_2 =M+rK-k(rG)=M+rK-r(kG)=M$ 再对点M进行解码就可以得到明文。 通过K、G 求k 或通过C_2 、G求r 都是相对困难的 实现密钥交换D_HA->a-(私钥)>aG(公钥) B->b(私钥)->bG 会话密钥:abG 消息认证和Hash函数Hash 函数简介首要目标-》保证数据完整性。 【消息认证】确保收到的信息和发送时一样(即没有修改、插入、删除重放等)通常还需保证发送方身份真实有效。当Hash函数提供消息认证功能时,Hash函数值通常称为消息摘要。 为防止中间人攻击,要对生成的hash值进行加密。更一般的,消息认证通过MAC实现,即带密钥的hash函数。 两种简单的hash函数,1.分组后直接异或。2.分组后,每一组循环左移,再与上一次hash值异或。 但都是容易被破解的:就是如果每一个比特取反,异或后结果还是一样的。因此移位也是很容易找到两个不同的消息有一样的hash值。 但两个都不安全。另一种采用CBC的方式,由于异或可以任意顺序计算,因此改变密文分组的顺序不会造成影响。 Hash 函数的要求1 可以应用于任何大小的消息M2 产生固定长度的输出h【任意长度-》(输出)固定长度】3 对于任何消息M,都很容易计算h= H(M) 4 给定h,不可求x 即 h (x)=h求x是不可行的 【单向性质】5 给定分块x求出y不可行的,其中H(y)=H(x) 【抗弱碰撞性】6 找到任何满足H(y)=H(x)的偶对在计算上是不可行的 【抗强碰撞性】(对hash函数要求高) 抗弱碰撞性包括抗强碰撞性。 MD5:哈希算法的一种,常用于文件的唯一标识。用于确保信息传输完整一致。MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被”压缩”成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串) SHA1:是使用最广泛的hash算法的一种,建立在MD4只上,输出为160位hash值。 安全哈希算法主要适用于数字签名标准里面定义的数字签名算法。对于长度小于2^64位的消息,SHA1会产生一个160位的消息摘要。当接收到消息的时候,这个消息摘要可以用来验证数据的完整性。在传输的过程中,数据很可能会发生变化,那么这时候就会产生不同的消息摘要。 对hash函数的两种攻击方式 暴力破解 生日攻击 A发送消息x, 第三方产生$2^{\\frac{m}{2}}$种变式x’,且每一种变式表达相同意义,存hash值。同时伪造消息y,造y的变式y’,且也表达同样的意义。然后计算H(y’),并与任意的H(x’)进行比对,直到找到某个H(y’)=H(x’) 则攻击者将变式x‘给A获取签名,然后将签名附在y’后发给接收方。 代价数量级,2^{m/2} 找到两个不同的消息,具有相同的hash值。利用“两个集合相交”问题的原理生成散列函数碰撞,达到目的的攻击称为生日攻击,也称为平方根攻击 。 这种攻击对Hash函数提出了一个必要的安全条件,即消息摘要必须足够长。 【试图破解强碰撞性】 单向散列函数无法解决的问题能够辨别出“篡改”,但无法辨别“伪装”。用于认证的技术->数字签名,消息认证码。认证需要使用密钥【只有Alice才知道的秘密信息】 其它密钥分发 基于对称加密的对称密钥分发 用户和KDC(密钥分发中心)共享密钥,分为密钥分发步骤和认证步骤。 密钥分发步骤,a提出请求,KDC把密钥给A并把B的消息也给它,然后a发给b并进行密钥认证。【认证得到的是同一个会话密钥】,可以记得用f(Na) 基于非对称加密的对称密钥分发 即用公钥私钥交换会话密钥,注意中间人攻击。【公钥少用于大数据块的加密,多用于小数据快】 确认保密性和身份认证的密钥分发 【随机数可以确认身份哦】 公钥分发 公开可访问的目录 里面有{姓名,公钥},但是攻击者盗用管理员私钥后gg 公钥证书 通信双方通过证书来交换密钥,证书保护公钥和公钥拥有者的标志。由可信的第三方进行签名。【1.关于分发2.协商会话密钥】 消息认证【伪装-内容修改-消息顺序修改-重放攻击重播】 可以用hash,Mac,消息加密【加密后的消息作为认证】 消息加密作为认证,因为密钥仅两方共有,不过不可识别随机码,但是可以增加错误控制。校验码,让明文是易于识别的结构。 消息认证码:MAC:确认消息完整性并对消息进行认证的技术。 消息认证码的输入包括任意长度的消息 和 一个发送者接收者之间共享的密钥。 同时发送消息和mac值,接收方用同样的密钥得到Mac进行比对。 MAC=C(K,M),C:MAC函数 1)判断消息未修改2)由于有密钥,所以可以确定消息来自真正的发送方。3)序列号保证消息顺序正确。 MAC函数应当有以下要求1.若攻击者已知M和MAC(K,M),构造MAC(K,M’)=MAC(K,M)计算上不可行 2.MAC(K,M)均匀分布,MAC(K,M’)=MAC(K,M)概率是$2^{-n}$[阻止基于明文的选择攻击] 3.已知M’=f(M),M’是M已知的变换,比如讲M一位多位取反等。要求 Pr[MAC(K,M’)=MAC(K,M)]=$2^{-n}$[认证算法对消息某部分或位不应当比其他部分或位更弱] 【能不能用会话密钥进行认证呢?假如发送的是随机的内容,随机码,会话密钥不太好进行认证;负荷比较大,要求完全解密】 应用实例 IPSEC SSL/TLS 实现方式 单向散列函数 HMAC 使用分组密码实现消息认证码。 会遭受的攻击 重放攻击 解决方法【加一个递增序号】【加时间戳】【加随机数】 暴力攻击&生日攻击 消息认证码无法解决的问题:对第三方证明消息的发送者、eg,b向c证明消息确实是由a发送的;防止否认:a可能会否认自己向b发送过这个消息。 用数字签名解决。 数字签名【防止发送方否认】生成签名 验证签名。数字签名对签名密钥【私钥】和验证密钥【公钥】进行了区分,使用验证密钥是无法生成签名的。签名密钥只能由签名的人持有,而验证密钥任何需要验证签名的人都可以持有。 1.直接对消息签名 2.对消息的散列值签名 【签名把签名和特定的消息绑定在一起】 消息认证可以保护信息交换双方不受第三方的攻击,但是它不能处理通信自身双方发生的攻击,如发送方否认。在收发双方不能完全信任的情况下,用数字签名,数字签名需要有以下几点特征: 1.它必须能验证签名者,签名日期和时间;2.它必须能认证被签的消息内容。3.签名应该由第三方仲裁,以解决政治。 因此数字签名具有认证功能。 数字签名的性质• must depend on the message signed【取决于消息】• must use information unique to sender【用了与发送方唯一相关的信息】– to prevent both forgery and denial【防止伪造和否认】• must be relatively easy to produce【易于生成】• must be relatively easy to recognize & verify【相对容易认证/确认】• be computationally infeasible to forge【伪造数字签名计算不可行,无论是从给定的数字签名伪造消息,还是从给定的消息伪造数字签名】 – with new message for existing digital signature – with fraudulent digital signature for given message• be practical save digital signature in storage【实际保存数字签名存储】 直接数字签名,只涉及通信双方,签名在内层,发生争执时,第三方查看签名。 缺点:发送方可以谎称私钥丢失或者被盗。因此需要一个时间戳,并在密钥被泄密后及时汇报。【不过不可以防止时间戳造假】 用户认证Arbitrated Digital Signatures 仲裁数字签名,有第三方。 涉及到仲裁者A的使用:-验证任何已签名的消息-然后注明日期并发送给收件人 •要求对仲裁者有适当程度的信任 •可以使用私有或公钥算法实现 •仲裁者可能看到信息,也可能看不到 【认证用户身份一般需要的工具 1.你本身性质body you are(DNA) 2.你有什么(令牌,like id,门禁卡什么的) 3.你知道什么 remember什么 密码什么的】【有的时候这几种可以同时使用】 认证协议:用于说服对方以交换会话密钥。可以是单向or双向。机密性-》会话密钥。及时性-》需要防止重放攻击。 重放攻击:复制并重新发送有效的签名的消息。防止措施:使用序号(不常用);时间戳(需要统一时钟);【挑战/应答】随机码 [挑战/应答,随机码]不适用于无连接类型的传输。 {因为在无连接传输之前的握手开销。实质上是否定了无连接传输的主要优点。} 基于对称加密的远程用户认证 双向认证 双向认证协议能使通信双方互相认证彼此身份并交换会话密钥 在分布式环境中,可以用一个两层的对称加密密钥。该方案需要可信的密钥分发中心(KDC)参与,每一方和KDC之间都共享一个密钥(称为主密钥)。KDC负责产生两者的会话密钥,并用主密钥来保证会话密钥的安全分发。 步骤4反映b收到密钥,步骤5使b明确自己和a的密钥一样【第三步可能会被重放】或者会话密钥泄露,第三方截获步骤四,伪装a发送步骤五。 注意这里时间戳也加密了。第二步的随机数换成了时间戳。 【时间戳,用时间确定】 需要保证时钟同步。 可以只根据b的时间订,可以看一下课本P346. 总的来说,主要注意:随机数-》除了防止重放,另一个作用是消息确实来源于自己,或消息和自己有关 随机数,防止重放+消息和发送方有关。时间戳,防止重放,时间双方均可验证;或者随机数加密验证ks更具有代表性吧 看以下:因为时间戳需要重复利用,因此用随机数。 Kerberos【单向认证】单向认证:电子邮件式,接收者确认信息来自发送者。 一种方法【对称密钥】: A→KDC: ID A || IDB || N 1 KDC→A: E Ka [Ks || ID B || N 1 || E Kb [Ks||ID A ] ] A→B: E Kb [Ks||ID A ] || E Ks [M] 不能抵抗重放攻击,也可以添加时间戳,但是电子邮件可能延误。 一种方法 非对称密钥 A→B: E KUb [Ks] || E Ks [M] (保密性)A→B: M || E KRa [H(M)] || E KRas [T||ID A ||KU a ] (认证性,后半部分是证书)可以看做a的证书,对a的公钥的签名认证a的 Kerberos通过提供一个集中的授权服务器来负责用户对服务器的认证和服务器对用户的认证。Kerberos仅仅依赖于对称加密体制而没有使用公钥加密体制。 在一个开放的网络环境中,所有用户都可以向任一服务器请求服务。每个服务器为了认证用户的合法性就必须知道每一个用户口令。显然网络规模越大维护越复杂,所以引入:认证服务器(AS):它将所有用户的口令集中存放在本地数据库中;而且它与每一个应用服务器共享一个唯一的密钥。(密钥通过物理的或其他安全的方式分发)客户端(C):代替用户与服务器进行信息交换。票据(Ticket) :身份或权利的证明。Ticket 由 AS 以数据报形式发放给 C。 票据许可服务器 (TGS)向已经通过TGS认证的用户发放服务Ticket。用户首先向AS请求一张票据许可票 Ticket tgs ,并将它保存在 C 中。每当用户要求一种新的服务时,客户便用这张能认证自己的 Ticket tgs 向TGS发出申请。TGS给用户发回一张针对某种特定服务的服务许可票据 Ticket V,客户将保留每一个Ticket V ,在每次请求相同服务时提供给服务器 V 来认证。 功能特性分析 可信的第三方Kerberos服务器 所需的密钥分配和管理变简单 AS负担认证工作,减轻应用服务器负担 安全相关数据集中管理和保护,使攻击者入侵难以成功 Ticket 使AS(TGS)的认证结果和会话密钥安全传给C和TGS(应用服务器) 生存期内可重用,减少认证开销,提高方便性 共享密钥 为认证提供安全依据 TGS 降低用户口令的使用频度,提供更好的口令保护 减轻AS负担,提高系统效率 Session Key 防止非法用户窃得Ticket进行重放攻击 提供了对服务器的认证 时间戳 防止对Ticket和认证符的重放攻击 局限性分析Kerberos服务器易受攻击– 它的安全性决定了整个系统得安全性,若此关键环节发生问题,危害是灾难性的。 口令攻击– 对手截获基于口令的密钥加密的内容,采用暴力破解成功后,得到口令也就到该用户的全部资源域间认证复杂完全没有任何非对称加密的技术,有一些局限性,可以用非对称加密算法避免 用于非对称加密的远程用户认证双向认证 需要保证是正确的公钥,使用central Authentication Server (AS),时间戳。【看课本吧360页】 改进,在56步中的证书里,加入IDa,保证请求唯一来自于A 对数字签名的攻击 中间人攻击 对单向散列函数的攻击 利用数字签名攻击公钥密码 数字签名无法解决的问题:用于验证签名的公钥必须属于真正的发送者。确认公钥是否合法——证书!。 证书:就是把公钥当成一个消息,由一个可信的第三方对其签名后得到的公钥。PKI【公钥基础设施】 证书公钥证书PKC 由认证机构(CA)施加数字签名 对证书的攻击 1.在公钥注册之前攻击。即a发送公钥去认证是,e将a的公钥替换成自己的。【so a在认证时应该将自己的公钥用认证机构公钥加密】 2.攻击者伪装成认证机构进行攻击。自己成立一个认证机构,给自己的公钥颁发证书,并声称是“Bob”的公钥【要注意的证书的颁发机构】 SSL\\TLS SSL记录协议–建立在可靠的传输协议(如TCP)之上–它提供连接安全性,有两个特点• 保密性,使用了对称加密算法• 完整性,使用HMAC算法–用来封装高层的协议• SSL握手协议–客户和服务器之间相互鉴别–协商加密算法和密钥–它提供连接安全性,有三个特点• 身份鉴别,至少对一方实现鉴别,也可以是双向鉴别• 协商得到的共享密钥是安全的,中间人不能够知道• 协商过程是可靠的 零知识证明它指的是证明者能够在不向验证者提供任何有用的信息的情况下,使验证者相信某个论断是正确的。零知识证明实质上是一种涉及两方或更多方的协议,即两方或更多方完成一项任务所需采取的一系列步骤。证明者向验证者证明并使其相信自己知道或拥有某一消息,但证明过程不能向验证者泄漏任何关于被证明消息的信息。大量事实证明,零知识证明在密码学中非常有用。如果能够将零知识证明用于验证,将可以有效解决许多问题。 (1)完备性。如果证明方和验证方都是诚实的,并遵循证明过程的每一步,进行正确的计算,那么这个证明一定是成功的,验证方一定能够接受证明方。(2)合理性。没有人能够假冒证明方,使这个证明成功。(3)零知识性。证明过程执行完之后,验证方只获得了“证明方拥有这个知识”这条信息,而没有获得关于这个知识本身的任何一点信息。 “零知识证明”是密码学中存在于“证明者(prover,以下用P代替)”和“验证者(verifier,以下用V代替)”双方的一种协议,这种协议的要求是: P使得V相信其拥有某知识 V不能从证明过程中得到知识的任何信息 为了使得V相信P拥有知识,证明须满足, 完备性:如果P确实拥有知识,那么证明成功的概率大于2/3 可靠性:如果P并不拥有知识,那么证明成功的概率小于1/3 若证明满足上面的条件,则通过反复多次的证明,就能使得V相信P拥有知识的概率趋近1,P不拥有知识的概率趋近0。 (零知识(ness性):验证者除了知道陈述是真实的之外,没有学到任何信息 ) 图的三色问题,是指找到这样一种染色方法,将图的顶点用三种颜色中的一种染色,并使得相邻顶点不同色。假如P找到了图G满足三色问题的一种染色方法,想要证明给V,他应该怎么做呢(O.O)? P随机选择一种颜色的置换方式(如红-蓝,蓝-黄,黄-红),将原来的染色方案按照颜色置换重新染色。将染色过后的G放在密封的信封里发给V。 随机选择图中的一条边,并要求P打开这条边的两个顶点。 P打开相应的信封,展示顶点的颜色。 如果展示的顶点颜色不同,则V接受证明。 完备性:如果P确实有三染色方案,那么证明必然成功。 可靠性:如果P没有三染色方案,那么他的染色方案中至少有一条边是顶点同色的。假设图G有n条边,那么证明失败的概率至少是1/n。如果证明进行m次,那么P人品爆表蒙混过关的概率就会小于(1-1/n)^m,当m足够大,这个概率就趋近于0。 而由于每次的随机颜色置换,V无法从揭示的边中获得任何染色方案的信息。因此这个证明是零知识证明。至于如何构造这样的“信封”,使得V在P开启信封前不能看到信封中的内容,而P也无法通过不同的”拆封”方式而操纵信封中的内容,这就是另外一种密码协议“承诺协议”的范围了。","categories":[{"name":"信息安全","slug":"信息安全","permalink":"https://yongbosmart.github.io/categories/信息安全/"}],"tags":[{"name":"课堂笔记","slug":"课堂笔记","permalink":"https://yongbosmart.github.io/tags/课堂笔记/"},{"name":"信息安全","slug":"信息安全","permalink":"https://yongbosmart.github.io/tags/信息安全/"}]},{"title":"Diffie-Hellman 密钥交换&ElGamal协议的安全密钥交换","slug":"Diffie-Hellman 密钥交换","date":"2018-05-17T16:00:00.000Z","updated":"2018-05-19T07:42:08.697Z","comments":true,"path":"2018/05/18/Diffie-Hellman 密钥交换/","link":"","permalink":"https://yongbosmart.github.io/2018/05/18/Diffie-Hellman 密钥交换/","excerpt":"","text":"Diffie-Hellman 密钥交换&ElGamal协议的安全密钥交换离散对数问题在整数中,离散对数是一种基于同余运算和原根的一种对数运算。而在实数中对数的定义 $log_b a$ 是指对于给定的 a 和 b,有一个数 x,使得$b^{x}$ = a。相同地在任何群 G中可为所有整数 k定义一个幂数为$b^{k}$,而离散对数是 $log_b a$指使得$b^{x}$ = a的 整数 k。 离散对数在一些特殊情况下可以快速计算。然而,通常没有具非常效率的方法来计算它们。公钥密码学中几个重要算法的基础,是假设寻找离散对数的问题解,在仔细选择过的群中,并不存在有效率的求解算法。 Diffie-Hellman 密钥交换Diffie-Hellman是一种建立密钥的方法,而不是加密方法。然而,它所产生的密钥可用于加密、进一步的密钥管理或任何其它的加密方式。Diffie-Hellman密钥交换算法及其优化首次发表的公开密钥算法出现在Diffie和Hellman的论文中,这篇影响深远的论文奠定了公开密钥密码编码学。 这种密钥交换技术的目的在于使得两个用户安全地交换一个秘密密钥以便用于以后的报文加密. Diffie-Hellman密钥交换算法的有效性依赖于计算离散对数的难度 。 主要过程 所有用户都知道的全局参数 大素数q g : 用来模q的原始根 每个用户生成自己的公钥(对于用户A) 选择一个密钥,(私钥):数字 $x_{A}$<q 计算公钥:$y_{A}=g^{x_{A}}\\mod q$ 每个用户公开公钥 则用户A&B共享的会话密钥是$K_{AB}$ K_{AB}=g^{x_{A}· x_{B}} \\mod q =y_{A}^{x_{B}}\\mod q (B可以计算出来) =y_{B}^{x_{A}}\\mod q (A可以计算出来) From WIKI: 最简单,最早提出的这个协议使用一个质数p的整数模n乘法群)以及其原根g。下面展示这个算法,绿色表示非秘密信息, 红色粗体表示秘密信息 爱丽丝和鲍伯写上一个有限循环群 G 和它的一个生成元 g。 (这通常在协议开始很久以前就已经规定好; g是公开的,并可以被所有的攻击者看到。) 爱丽丝选择一个随机自然数 a(很大) 并且将 $g^{a}\\mod p$(大素数)发送给鲍伯。 鲍伯选择一个随机自然数 b (很大)并且将 $g^{b}\\mod p$发送给爱丽丝。 爱丽丝 计算 $(g^{b})^{a} \\mod p$ 。 鲍伯 计算 $(g^{a})^{b} \\mod p$ 。 爱丽丝和鲍伯就同时协商出群元素$g^{ab}$,它可以被用作共享秘密。$(g^{b})^{a}$和$(g^{a})^{b}$因为群乘法交换的。 算法解释爱丽丝和鲍伯最终都得到了同样的值,因为在模p下$g^{ab}$和 $g^{ba}$ 相等。 注意a, b 和 $g^{ab}= g^{ba} \\mod p$ 是秘密的。 其他所有的值 – p, g, $g^{a} \\mod p$, 以及 $g^{b}\\mod p $– 都可以在公共信道上传递。 一旦爱丽丝和鲍伯得出了公共秘密,他们就可以把它用作对称密钥,以进行双方的加密通讯,因为这个密钥只有他们才能得到。 Elgamal密码体制1984年,T.Elgamal提出了一种基于离散对数的公开密钥体制,是一个基于迪菲-赫尔曼密钥交换的非对称加密算法。ElGamal密码体系应用于一些技术标准中,如数字签名标准(DSS)和S/MIME电子邮件标准。与Diffie-Hellman一样,ElGamal的系统用户也是共同选择一个素数q,$g$是q的素跟。 算法描述ElGamal加密算法由三部分组成:密钥生成、加密和解密。 密钥生成密钥生成步骤如下: Alice利用生成元 $g$ 产生一个 q,阶循环群 $G$,的有效描述。该循环群需要满足一定的安全性质。 Alice从 $\\{1,\\cdots,q-1\\}$中随机选择一个 $x$。 Alice计算 $h:=g^{x} \\mod q $。 Alice公开$h$,以及 $G,q,g$ 的描述作为其公钥,并保留 $x$ 作为其私钥。私钥必须保密。 加密其他用户可以通过Alice的公钥进行加密。 用Alice的公钥 $(G,q,g,h)$向她加密一条消息 m的加密算法工作方式如下: 将信息表示成一个整数M,其中$1\\leq M \\leq q-1$,以分组密码序列的方式来发送信息,其中每个分块的长度不小于整数q。 Bob从 $\\{1,\\cdots,q-1\\}$ 随机选择一个 $y$。(私钥) 然后计算 密钥$K=h^{y} \\mod q$。(会话密钥) 将M加密成明文对$(C_{1},C_{2})$,其中 $C_{1}=g^{y} \\mod q ; C_{2}=K\\cdot M \\mod q $(公钥,加密的信息) 解密Alice利用自己的私钥进行解密。 得到密钥:$K=(C_{1})^{x} \\mod q$ 得到消息$M = (C_{2}K^{-1}) \\mod q$ 如果信息必须分组,然后以加密的密钥块序列发送,那么每个分块要有唯一的x(私钥)。如果x用于多个分块,则利用信息的分块$M_{1}$,攻击者会计算出其他块。 ElGamal的安全性是基于计算离散对数的困难性之上。 参考资料[1]维基百科编者. 迪菲-赫尔曼密钥交换[G/OL]. 维基百科, 2018(20180503)[2018-05-03]. https://zh.wikipedia.org/w/index.php?title=%E8%BF%AA%E8%8F%B2-%E8%B5%AB%E7%88%BE%E6%9B%BC%E5%AF%86%E9%91%B0%E4%BA%A4%E6%8F%9B&oldid=49408565. [2]《密码编码学与网络安全 原理与实践》(第6版)斯托林斯著 [3]维基百科编者. ElGamal加密算法[G/OL]. 维基百科, 2016(20161214)[2016-12-14]. https://zh.wikipedia.org/w/index.php?title=ElGamal%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95&oldid=42453545.","categories":[{"name":"信息安全","slug":"信息安全","permalink":"https://yongbosmart.github.io/categories/信息安全/"}],"tags":[{"name":"课堂笔记","slug":"课堂笔记","permalink":"https://yongbosmart.github.io/tags/课堂笔记/"}]},{"title":"半边数据结构&网格细分与简化","slug":"计算机图形学-半边数据结构","date":"2018-05-05T16:00:00.000Z","updated":"2018-05-09T13:40:51.653Z","comments":true,"path":"2018/05/06/计算机图形学-半边数据结构/","link":"","permalink":"https://yongbosmart.github.io/2018/05/06/计算机图形学-半边数据结构/","excerpt":"","text":"半边数据结构&网格细分参考博客: https://blog.csdn.net/lafengxiaoyu/article/details/51524361 https://blog.csdn.net/outtt/article/details/78544053 http://www.cnblogs.com/shushen/p/5251070.html 对于表面网络来说,其重要的特点在于拓扑,也就是曲面是如何表达的,而不是其顶点的位置。拓扑的不同造就了不同的数据结构和标准,不同的拓扑,其进行网格查询和编辑的性能也不同。计算机图形学上,通常说的流形是一种几何模型表面(但不是所有的),即二维流形,对应拓扑流形。如果网格的每个边最多被两个面片共用,那么这个网格就是流形网络,否则称为非流形网络。 半边数据结构:最大特点是半边,每个边分为两个半边,每个半边都是一个有向边,方向相反。如果一个边被两个面片公用,则每个面片都能各自拥有一个半边。如果一个边仅被一个面片占用(边界边),则这个面片仅拥有该边的其中一个半边,另一个半边为闲置状态。每一条半边仅存储它的起点指针 半边数据结构仅支持流形网络。 半边数据结构的三个重要的数据结构——顶点、半边、面片 顶点(Vertex):包含出半边(OutgoingHalfedge)的指针或索引 在半边数据结构中的点储存着x,y,z的位置和以其为起始点的半边的指针。在任意给定的点上存在超过一条我们可以选择的半边,但是我们只需要选择其中一条并且是哪一条没关系,在下面的查询方法中我们会看到解释。 12345678910struct HE_vert { float x; float y; float z; HE_edge* edge; // one of the half-edges emantating from the vertex }; 半边(HalfEdge):包含终点(StartVertex)、邻接面(AdjacentFace)、下一条半边(NextHalfedge)、相反边(opposite)的指针或索引 12345678struct HE_edge { HE_vert* vert; // vertex at the end of the half-edge HE_edge* pair; // oppositely oriented adjacent half-edge HE_face* face; // face the half-edge borders HE_edge* next; // next half-edge around the face }; 面片(Face):包含一条起始边(FirstHalfedge)的指针或索引对于一个半边数据结构的简单形式,一个面仅仅需要储存一个围绕它的边的指针,在一些特定场合可能要求我们储存比如材质和法向一类的信息。和上面一样,虽然有很多边围绕着面,我们只需要储存其中一条,而无所谓是哪一条 123456struct HE_face { HE_edge* edge; // one of the half-edges bordering the face }; 现在问题来了。顶点可能有两条或以上的出半边,而顶点的数据表达只有一条出半边,那这条出半边是哪一条?半边的下一条半边又是哪一条?面片的起始半边又是哪一条?通过某个网格的数据结构图(如图1)能看得出这些信息吗? 答:事实上,半边数据结构的网格的构建通常是通过面列表来创建的,也就是说,正常的构建半边数据结构网格是通过一个一个面片的添加来构建的。 所以面的添加顺序就决定了点边面结构的信息,添加面的方法通常是addFace(a,b,c,…),a,b,c…参数是该面片按其某条环路顺序排列的顶点的指针或索引。注意,环路可以是顺时针或者逆时针,决定了该面片的方向(法向量的方向)。 三维网格细分算法:Catmull-Clark subdivisionCatmull-Clark细分是一种四边形网格的细分法则,每个面计算生成一个新的顶点,每条边计算生成一个新的顶点,同时每个原始顶点更新位置。 1.网格内部F-顶点位置: 设四边形的四个顶点为v0、v1、v2、v3,则新增加的顶点位置为v = $\\frac{1}{4}$ ×(v0 + v1 + v2 + v3)。 2.网格内部V-顶点位置: 设内部顶点v0的相邻点为v1、v2,…,v2n,则该顶点更新后位置为,其中α、β、γ分别为α = 1 - β - γ。 3.网格边界V-顶点位置: 设边界顶点v0的两个相邻点为v1、v2,则该顶点更新后位置为v = $\\frac{3}{4}×v0 +\\frac{1}{8}$×(v1 + v2)。 4.网格内部E-顶点位置: 设内部边的两个端点为v0、v1,与该边相邻的两个四边形顶点分别为v0、v1、v2、v3和v0、v1、v4、v5,则新增加的顶点位置为v = $\\frac{1}{4}$ (v0 + v1 + vf1 + vf2) = $\\frac{3}{8}$ (v0 + v1) + $\\frac{1}{16}$ (v2 + v3 + v4 + v5)。 5.网格边界E-顶点位置: 设边界边的两个端点为v0、v1,则新增加的顶点位置为v = $\\frac{1}{2}$ (v0 + v1)。 Loop subdivisionLoop细分是一种三角形网格的细分法则,它按照1-4三角形分裂,每条边计算生成一个新的顶点,同时每个原始顶点更新位置。下图为Loop细分格式的细分掩膜,对于新增加的顶点位置以及原始顶点位置更新规则如下: 1.网格内部V-顶点位置: 设内部顶点v0的相邻点为v1、v2,…,vn,则该顶点更新后位置为,其中。 2.网格边界V-顶点位置: 设边界顶点v0的两个相邻点为v1、v2,则该顶点更新后位置为 v = $\\frac{3}{4}$ ×v0 + $\\frac{1}{8}$ ×(v1 + v2)。。 3.网格内部E-顶点位置(新增点): 设内部边的两个端点为v0、v1,相对的两个顶点为v2、v3,则新增加的顶点位置为v = $\\frac{3}{8}$ (v0 + v1) + $\\frac{1}{8}$(v2 + v3)。 网格内部某条边的两个端点为v0、v1,共享这条边的两个三角形的面是(v0,v1,v2)和(v0,v1,v3) 4.网格边界E-顶点位置(新增点): 设边界边的两个端点为v0、v1,则新增加的顶点位置为v = $\\frac{1}{2}$×(v0 + v1)。","categories":[{"name":"计算机图形学","slug":"计算机图形学","permalink":"https://yongbosmart.github.io/categories/计算机图形学/"}],"tags":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"},{"name":"计算机图形学","slug":"计算机图形学","permalink":"https://yongbosmart.github.io/tags/计算机图形学/"}]},{"title":"算法作业-有向图","slug":"算法作业-有向图","date":"2018-03-25T16:00:00.000Z","updated":"2018-05-05T07:27:23.971Z","comments":true,"path":"2018/03/26/算法作业-有向图/","link":"","permalink":"https://yongbosmart.github.io/2018/03/26/算法作业-有向图/","excerpt":"","text":"算法作业 近期算法布置了一篇课设,在这里描述一下,并晒一下代码。整体来说算法实验很简单,实现方法也很简单粗暴,不过练习一下图的写法吧。 (PS.博主使用的是eclipse for C++,可以利用debug纠错) 问题描述 生成100个点,500条边的有向图,任选一点为源点,计算s到其它点的距离。(用邻接链表存储) 将上述有向图变成dag图,从中去掉一些边,不允许使用递归 计算上述dag图中最长路径,并记录下路径 问题一生成100个点,500条边的有向图,任选一点为源点,计算s到其它点的距离。(用邻接链表存储) 问题分析因为需要用邻接链表存储,因此需要用一个链表结构,基本结构如下: 123456789101112131415161718192021222324252627282930313233struct node{public: node* next=NULL;//下一个 int weight=-1; int ini=0;//入度 int id=-1;//本node的编号public: void init(node* next,int length,int id){//初始化方法 this->next=next; this->weight=length; } void init(int length,int id){//初始化方法 weight=length; this->id=id; } void init(int id){//初始化方法 this->id=id; } bool existedge(int i){//查看是否此边已存在。 node* current=this; if(i==id){ return true; }//自环 while(current->next!=NULL){ if(current->next->id==i){ return true; } current=current->next; } return false; }}; 这里,我写的node的性质比较多,但是事实上是完全没有必要的,如我的小伙伴就只存了一个id,后来证明她这种做法更为合理。 下面需要考虑建一个图需要什么样的方法,由于博主之前写过后缀树一类的,因此这个还是很easy的, 12345678910111213141516171819202122class Arithmex1 {public: Arithmex1(int , int ); virtual ~Arithmex1(); int n=0;//顶点数 int e=0;//边数 int time=0; bool create() ;//创建图,是否存在边 bool insertEdge(int,int); //插入边 bool eraseEdge(int,int);//删除边 int weight(int,int);//某一边的权重 int distance(int);//任选一点为源点,计算s到其它点的距离 void dfs(int); int maxdis();//计算dag图中最长路径,并记录下路径 void dag();//将图变成dag图// 其他方法 bool directed(); void print();//图的打印}; 下面开始图的主要代码: create方法: 123456789101112131415161718bool Arithmex1::create(){ srand(10);//设置随机数种子 graph=new node[n];//是一个装了node(首)的数组 for(int i=0;i<n;i++){ graph[i].init(1,i+1);//于是附上初值,权重为1,编号为i+1; } int j=0; while(j<e){ int tmp1=rand()%n+1;//对应ID int tmp2=rand()%n+1;//dui if(!graph[tmp1-1].existedge(tmp2)){ insertEdge(tmp1,tmp2); }else{ continue; } j++; }} insertEdge方法(简单的,链表插入的方法): 123456789101112131415bool Arithmex1::insertEdge(int a,int b){//插入边 if(!graph[a-1].existedge(b)){ node* current=&graph[a-1]; while(current->next!=NULL){ current=current->next; } node *newnode=new node(); newnode->init(1,b); current->next=newnode; graph[b-1].ini++; return true; } return false;} 博主为了方便,写的方法比较粗暴,因此只是作为参考。值得注意的是,当我们以node来表示节点时,节点在存储空间内不止有一个。如下图:数组中有一个1,链表中2/4后也均有1,因此就像上面说的,在node struct中增加属性没有必要,且可能浪费空间,这时候一个好的做法是维护一个同等规模的数组来保存节点的属性 下面是需要计算源点s到图中各点的距离,显然可以用BFS,主要思路是维持一个数组存储长度,子节点的长度是父节点长度+1; 123456789101112131415161718192021222324252627282930313233343536373839int Arithmex1::distance(int i){ int source=i; node* current=&graph[source-1];//得到源点位置 int tip[n];//tip,标记源点 for(int j=0;j<n;j++){//标记,有没有被找到过 tip[j]=0; } int tis[n];//tip,标记源点 for(int j=0;j<n;j++){//距离,有多远 tis[j]=-1;//开始认为不可达 } tip[source-1]=-1;//设为首节点认为已标记 tis[source-1]=0;//距源点为0 queue<node*> tmp; tmp.push(current);//头结点 while(!tmp.empty()){ node* tt=tmp.front();//头结点 tt=&graph[tt->id-1];//找到在链表中的位置 tmp.pop(); node* cnode=tt; while(cnode->next!=NULL){//遍历其邻接链表 if(tip[cnode->next->id-1]!=-1){//如果没有做过标记 tmp.push(cnode->next);//推入队列 tis[cnode->next->id-1]=tis[tt->id-1]+1; tip[cnode->next->id-1]=-1;//同时做上标记,已扫描// cout<<cnode->next->id<<\" \"<<tis[cnode->next->id-1]<<\" ]]\"; } cnode=cnode->next;//去下一个 } } for(int i=0;i<n;i++){ cout<<graph[i].id<<\"的距离是==> \"<<tis[i]; cout<<endl; }} 问题二将上述有向图变成dag图,从中去掉一些边,不允许使用递归 问题分析这里的一个简单思路就是删除返回边。根据《算法导论》上的算法描述,即dfs时遇到灰色节点,边可被删除。难点主要在于白色、黑色、灰色(也可以是开始时间、结束时间)的判定。另一个难点在于不允许使用递归。这里面有很多种情况,需要仔细判定。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586void Arithmex1::dfs(int start){ //链表属性 node* current=&graph[start-1];//得到源点位置 stack<int> ppid;//记录压入栈时的先驱点 stack<node*> dfss;//记录压入栈的node,有一个固定的id,只需要用id与遍历 ppid.push(-1);//起始点先驱点为-1 dfss.push(current);//放入当前点 while(!dfss.empty()){ node* newnode=dfss.top();//得到node// cout<<\"新一轮循环\"<<newnode->id<<endl; dfss.pop(); int tpid=ppid.top();//当前点newnode押出栈时的先驱点 ppid.pop();//当前节点先驱点 if(pro[newnode->id-1].color==0){//如果其颜色=0 pro[newnode->id-1].color =1;//pop出,正式找其它点,变灰色 pro[newnode->id-1].distime=++time;//发现时间 node* current=&graph[newnode->id-1];//找到图中它的位置就可以遍历链表 int num=0;//是否变黑,只要push进去节点,说明都没有完成,完成的才变黑 while(current!=NULL&&current->next!=NULL){//遍历其邻接链表 if(pro[current->next->id-1].color ==0){//如果没有做过标记 pro[current->next->id-1].pid=newnode->id;//随时会变会更新的pid dfss.push(current->next);//压入栈 ppid.push(newnode->id);//这个对应的pid// cout<<\"放入\"<<current->next->id<<endl; num++;//操作未完成 }else if(pro[current->next->id-1].color==1){//如果找到了灰色的,返回边。// cout<<current->id<<\"shiyan\"<<endl;// cout<<current->next->id<<\"shiyan\"<<endl; change=eraseEdge(newnode->id,current->next->id);//删除// print(); } if(!change){ current=current->next; }else{ change=false; } } if(num==0){//叶子节点或者无用节点,本节点可以为0 pro[newnode->id-1].color=2;//黑色 pro[newnode->id-1].fintime=++time; //开始追根溯源 int tmpid=pro[newnode->id-1].pid; if(!ppid.empty()){ while(tmpid!=ppid.top()){ pro[tmpid-1].color=2; pro[tmpid-1].fintime=++time; tmpid=pro[tmpid-1].pid; } }else{ pro[tmpid-1].color=2; pro[tmpid-1].fintime=++time; // tmpid=pro[tmpid-1].pid; } } }else if(pro[newnode->id-1].color==2){//早之前已经发现 int tmpid=tpid; if(!ppid.empty()){ while(tmpid!=ppid.top()){// cout<<tmpid<<\"pout\"<<endl; pro[tmpid-1].color=2; pro[tmpid-1].fintime=++time; tmpid=pro[tmpid-1].pid; } }else{ pro[tmpid-1].color=2; pro[tmpid-1].fintime=++time; } } }} 因为有的节点入度=0,即一次dfs会忽视它们,因此可以采用直接遍历的方法 1234567void Arithmex1::dag(){ for(int i=0;i<n;i++){ if(pro[i].color==0) dfs(i+1); } print();} 问题三计算上述dag图中最长路径,并记录下路径。 问题分析看到这个题,第一感觉是用拓扑序列,因为拓扑头一定是头结点。 但是博主又想偷懒了,拓扑头怎么求?在上文中,我们已经求出了结束时间最晚的那个节点,无疑一定是拓扑头。但是这里,博主直接采用的入度为0的为拓扑头,(再维持一个数组添加入度属性啦,插入边入度+1,删除边入度-1),这里,只要从拓扑头开始bfs(注意,这里让求的是最长路径,因此无需判断bfs时此点有没有访问过,而且是有向无环图,所以不会死循环) 这是一种比较简单的思路,却需要大量空间时间,另一种比较好的方法是完全用拓扑序列来实现。至于一些细节就不再赘述,代码如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889int Arithmex1::maxdis(){ int* maxpid; maxpid=new int[n]; for(int j=0;j<n;j++){//距离长短 maxpid[j]=-1; } int* tmppid; tmppid=new int[n]; for(int j=0;j<n;j++){//距离长短 tmppid[j]=-1; } int lastid=0; int realastid=-1; int maxdis=0; int cc=0; for(int i=0;i<n;i++){//遍历顶点// cout<<graph[i].ini<<endl; if((graph[i].ini==0)){//入度==0 cc++; int tis[n];//tip,标记源点 for(int j=0;j<n;j++){//距离长短 tis[j]=-1; } tis[i]=0;//到自己的距离为0 int tmpdis=0;// tis[i]=1; node* current=&graph[i];//得到源点位置 queue<node*> tmp; tmp.push(current);//头结点 while(!tmp.empty()){ node* tt=tmp.front();//头结点 tt=&graph[tt->id-1];//找到在链表中的位置 tmp.pop(); node* cnode=tt; while(cnode->next!=NULL){//遍历其邻接链表 tmp.push(cnode->next);//推入队列 tis[cnode->next->id-1]=tis[tt->id-1]+1; tmpdis=tis[cnode->next->id-1]; lastid=cnode->next->id; tmppid[cnode->next->id-1]=tt->id; cnode=cnode->next;//去下一个 } if(cnode->next==NULL){ lastid=cnode->id; } } if(maxdis<tmpdis){ realastid=lastid; maxdis=tmpdis; maxpid=tmppid; for(int j=0;j<n;j++){//距离长短 maxpid[j]=tmppid[j];// cout<<tmppid[j]<<\"||\"<<maxpid[j]<<\" \"; } cout<<endl; tmppid=new int[n]; for(int j=0;j<n;j++){//距离长短 tmppid[j]=-1; } } } } tmppid=new int[maxdis+1]; for(int j=0;j<maxdis+1;j++){//距离长短 tmppid[j]=-1; } if(realastid!=-1)lastid=realastid; int jianyan=lastid; tmppid[maxdis]=lastid; int count=maxdis-1; while(jianyan!=-1){ jianyan=maxpid[jianyan-1]; tmppid[count]=jianyan; count--; } cout<<endl; for(int j=0;j<maxdis+1;j++){//距离长短 cout<<tmppid[j]<<\" \"; } cout<<endl; return maxdis;} 更多完整代码参见https://github.com/yongbosmart","categories":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/categories/日常练习/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/tags/算法/"},{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"ccf1202-游戏","slug":"ccf1202-游戏","date":"2018-03-23T16:00:00.000Z","updated":"2018-04-30T13:36:44.665Z","comments":true,"path":"2018/03/24/ccf1202-游戏/","link":"","permalink":"https://yongbosmart.github.io/2018/03/24/ccf1202-游戏/","excerpt":"","text":"问题描述有n个小朋友围成一圈玩游戏,小朋友从1至n编号,2号小朋友坐在1号小朋友的顺时针方向,3号小朋友坐在2号小朋友的顺时针方向,……,1号小朋友坐在n号小朋友的顺时针方向。 游戏开始,从1号小朋友开始顺时针报数,接下来每个小朋友的报数是上一个小朋友报的数加1。若一个小朋友报的数为k的倍数或其末位数(即数的个位)为k,则该小朋友被淘汰出局,不再参加以后的报数。当游戏中只剩下一个小朋友时,该小朋友获胜。 例如,当n=5, k=2时: 1号小朋友报数1; 2号小朋友报数2淘汰; 3号小朋友报数3; 4号小朋友报数4淘汰; 5号小朋友报数5; 1号小朋友报数6淘汰; 3号小朋友报数7; 5号小朋友报数8淘汰; 3号小朋友获胜。 给定n和k,请问最后获胜的小朋友编号为多少? 输入格式 输入一行,包括两个整数n和k,意义如题目所述。 输出格式 输出一行,包含一个整数,表示获胜的小朋友编号。 样例输入 5 2 样例输出 3 样例输入 7 3 样例输出 4 数据规模和约定 对于所有评测用例,1 ≤ n ≤ 1000,1 ≤ k ≤ 9。 问题分析用队列比较简单 代码1234567891011121314151617181920212223242526272829303132333435363738394041424344package oj;import java.util.LinkedList;import java.util.Queue;import java.util.Scanner;public class ccf1 { public static void main(String[] args) { // TODO 自动生成的方法存根 int n, k; int tmp=1;//记录下一个要数的数 int result=-1; Scanner sc = new Scanner(System.in); n = sc.nextInt();// k =sc.nextInt(); Queue<Integer> cf=new LinkedList<Integer>(); for (int i = 0; i < n; i++) { cf.offer(i+1);// tmp++; } if(n==1){ result=cf.peek(); System.out.println(result); }else{ while(cf.size()>1){//循环往复的问题,,这里第一轮忘记淘汰了// System.out.println(cf.size()); if(tmp%k==0||tmp%10==k){//可以被淘汰 cf.poll(); tmp++;} else{ result=cf.poll(); cf.offer(result);//不淘汰 tmp++; } } System.out.println(cf.peek());//最后这里开始输的是result TAT,所以细心很,很,很重要 } }}","categories":[{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/categories/日常练习/"}],"tags":[{"name":"算法","slug":"算法","permalink":"https://yongbosmart.github.io/tags/算法/"},{"name":"日常练习","slug":"日常练习","permalink":"https://yongbosmart.github.io/tags/日常练习/"}]},{"title":"nachos实验1-3","slug":"nachos实验1-3","date":"2018-02-03T16:00:00.000Z","updated":"2018-05-05T08:31:21.042Z","comments":true,"path":"2018/02/04/nachos实验1-3/","link":"","permalink":"https://yongbosmart.github.io/2018/02/04/nachos实验1-3/","excerpt":"","text":"ProJ1: Build athread system for kernel processes实验要求In this project, The only packageyou will submit is nachos.threads, so don’t add any source files to any otherpackage. The autograderwill not call ThreadedKernel.selfTest() or ThreadedKernel.run(). If there isany kernel initialization you need to do, you should finish it beforeThreadedKernel.initialize() returns. There are somemandatory autograder calls in the KThread code. Leave them as they are. 实验分析Phase1分析:在这一阶段,我们主要实现nachos的代码部分。 1)阅读并理解nachos系统的线程部分。nachos系统实现了线程fork,为同步实现了信号量。同时提供了锁和利用信号量实现的条件变量。 2)可添加适当的类或代码,补充并完善Thread,实现合理正确的同步代码。 Task1.1 KThread.join()实验要求:实现KThread的join方法,注意其它的线程不必调用join(),但是如果 join()被调用的话,也只能被调用一次。对 join()第二次调用的执行结果是没有定义的,即使第二次调用者和第一个不同。无论有没有被 join,一个进程都必须正常结束。 实验分析:线程调用了join,即把时间片从当前线程手中抢了过来。主要作用是使当前线程阻塞。因此join方法需要一个等待队列,存放因其调用而存放的前当前线程。同时,在调用者结束时,要释放(唤醒)自己等待队列中的线程。 例如,在a线程中调用b.join不过是让a排在了b的后面。 在实现过程中需要关中断,避免因时钟中断引起的调度的发生。 实验实现:数据结构: 一个Threadqueue,用作等待队列。 1public ThreadQueue JoinQueue=null; 关键代码如下: 12345678910111213141516public void join() { Lib.debug(dbgThread, \"Joining to thread: \" + toString()); Lib.assertTrue(JoinCount==0);//join只可调用一次 Lib.assertTrue(this != currentThread); JoinCount++; boolean intState = Machine.interrupt().disable();//关中断,notice时钟中断 if(this.JoinQueue==null){ JoinQueue = ThreadedKernel.scheduler.newThreadQueue(true);//若没有新建,则进行新建。 } if (status != statusFinished&status != statusBlocked){ JoinQueue.acquire(this); JoinQueue.waitForAccess(currentThread);//将当前线程加入到等待队列里 KThread.sleep();//当前线程睡眠 } Machine.interrupt().restore(intState);//恢复中断 } finish方法(把等待队列中的线程唤醒) 在finish中添加以下代码 123456789if(currentThread.JoinQueue!=null){ KThread x =currentThread.JoinQueue.nextThread();//唤醒等待队列中的所有线程 while(x!=null) { x.ready(); x=currentThread.JoinQueue.nextThread(); }} 测试代码:123456789101112131415161718192021222324252627282930313233343536373839404142public class join1_1 { public static void JoinTest(){ KThread a=new KThread(new Runnable(){//新建线程a @Override public void run() { // TODO 自动生成的方法存根 for (int i = 0; i < 3; i++) { System.out.println(\"a线程执行第\"+i+\"次\"); } } }); KThread b=new KThread(new Runnable(){//新建线程b @Override public void run() { // TODO 自动生成的方法存根 System.out.println(\"b0在运行\"); System.out.println(\"b1在运行\"); System.out.println(\"a插队b\"); a.join();//a调用join for (int i = 2; i < 5; i++) { System.out.println(\"b\"+i+\"在运行\"); } } }); b.fork(); a.fork(); }} 测试结果: Task1.2 Condition实验要求:直接实现条件变量,通过中断的开启与关闭提供原子性。我们提供一个用信号量的示例实现。你的工作是提供一个对等的实现,而不直接使用信号量(你也可以使用锁,即使他们间接使用信号量)。 一旦完成,您将有两选择实现相同的功能。 您的第二个条件变量的实现必须在类nachos.threads.Condition2中。 实验分析:由于对条件变量和信号量有些遗忘,在做这个实验时,又读了《操作系统的设计与实现》,以期对这些有更深的理解。 这本书在实现管程的过程中提到了条件变量: 管程提供了一种实现互斥的简便途径,但这还不够。我们还需要一种办法以使得进程在无法继续运行时被阻塞。在生产者-消费者问题中,很容易将针对缓冲区满和缓冲区空的测试放到管程的过程中,但是生产者在发现缓冲区满的时候如何阻塞? 解决方法在于引入条件变量(condition variables),及相关的两个操作:WAIT和SIGNAL。当一个管程过程发现它无法继续时(例如,生产者发现缓冲区满),它在某些条件变量上执行WAIT,如full。这个动作引起调用进程阻塞。它也允许另一个先前被挡在管程之外的进程现在进入管程。另一个进程,如消费者,可以通过对其伙伴正在其上等待的一个条件变量执行SIGNAL来唤醒正在睡眠的伙伴进程。为避免管程中同时有两个活跃进程,我们需要一条规则来通知在SIGNAL之后该怎么办。 由此我们知道,条件变量是与对共享资源的原子操作有关。在没有资源时(条件不满足),条件变量使进程沉睡,在有资源时(条件满足),条件变量再唤醒进程。 在我看来,条件变量代表资源有没有,进而使进程沉睡苏醒,而信号量可以表示资源有多少,当信号量值最大为1,信号量只有两个状态,也可以表示资源有没有,这时它和条件变量作用相同。(Condition1即为这般实现)。 实验实现:因为条件变量帮助线程实现沉睡苏醒,因此需要有一个队列,来存储线程。同样需要一个锁,实现原子操作。 主要数据结构: 12private Lock conditionLock;private Queue<KThread> waitQueue; Sleep()方法,在条件不满足(没有资源)时,帮助线程沉睡。 12345678910 public void sleep() {//资源没有,沉睡Lib.assertTrue(conditionLock.isHeldByCurrentThread());boolean preState = Machine.interrupt().disable();//关中断,信号量不需要是因为信号量内有 waitQueue.offer(KThread.currentThread());//将当前线程加入到waitQueue中conditionLock.release();//释放锁,表示不占用资源了, KThread.sleep();//当前进程进入睡眠 conditionLock.acquire();//线程苏醒时再次拿到锁Machine.interrupt().restore(preState);//恢复中断 } Wake()方法,唤醒wait队列中的一个线程 1234567891011public void wake() {//资源拥有时,统一的唤醒操作Lib.assertTrue(conditionLock.isHeldByCurrentThread());boolean status=Machine.interrupt().disable();//关中断KThread thread = waitQueue.poll();if (thread != null) { thread.ready(); }Machine.interrupt().restore(status); } Wakeall()方法,利用wake方法,唤醒wait队列中所有线程 12345678910 public void wakeAll() {Lib.assertTrue(conditionLock.isHeldByCurrentThread());boolean status=Machine.interrupt().disable();//关中断while(waitQueue.peek()!=null){ wake(); //将waitQueue中的所有线程均唤醒} Machine.interrupt().restore(status); } 测试代码:测试代码采用了类似生产者和消费者模型的形式。 主要逻辑: a[资源有无,资源内容]。 线程a1——》有无资源?若无-》沉睡-》若有-》修改资源。 线程a2——》有无资源?若无-》沉睡-》若有-》修改资源。 线程a3——》有无资源?若无-》产生资源-》唤醒a1/a2。 代码实现: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374public class Condition21_2 { Lock lock = new Lock(); Condition2 con = new Condition2(lock); int array[]={0,0};//数组,第一个数表示资源有无,第二个数表示资源内容 public Condition21_2(){ } public void condition2test(){ KThread a1=new KThread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 lock.acquire(); if(array[0]==0){ System.out.println(\"线程a1进入睡眠\"); con.sleep(); } if(array[0]>0){//有资源 array[1]-=500;//消费行为 System.out.println(\"线程a1苏醒\"); System.out.println(\"线程a1修改变量值为\" +array[1]); } lock.release(); } }) ; KThread a2=new KThread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 lock.acquire(); if(array[0]==0){ System.out.println(\"线程a2进入睡眠\"); con.sleep(); } if(array[0]>0){//有资源 array[1]-=500;//消费行为 System.out.println(\"线程a2苏醒\"); System.out.println(\"线程a2修改变量值为\" +array[1]); } lock.release(); } }) ; KThread a3=new KThread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 lock.acquire();//共享变量,应当加上互斥锁 if(array[0]==0){ array[1]=1000;//消费行为 System.out.println(\"线程a3修改变量值为\" +array[1]); array[0]++; System.out.println(\"唤醒所有线程\"); con.wakeAll(); } lock.release(); } }); a1.fork(); a2.fork(); a3.fork(); }} 运行结果: Task1.3 Alarm实验要求:完成Alarm类,通过实现waitUntil方法。一个线程调用waitUntil来挂起直到时间到达now+x,在实际中可用于光标闪烁等。这里并不需要到时间后线程被立刻唤醒,只需它们等待过最少的那个时间即可。不需要新建额外的线程,只需要修改waitUntil方法和计时器的中断处理器。WaitUntil方法不局限于一个线程,任意数量的线程可调用并被挂起。 实验分析:这里让我们实现一个让线程睡眠一段时间后再苏醒的机制。因此会需要一个等待队列,存放睡眠的线程,同时也需要存放它们对应的苏醒时间,以在必要时进行唤醒。 与 Alarm 类有关的是 machine.Timer 类,它在大约每 500 个时钟,滴答使调用回调函数(由 Timer.setInterruptHandler 函数设置)。因此, Alarm类的构造函数中首先要设置该回调函数 Alarm.timerInterrupt()。方法在每一次 timer 产生时间中断时遍历队列,检查队列中的时间状态,当线程到了等待的时间就把线程从队列中取出放入就绪队列。 waitUntil()方法主要让线程睡眠并存储线程和其睡眠时间。它使用了一个队列可以存放线程以及唤醒时间。每次调用时,把当前线程和唤醒时间加入队列,等待唤醒。这里使用LinkedList,方便遍历,移出。 系统响应中断的时机: (1) 中断由中断关闭到中断开启时(即原来是disabled,当执行Interrupt.enable()时,或Interrupt. setStatus(true)时。 (2) Nachos的CPU执行完一条Nachos应用程序指令 上述两种情况下,触发Interrupt.tick()方法对Nachos的时钟进行增量(第一种情况增10,第二种情况增1)。tick()触发checkIfDue(),检查目前有无到期的中断,有则响应所有到期的中断 实验实现:数据结构:一个列表,用于存放线程及其沉睡时间。 1LinkedList<Waiter> Wa; 1234567891011121314151617181920public class Waiter { KThread kt=null; long time=0; public Waiter(KThread kt,long time){ this.kt=kt; this.time=time; } public KThread getKt() { return kt; } public void setKt(KThread kt) { this.kt = kt; } public long getTime() { return time; } public void setTime(long time) { this.time = time; }} WaitUntil方法,让线程沉睡一定时间。 123456789 public void waitUntil(long x) { boolean preStatus = Machine.interrupt().disable(); //系统关中断 long wakeTime = Machine.timer().getTime() + x; //确定唤醒的时间 Waiter x2 = new Waiter(KThread.currentThread(),wakeTime); KThread.currentThread().waketime=wakeTime; Wa.offerLast(x2); //将线程加入到等待队列上 KThread.sleep();//当前线程进入睡眠 Machine.interrupt().restore(preStatus);} TimerInterrupt()方法,在固定时间将线程唤醒。 在这里,我犯过一个错误,开始的时候,我并不是用linkedlist存,而是用队列queue存,由于放入的顺序并没有按苏醒时间排列,因此不能很好的提出所需线程。后来便换了灵活的linkedlist。这里应该算犯了逻辑错误。 1234567891011public void timerInterrupt() { boolean preState = Machine.interrupt().disable();//关中断 for(int i=0;i<Wa.size();i++ ){//这里很不合适用while的话,因为不是每一次都需要把线程拿出来的!!! if(Wa.get(i).getTime()<= Machine.timer().getTime())//当苏醒时间<当前时间 { Wa.get(i).getKt().ready();//线程进入就绪状态 Wa.remove(i); } } Machine.interrupt().restore(preState);//恢复中断} 测试代码:12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849public class Alarm1_3 { public static void alarmTest(){ Alarm x=new Alarm(); KThread a1=new KThread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 System.out.println(\"a1线程开始运行……\"); System.out.println(\"a1线程运行第1次\"); System.out.println(\"a1线程进入休眠||时间为:\"+Machine.timer().getTime()+\"||应在\"+(Machine.timer().getTime() + 800)+\"醒来.\"); x.waitUntil(800); //让a1睡眠; System.out.println(\"唤醒线程:a1\"+\"||时间为:\"+Machine.timer().getTime()); for(int i=2;i<=4;i++){ System.out.println(\"a1线程运行第\"+i+\"次\"); } } }); a1.setName(\"a1\"); KThread b1=new KThread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 System.out.println(\"b1线程开始运行……\"); System.out.println(\"b1线程运行第1次\"); System.out.println(\"b1线程运行第2次\"); System.out.println(\"b1线程休眠||时间为:\"+Machine.timer().getTime()+\"||应在\"+(Machine.timer().getTime() + 340)+\"醒来.\"); x.waitUntil(340); //让b1睡眠; System.out.println(\"唤醒线程:b1\"+\"||时间为:\"+Machine.timer().getTime()); for(int i=3;i<=4;i++){ System.out.println(\"线程b1正在运行第\"+i+\"次循环\"); } } }); a1.setName(\"a1\"); b1.setName(\"b1\"); a1.fork(); b1.fork(); }} 实验结果:","categories":[{"name":"操作系统","slug":"操作系统","permalink":"https://yongbosmart.github.io/categories/操作系统/"}],"tags":[{"name":"操作系统","slug":"操作系统","permalink":"https://yongbosmart.github.io/tags/操作系统/"},{"name":"nachos","slug":"nachos","permalink":"https://yongbosmart.github.io/tags/nachos/"}]},{"title":"nachos介绍","slug":"nachos介绍","date":"2018-01-31T16:00:00.000Z","updated":"2018-05-05T08:24:28.444Z","comments":true,"path":"2018/02/01/nachos介绍/","link":"","permalink":"https://yongbosmart.github.io/2018/02/01/nachos介绍/","excerpt":"","text":"实验目的深入理解计算机操作系统,对上学期操作系统知识进行复习回顾,同时理解操作系统在实际中的应用。对线程(包括同步、信号量、调度)、文件系统(包括分页、文件创建等操作)有进一步的理解和认识。 nachos介绍Nachos的全称是“Not Another Completely Heuristic Operating System”,它是一个可修改和跟踪的操作系统教学软件。它给出了一个支持多线程和虚拟存储的操作系统骨架,可让学生在较短的时间内对操作系统中的基本原理和核心算法有一个全面和完整的了解。 nachos系统及部分源码分析 Nachoes 包整体分析: nachos.ag 包提供了能够自动分层 nachoes 项目的类;nachoes.machine 包提供了实现 nachoes 虚拟机的类;nachoes.security 包主要是用来保护 nachoes 的内核不受到破坏;nachoes.thread 包主要提供了用来实现 nachoes 多线程内核的类;nachoes.userprog 包主要是提供了允许 nachoes 在单独的地址空间中加载和执行单线程用户程序的一些类;nachoes.vm 主要是用来让 nachoes 运行的进程按需求分页,利用一些硬件 TLB 来地址上的一些转换。 nachos线程: 一个KThread对象代表一个Nachos线程,线程级的概念与操作,是在KThread类中体现的,如:线程的创建 (KThread.fork())线程的就绪(KThread.ready())线程的睡眠(KThread.sleep())线程的状态与转换线程的上下文切换(sleep和yield可引起)一个Nachos线程由三部分组成,一个KThread对象,与Kthread对象对应的TCB对象,与TCB对象绑定的Java 线程。java thread、TCB对象、KThread对象之间是一一映射关系。关于Nachos线程的创建KThread NachosThread=new KThread(new KThreadTarget);NachosThread.fork();KThreadTarget是一个Runnable对象,其run()中代码是子线程的执行体。 KThread主要方法及功能解析: currentThread():静态方法,实现返回当前KThread类型线程的操作。KThread():构造方法。实现判断当前线程是否为空。如果currentThread不为空,则分配一个新的TCB空间;否则,该方法会创建一个新的线程等待队列,将新线程加入等待队列中,将新线程设为当前线程,将tcb指向当前TCB空间,并修改新线程的状态(会由等待改变为运行状态),检查toBeDestory值等一系列线程运行前的准备工作。KThread(Runnabletarget):构造方法,创建一个新的KThread,并传递给它一个对应的java线程。setTarget(Runnabletarget):为KThread设置要运行它的对应的java线程。fork():创建线程的方法。先运行关中断操作,然后对应的tcb启动一个java线程,调用runThread方法,然后将其加入等待队列中。runThread():线程运行。先调用begin()方法,然后让KThread对应的java线程run()方法,最后调用finish()方法,结束线程。begin():调用了restoreState()方法,该方法实现了KTread运行前的一系列准备工作。finish():该方法实现了当当前线程运行完成后,将等待队列中下一个线程拿出。yield():静态方法,将当前线程加入等待队列,引起调度。sleep():静态方法,拿出等待队列中的下一个进程即将运行,将当前线程进入等待队列中,引起调度。ready():设置当前线程状态为等待,加入就绪队列。creatIdleThread()方法:创建一个闲逛线程,永远不会被阻塞,只有当其他所有线阻塞时才运行,永远不会被加入到等待队列。runNextThread()方法:取出等待队列中的下一个线程并使其运行。run()方法:先将当前线程加入就绪队列,调用savaState方法,准备放弃CPU。调用run方法的线程设为当前线程,tcb进行上下文切换,调用resoreState方法,使当前线程做运行前的准备工作。 nachos中锁的使用: nachos中提供锁,以实现同步等。 Lock类主要方法如下:acquire():若当前线程没有持有锁,可原子地得到锁。关中断。如果锁持有者不为空,则将当前线程加入waitQueue,否则取出当前线程让他得到锁。开中断。release():释放锁,即让waitQueue里的下一个线程得到锁,并将其加入等待队列。isHeldByCurrentThread():判断当前线程是否持有锁。 nachos中的中断系统: nachos在初始化时会创建interrupt controller(中断控制器),对外部提供开关中断的接口,供其它程序调用,保证程序原子性。外部程序可以调用中断控制器提供的接口开关中断(disable()与enable())当为一个设备设置中断时,可在该设备的类中调用Interrupt.schedule(),该方法需要三个参数 (when:该中断何时到期可以响应;type: 中断类型;handler: 响应该中断时执行的中断处理程序)。nachos的中断不是实时响应,而是在设置中断时提供一个时间参数time,从设置中断开始的time时刻后,该中断到期,可以响应该中断。开关中断操作:开中断: public boolean enable() { setStatus(true); }关中断: public boolean disable() {setStatus(false); }Nachos中断控制器模拟了一个时钟,该时钟从nachos启动时开始计数(ticks),作为nachos的系统时间。当nachos模拟的CPU执行完一条指令,ticks=ticks+1,当中断状态从disabled转到enabled,ticks+10 。系统调用 checkIfDue 来执行中断查询,在hander中安排调度。涉及类:Machine.Interrupt模仿底层的中断硬件,提供了方法setStatus,来设置能够或不能够中断。记录所有硬件设备可能引起的行为将发生的中断和什么时间他们可能出现。这个模块也记录模拟的节拍,下面情况发生时节拍增长:之前是关中断,开了中断后+10;每秒百万条指令已经执行后+1。中断(包括时间片上下文切换)不可以出现在开中断时的任何代码处。但只在模拟节拍前进时可以,以便它可以成为模拟硬件的节拍来调用中断控制。这意味着错误的同步代码可以在这个模拟硬件上运行(甚至在随意的时间片),但是却不能在真正的硬件上运行。即使nachos不能一直察觉到你的程序在现实中是错的,你也应该写正确的同步代码。Machine.Timer该类是计时器类,控制着系统时间和计时器中断。其核心是Timer.scheduleInterrupt(),与 Timer.timerInterrupt()相互作用维持计时器中断。Timer.scheduleInterrupt()中获取Status.TimerTicks 来安排未来的调度时间(也就是调度间隔)并利用随机数制造调度时间的不稳定性(0~25),当 timerInterrput()被执行,会再次执行 Timer.scheduleInterrupt()安排下次的调度,而 timerInterrrput()中会执行 Timer.handler 的 run 方法,这个handler 是由 setHandler 传入的定时器中断。至于 AutoGraderInterrupt,虽然 delay为 1,但是由于其 scheduleInterrupt 安排在 timerIntterupt 中,也是每 Stats.TimerTicks执行一次,该中断处理程序会对权限进行检查,保证调度器有权限来调度。 nachos中的文件系统: Nachos 文件系统都实现了 FileSystem 接口,提供打开文件和删除文件的方法。所有的文件都要实现 OpenFile 类,作为文件系统返回的文件。OpenFile 定义了Nachos的一个文件及对文件的相关操作,类似于Java中的File类。Nachos系统利用java的File实现nachos文件系统的open(), remove(), read(), write(), close()具体实现。 Userprocess类:包括一个用户进程,一个文件表,以及有关正在执行的程序的信息。execute方法方法:根据文件名和参数,用指定的参数来执行可执行文件。readVirtualMemoryString方法:从进程的虚拟内存中读取内容并转化成字符串的格式。intreadVirtualMemor方法:将该进程的虚拟内存中的数据传到指定的数组中。 Userkernel类:继承自ThreadedKernel类,实现一个内核支持多个用户进程,(实现多道程序设计。)Initialize初始化一个内核,创建一个同步控制台;selftest()进行自检,控制台是否运行正常;UserProcesscurrentProcess()用来获得当前进程;run()方法是通过创造一个进程或是在里面运行一个脚本程序来开始运行用户程序,而这个必须通过运行的脚本程序的名字是靠Machine.getShellProgramName()返回的;terminate()方法就是用来停止内核不返回。 SynchConsole类:为机器的控制台提供一个简单的同步的接口,而这个接口也可以被OpenFile的对象访问。","categories":[{"name":"操作系统","slug":"操作系统","permalink":"https://yongbosmart.github.io/categories/操作系统/"}],"tags":[{"name":"操作系统","slug":"操作系统","permalink":"https://yongbosmart.github.io/tags/操作系统/"},{"name":"nachos","slug":"nachos","permalink":"https://yongbosmart.github.io/tags/nachos/"}]}]}