Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

38.开源项目--git常用命令总结


39.开源项目——git自我总结

$
0
0

 终于把git一整套基础学完,感谢王利涛老师的优秀视频,希望我的笔记能够帮助网友们。

作者:aixiaoxiaoyu 发表于2017/11/24 21:47:31 原文链接
阅读:45 评论:0 查看评论

IOS中NSArray的4种遍历方式

$
0
0

objective-c 语言 数组遍历的4种方式:1、普通for循环;2、快速for循环;3、特性block方法;4、枚举方法。

一. for循环

Student *stu = [Student student];  
NSArray *array = [NSArray arrayWithObjects:stu, @"1",@"2",nil];  
int count = array.count;//减少调用次数  
for( int i=0; i<count; i++){  
    NSLog(@"%i-%@", i, [array objectAtIndex:i]);  
}  

二. 增强for

for(id obj in array){  
    NSLog(@"%@",obj);  
}  

三. 迭代器

NSEnumerator *enumerator = [array objectEnumerator];  
id obj = nil;  
while(obj = [enumerator nextObject]){  
    NSLog(@"obj=%@",obj);  
} 

四. Block块遍历

[array enumeratorObjectsUsingBlock:  
^(id obj, NSUInteger index, BOOL  *stop){  
    NSLog(@"%i-%@",index,obj);  
    //若终断循环  
    *stop = YES;  
}];  
作者:gsg8709 发表于2017/11/24 15:55:16 原文链接
阅读:119 评论:0 查看评论

使用Sklearn模型做分类并绘制机器学习模型的ROC曲线

$
0
0

    简单的实验,主要是使用sklearn库中的RFR模型来进行回归分析并绘制相应的ROC曲线,主要是熟悉流程,下面是具体的实现:


#!usr/bin/env python
#encoding:utf-8
'''
__Author__:沂水寒城
功能:使用RFR模型
'''

import csv
from sklearn.metrics import roc_curve, auc
from sklearn.ensemble import RandomForestRegressor
import matplotlib.pyplot as plt 



def RFR_model_train(X_list, y_list):
    '''
    随机森林回归模型
    '''
    model=RandomForestRegressor(max_depth=10, random_state=0)
    model.fit(X_list, y_list)
    y_predict=model.predict(X_list)
    draw_ROC_curve(y_list,y_predict,savepath='RFR.png')
    feature_weight=model.feature_importances_
    print 'feature_weight'
    print feature_weight
    return feature_weight


def draw_ROC_curve(y_test,y_predict,savepath):
    '''
    画ROC曲线
    '''
    false_positive_rate,true_positive_rate,thresholds=roc_curve(y_test, y_predict)
    roc_auc=auc(false_positive_rate, true_positive_rate)
    plt.title('ROC')
    plt.plot(false_positive_rate, true_positive_rate,'b',label='AUC = %0.2f'% roc_auc)
    plt.legend(loc='lower right')
    plt.plot([0,1],[0,1],'r--')
    plt.ylabel('TPR')
    plt.xlabel('FPR')
    plt.savefig(savepath)
    plt.close(0)


if __name__ == '__main__':
    RFR_model_train(x_list, y_list)



结果如下:


feature_weight
[ 0.12049628  0.          0.          0.          0.05872002  0.
  0.08056693  0.          0.43856094  0.27010771  0.          0.
  0.03154812]

ROC曲线如下:



作者:Together_CZ 发表于2017/11/24 22:24:15 原文链接
阅读:79 评论:0 查看评论

BZOJ1087(SCOI2005)[互不侵犯King]--状压DP

$
0
0

【链接】
bzoj1087

【解题报告】

。。。状压DP。。。

定义f[i][j][k]表示目前推到i行,第i行的放置情况为j,目前一共放了k个棋子。

再预处理一下行与行之间可以放的情况就行了。

#include<cstdio>
#include<cstring>
#define LL long long
using namespace std;
const int maxn=515;
int n,m,S,num[maxn];
LL ans,f[10][maxn][82];
bool vis[maxn][maxn];
int main()
{
    freopen("1087.in","r",stdin);
    freopen("1087.out","w",stdout);
    scanf("%d%d",&n,&m); S=1<<n;
    memset(vis,0,sizeof(vis));
    for (int i=0; i<S; i++)
     for (int j=0; j<S; j++)
      {
        for (int k=1; k<=n; k++)
         if (j&(1<<k-1))
          if ((k>1&&(j&(1<<k-2)))||(k<n&&(j&(1<<k)))||(k>1&&(i&(1<<k-2)))||(k<n&&(i&(1<<k)))||(i&(1<<k-1))) {vis[i][j]=1; break;}
      }
    memset(num,0,sizeof(num));
    for (int i=0; i<S; i++)
     for (int j=1; j<=n; j++)
      if (i&(1<<j-1)) num[i]++;
    memset(f,0,sizeof(f));
    f[0][0][0]=1;
    for (int i=1; i<=n; i++)
     for (int j=0; j<S; j++)
      for (int k=0; k<S; k++)
       if (!vis[j][k])
        for (int s=0; s<=m; s++)
         if (s+num[k]<=m) f[i][k][s+num[k]]+=f[i-1][j][s];
    ans=0;
    for (int i=0; i<S; i++) ans+=f[n][i][m];
    printf("%lld",ans);
    return 0; 
}
作者:CHNWJD 发表于2017/11/25 8:59:19 原文链接
阅读:89 评论:0 查看评论

从零开始前端学习[54]:js中自定义标签属性和自定义属性

$
0
0

js中自定义标签属性和自定义属性

  • 自定义标签属性
  • 自定义属性

提示
博主:章飞_906285288
博客地址:http://blog.csdn.net/qq_29924041


自定义标签属性

首先要知道什么是标签属性,标签属性也就是写在标签内部的属性,如class,id等
那什么是自定义的标签属性,也就是在标签内部的非系统定义的属性,如

<div class = "box" name = "nBox"></div>
对应的这个name = "nBox"也就是自定义标签属性。

对于自定义标签属性的相关操作如下标所示:

函数 函数含义
setAttribute([string]name,[string]value) 自定义标签属性的设置
getAttribute([string]name); 获取自定义标签属性
removeAttribute([string]name) 自定义标签属性的移除

自定义标签属性其实就是写在标签里面的原来没有的属性。

自定义属性

那与自定义标签属性相互对应的就是自定义属性,什么是自定义属性呢??自定义属性可以认为是原来对象没有的属性,主要是针对javascript来说的在javascript的原来的对象中,原来是没有这样的属性的,但是我们可以通过外部增加的这样一种方式来添加这样的一种属性

如下所示:
<p>自定义属性</p>
<script>
    var aP = document.getElementByTagName("p");
        aP [0].onclick = function () {
        aP[0].index_number = 3;
      aP[0].custom_index = 4;
  console.log("aP[0].index_number:"+aP[0].index_number);
  console.log("aP[0].custom_index"+  aP[0].custom_index)
}

</script>

index_number和custom_index其实是标签对象原来没有的属性,但是我们在js中通过给其设置这样的一种属性的形式,然后让其具有这样的一种属性形式。

代码演示部分:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <meta charset="UTF-8"><!--申明当前网页的编码集UTF-8-->
  <meta name="Generator" content="EditPlus®">   <!--编辑器的名称-->
  <meta name="Author" content="作者是谁">       
  <meta name="Keywords" content="关键词">
  <meta name="Description" content="描述和简介">
  <style type="text/css">                                        
        body,dl,dd,dt,p,h1,h2,h3,h4,h5,h6{ margin: 0;}
        ul,ol{margin: 0; list-style: none; padding: 0;}
        a{ text-decoration: none; }
        *{ margin: 0; padding: 0; }
    .main{width: 1200px;margin: 10px auto;box-shadow: 0 0 10px 0 deeppink;border: 1px solid transparent}
    p{width: 150px;height: 150px;background: greenyellow;margin: 5px;line-height: 150px;text-align: center}
  </style>
</head>
<body>
  <div class="main">
    <p class="attribute" comebaby = "comebaby" attribute = "123445">
        attribute
    </p>
    <p class="custom">
      custom
    </p>
  </div>
  <script>
    var aP = document.getElementsByTagName("p");
    aP[0].onclick = function () {
        this.getAttribute("comebaby");
        aP[0].number_index = 0;
        aP[0].style.backgroundColor = "red";

        console.log("aP[0].number_index:" + aP[0].number_index);
        console.log("aP[0].getAttribute(comebaby)"+this.getAttribute("comebaby"));
        console.log("aP[0].getAttribute(attribute)"+this.getAttribute("attribute"));
        this.setAttribute("attribute","2233456");
        console.log("aP[0].setAttribute(attribute)"+this.getAttribute("attribute"));
    }
    aP[1].onclick = function () {
      aP[1].index_number = 3;
      aP[1].custom_index = 4;
      aP[1].style.backgroundColor = "blue";
      console.log("aP[1].index_number:"+aP[1].index_number);
      console.log("aP[1].custom_index"+  aP[1].custom_index)
    }
  </script>
</body>
</html>

显示效果如下所示:

这里写图片描述

作者:qq_29924041 发表于2017/11/25 9:01:28 原文链接
阅读:95 评论:0 查看评论

centos(6)-目录和文件

$
0
0

目录和文件的操作是centos必备的基础知识,本篇主要介绍其相关命令。

查看文件和目录 ls

ls:查看当前目录下的文件。

ls -l:以列表方式显示,每行显示一个文件的详细信息。

ll:是ls -l命令的别名,显示结果是一样的。

ll -a:相当于ls -la,同时包括-l和-a,-a的意思是显示隐藏文件。创建文件时前面加一个点,自动就是隐藏文件。


更多用法查看ls --help。

符号目录

. 代表当前目录

.. 代表上级目录

~  代表当前用户的主目录

/  代表根目录

目录命令

cd:代表切换目录,红框可以看出当前所切换到的目录。

pwd:查看当前目录路径。
mkdir dir:创建一个目录,名为dir。
rmdir dir:删除dir目录,注意,这个命令只能删除空目录。

创建、复制、移动、删除

touch file:创建一个没有内容的空文件,名字为file。
cp file1 file2:复制file1文件到file2。
cp -r dir1 dir2:复制dir1目录及内容到dir2,目录比文件多一个-r选项。
rm file:删除file文件。
rm -r dir:删除dir目录及内容,目录比文件多一个-r选项。注意,这个命令会删除目录下的所有内容,并会出现警告提示。
rm -rf dir:比上面多了一个f选项,代表忽略警告提示,直接删除
mv file1或dir1 file2或dir2:将文件或目录移动到新的位置,相当于window中的剪切。也可用于重命名。

查看文件内容

cat -n file:查看file文件内容,注意,这里只谈内容是文本格式的文件。-n是显示行号,默认不显示。
tac file:把cat命令反过来拼,也就是从最后一行到第一行,倒着显示内容。
less file:进入查看模式,当文件内容很长时,会根据当前窗口大小进行分页,先显示第一页。在此模式可以输入以下命令:
上下键:可以上下滚动显示
上下翻页键:可以上下翻页。
q:退出查看模式,回到命令行。

vi 基本用法

在winscp中可以双击打开文件,直接在window中编辑。但是并不建议这样做,因为有可能会将window相关环境也带入文件中,可能会破坏centos文件的原有格式,报一些莫名的bug,因此可以用vi命令来编辑文件。

vi file:如果不存在file文件,则创建file并进入这个文件的vi模式。如果存在file文件,则直接进入这个文件的vi模式。

此时输入i会进入编辑模式,左下角会显示进入insert编辑模式。即可通过键盘修改文件内容,通过上下左右键和上下翻页键可以进行定位。


修改完成后按Esc键,上图的INSERT标志会消失。此时输入命令可以保存修改或放弃修改。

:wq:保存并且退出文件。左下角会有相应显示

:q!:不保存退出文件。

:w:保存不退出。


作者:wangb_java 发表于2017/11/25 9:58:20 原文链接
阅读:59 评论:0 查看评论

Educational Codeforces Round 33 (Rated for Div. 2) A-C题解

$
0
0

靠着写CF的题解艰难维持博客浏览量,不过涨分了还是蛮高兴的。最后一秒过C题也是相当刺激。

A. Chess For Three
time limit per test
1 second
memory limit per test
256 megabytes
input
standard input
output
standard output

Alex, Bob and Carl will soon participate in a team chess tournament. Since they are all in the same team, they have decided to practise really hard before the tournament. But it's a bit difficult for them because chess is a game for two players, not three.

So they play with each other according to following rules:

  • Alex and Bob play the first game, and Carl is spectating;
  • When the game ends, the one who lost the game becomes the spectator in the next game, and the one who was spectating plays against the winner.

Alex, Bob and Carl play in such a way that there are no draws.

Today they have played n games, and for each of these games they remember who was the winner. They decided to make up a log of games describing who won each game. But now they doubt if the information in the log is correct, and they want to know if the situation described in the log they made up was possible (that is, no game is won by someone who is spectating if Alex, Bob and Carl play according to the rules). Help them to check it!

Input

The first line contains one integer n (1 ≤ n ≤ 100) — the number of games Alex, Bob and Carl played.

Then n lines follow, describing the game log. i-th line contains one integer ai (1 ≤ ai ≤ 3) which is equal to 1 if Alex won i-th game, to 2if Bob won i-th game and 3 if Carl won i-th game.

Output

Print YES if the situation described in the log was possible. Otherwise print NO.

Examples
input
3
1
1
2
output
YES
input
2
1
2
output
NO
Note

In the first example the possible situation is:

  1. Alex wins, Carl starts playing instead of Bob;
  2. Alex wins, Bob replaces Carl;
  3. Bob wins.

The situation in the second example is impossible because Bob loses the first game, so he cannot win the second one.

就是三个小屁孩,没事下棋玩。1和2先比,输了的当旁观者,然后胜者跟旁观者比,问给出的人数的序列是否是合法的。

想都不用想,模拟。。。。我想了好久好久。

代码实现:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<cstdio>
#define ll long long
#define mset(a,x) memset(a,x,sizeof(a))

using namespace std;
const double PI=acos(-1);
const int inf=0x3f3f3f3f;
const double esp=1e-6;
const int maxn=1e6+5;
const int mod=1e9+7;
int dir[4][2]={0,1,1,0,0,-1,-1,0};
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}
ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}

int main()
{
	int map[1000],n,i,j,k;
	int x=3;
    	int a=1;
    	int b=2;
	cin>>n;
	for(i=0;i<n;i++)
	{
		cin>>k;
		if(k==x)
		{
			cout<<"NO"<<endl;
			return 0;
		}
		if(k==a)
		swap(b,x);
		else
		swap(a,x);
	}
	cout<<"YES"<<endl;
	return 0;
}

B. Beautiful Divisors
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Recently Luba learned about a special kind of numbers that she calls beautiful numbers. The number is called beautiful iff its binary representation consists of k + 1 consecutive ones, and then k consecutive zeroes.

Some examples of beautiful numbers:

  • 12 (110);
  • 1102 (610);
  • 11110002 (12010);
  • 1111100002 (49610).

More formally, the number is beautiful iff there exists some positive integer k such that the number is equal to (2k - 1) * (2k - 1).

Luba has got an integer number n, and she wants to find its greatest beautiful divisor. Help her to find it!

Input

The only line of input contains one number n (1 ≤ n ≤ 105) — the number Luba has got.

Output

Output one number — the greatest beautiful divisor of Luba's number. It is obvious that the answer always exists.

Examples
input
3
output
1
input
992
output
496

给出一个美丽数字的定义,也就是二进制中前k+1位是1,后k位是0,问n的因子中最大符合条件的数是多少。

打表发现到10^5就8个这种数,然后我就优化后模拟,一直WA test4,然后干脆暴力,过了。无话可说。


代码实现:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<cstdio>
#define ll long long
#define mset(a,x) memset(a,x,sizeof(a))

using namespace std;
const double PI=acos(-1);
const int inf=0x3f3f3f3f;
const double esp=1e-6;
const int maxn=1e6+5;
const int mod=1e9+7;
int dir[4][2]={0,1,1,0,0,-1,-1,0};
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}
ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}
int ans[]={1,6,28,120,496,2016,8128,32640,130816,523776,2096128,8386560,33550336,134209536,536854528,2147450880};

int main()
{
	int i,j,k=0,sum=0,a=0,n;
	while(cin>>n)
	{
		int maxx=0;
		for(i=1;i<=n;i++)
		{
			if(n%i==0)
			{
				for(j=0;j<10;j++)
				{
					if(i==ans[j])
					{
						maxx=max(maxx,i);
						break;
					}
				}
			}
		}
		cout<<maxx<<endl;
	}
	return 0;
}


C. Rumor
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Vova promised himself that he would never play computer games... But recently Firestorm — a well-known game developing company — published their newest game, World of Farcraft, and it became really popular. Of course, Vova started playing it.

Now he tries to solve a quest. The task is to come to a settlement named Overcity and spread a rumor in it.

Vova knows that there are n characters in Overcity. Some characters are friends to each other, and they share information they got. Also Vova knows that he can bribe each character so he or she starts spreading the rumor; i-th character wants ci gold in exchange for spreading the rumor. When a character hears the rumor, he tells it to all his friends, and they start spreading the rumor to their friends (for free), and so on.

The quest is finished when all n characters know the rumor. What is the minimum amount of gold Vova needs to spend in order to finish the quest?

Take a look at the notes if you think you haven't understood the problem completely.

Input

The first line contains two integer numbers n and m (1 ≤ n ≤ 105, 0 ≤ m ≤ 105) — the number of characters in Overcity and the number of pairs of friends.

The second line contains n integer numbers ci (0 ≤ ci ≤ 109) — the amount of gold i-th character asks to start spreading the rumor.

Then m lines follow, each containing a pair of numbers (xi, yi) which represent that characters xi and yi are friends (1 ≤ xi, yi ≤ n,xi ≠ yi). It is guaranteed that each pair is listed at most once.

Output

Print one number — the minimum amount of gold Vova has to spend in order to finish the quest.

Examples
input
5 2
2 5 3 4 8
1 4
4 5
output
10
input
10 0
1 2 3 4 5 6 7 8 9 10
output
55
input
10 5
1 6 2 7 3 8 4 9 5 10
1 2
3 4
5 6
7 8
9 10
output
15
Note

In the first example the best decision is to bribe the first character (he will spread the rumor to fourth character, and the fourth one will spread it to fifth). Also Vova has to bribe the second and the third characters, so they know the rumor.

In the second example Vova has to bribe everyone.

In the third example the optimal decision is to bribe the first, the third, the fifth, the seventh and the ninth characters.


这个题的意思就是输入n个数,然后输入m对数,每对数之间的交流是没有花费的,问最后最小花费是多少。

可以并查集找所有有关系的中最小的一个,还可以dfs求联通块,维护最小值。最后一秒钟提交,真是刺激。

代码实现:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<cstdio>
#define ll long long
#define mset(a,x) memset(a,x,sizeof(a))

using namespace std;
const double PI=acos(-1);
const int inf=0x3f3f3f3f;
const double esp=1e-6;
const int maxn=1e6+5;
const int mod=1e9+7;
int dir[4][2]={0,1,1,0,0,-1,-1,0};
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}
ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}
int pre[1000005];
int map[maxn],ans[maxn];
int find(int x){
    int r=x;
    while (pre[r]!=r){
        r=pre[r];
    }
    int i=x;
    int temp;
    while (i!=r){
        temp=pre[i];
        pre[i]=r;
        i=temp;
    }
    return r;

}
void join(int x,int y){
    int fx=find(x);
    int fy=find(y);
    if(fx!=fy){
        pre[fx]=fy;
    }
}


int main()
{
	int n,m,i,j,k,aa,bb;
	while(cin>>n>>m)
	{
		for(i=1;i<=n;i++)
		cin>>map[i];
		for (int i=1;i<=n;i++) pre[i]=i;
		for (int i=0;i<m;i++)
		{
	        scanf ("%d%d",&aa,&bb);
	        join(aa,bb);
    	}
		mset(ans,inf);
		for(i=1;i<=n;i++)
		{
			int x=find(i);
			ans[x]=min(ans[x],map[i]);
		}
		ll sum=0;
		for(i=1;i<=n;i++)
		{
			if(ans[i]<inf)
			sum+=ans[i];
		}
		cout<<sum<<endl;
	}
}


作者:Ever_glow 发表于2017/11/25 10:02:50 原文链接
阅读:92 评论:0 查看评论

BZOJ1089(SCOI2003)[严格n元树]--递推+高精度

$
0
0

【链接】
bzoj1089

【解题报告】

定义fi表示目前i层的方案数。

fi=(fi1)n+1

所以答案就是fdfd1

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=30,maxv=5005;
int n,m;
struct Bignum
{
    int s[maxv];
    Bignum operator + (const Bignum &a) const{
        Bignum c;
        memset(c.s,0,sizeof(c));
        c.s[0]=max(s[0],a.s[0]);
        for (int i=1; i<=c.s[0]; i++) c.s[i]+=s[i]+a.s[i],c.s[i+1]+=c.s[i]/10,c.s[i]%=10;
        while (c.s[c.s[0]+1]) c.s[0]++;
        return c;
    }
    Bignum operator - (const Bignum &a) const{
        Bignum c;
        memset(c.s,0,sizeof(c));
        c.s[0]=s[0];
        for (int i=1; i<=c.s[0]; i++)
        {
            c.s[i]+=s[i]-a.s[i];
            if (c.s[i]<0) c.s[i]+=10,c.s[i+1]--;
        }
        while (c.s[0]>1&&!c.s[c.s[0]]) c.s[0]--;
        return c;
    }
    Bignum operator * (const Bignum &a) const{
        Bignum c;
        memset(c.s,0,sizeof(c));
        c.s[0]=s[0]+a.s[0];
        for (int i=1; i<=s[0]; i++)
         for (int j=1; j<=a.s[0]; j++) c.s[i+j-1]+=s[i]*a.s[j];
        for (int i=1; i<=c.s[0]; i++) c.s[i+1]+=c.s[i]/10,c.s[i]%=10;
        while (c.s[c.s[0]+1]) c.s[0]++;
        while (c.s[0]>1&&!c.s[c.s[0]]) c.s[0]--;
        return c;
    }
    Bignum operator ^ (int b) const{
        Bignum c,a;
        for (int i=0; i<=s[0]; i++) a.s[i]=s[i];
        memset(c.s,0,sizeof(c.s));
        c.s[0]=c.s[1]=1;
        for (; b; b>>=1,a=a*a)
         if (b&1) c=c*a;
        return c;
    }
    void Write()
    {
        for (int i=s[0]; i; i--) putchar(s[i]+48);
    }
}f[maxn],a1;
int main()
{
    freopen("1089.in","r",stdin);
    freopen("1089.out","w",stdout);
    scanf("%d%d",&n,&m);
    if(m==0) {printf("1"); return 0;}
    a1.s[0]=f[0].s[0]=a1.s[1]=f[0].s[1]=1;
    for (int i=1; i<=m; i++)
     f[i]=(f[i-1]^n)+a1;
    (f[m]-f[m-1]).Write();
    return 0;
}
作者:CHNWJD 发表于2017/11/25 10:52:04 原文链接
阅读:34 评论:0 查看评论

《剑指offer》刷题笔记(时间空间效率的平衡):第一个只出现一次的字符

$
0
0

《剑指offer》刷题笔记(时间空间效率的平衡):第一个只出现一次的字符



题目描述

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置

解题思路

我们首先想到的可能就是遍历数组,对每一个字符串再遍历判断是否唯一,这样的时间复杂度为O(n^2)。这显然不是我们想要的。

然后我们可能会想到,为什么不用hash表呢?首先遍历数组建立hash表,key为字符串,value为该字符串出现的次数。然后再遍历hash,找到里面value为1的key就可以了。

C++中,我们可以用数组实现,此时,数组长度设定为256,对应字符串的ASCII码;也可以用map实现。
Python中,我们可以用字典实现,但是很耗空间。

C++版代码实现

数组

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.size() == 0)
            return -1;
        char ch[256] = {0};
        for(int i=0; i < str.size(); ++i)
            ch[str[i]]++;
        for(int i=0; i < str.size(); ++i){
            if(ch[str[i]] == 1)
                return i;
        }
        return 0;
    }
};

map

class Solution {
public:
    int FirstNotRepeatingChar(string str) {
        if(str.size() == 0)
            return -1;
        map<char, char> mp;
        for(int i=0; i < str.size(); ++i)
            mp[str[i]]++;
        for(int i=0; i < str.size(); ++i){
            if(mp[str[i]] == 1)
                return i;
        }
        return 0;
    }
};

Python版代码实现

# -*- coding:utf-8 -*-
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if len(s) == 0:
            return -1
        cur = {}
        for i in range(len(s)):
            if s[i] not in cur.keys():
                cur[s[i]] = 1
            else:
                cur[s[i]] += 1
        for i in range(len(s)):
            if cur[s[i]] == 1:
                return i
        return 0

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

作者:u011475210 发表于2017/11/25 11:00:01 原文链接
阅读:44 评论:0 查看评论

BZOJ1188: [HNOI2007]分裂游戏(洛谷P3185)

$
0
0

SG函数

BZOJ题目传送门
洛谷题目传送门

把一堆豆子分成独立的一颗一颗的豆子。那么在第i堆的一颗豆子就被放到了第j堆和第k堆中,即sg[i]=sg[j] xor sg[k] (i<jk)
那么一堆豆子(a[i])个的sg值就是sg[a[i]] xor n次。
然后就和普通的一样做了。

因为这道题是把所有豆子移到最后一堆,所以不妨把sg值倒着算。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 26
using namespace std;
int sg[N],a[N],f[N*N],n,t;
int main(){
    for (int i=1;i<=N;i++){
        for (int j=i-1;j;j--)
            for (int k=j;k;k--)
                f[sg[j]^sg[k]]=i;
        while (f[sg[i]]==i) sg[i]++;
    }
    scanf("%d",&t);
    while (t--){
        scanf("%d",&n); int flag=0;
        for (int i=n;i;i--)
            scanf("%d",&a[i]);
        for (int i=n;i;i--)
            if (a[i])
                for (int j=i-1;j;j--)
                    for (int k=j;k;k--){
                        a[i]--; a[j]++; a[k]++;
                        int ans=0;
                        for (int p=1;p<=n;p++)
                            if (a[p]&1) ans^=sg[p];
                            else ans^=0;
                        if (!ans){
                            if (!flag) printf("%d %d %d\n",n-i,n-j,n-k);
                            flag++;
                        }
                        a[i]++; a[j]--; a[k]--;
                    }
        if (!flag)
            printf("-1 -1 -1\n");
        printf("%d\n",flag);
    }
    return 0;
}
作者:a1799342217 发表于2017/11/25 11:02:33 原文链接
阅读:35 评论:0 查看评论

大数据的一些相关知识介绍

$
0
0

什么是大数据

   大数据(big data),指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。
   大数据的定义是4Vs:Volume、Velocity、Variety、Veracity。用中文简单描述就是大、快、多、真。
  • Volume —— 数据量大

    随着技术的发展,人们收集信息的能力越来越强,随之获取的数据量也呈爆炸式增长。例如百度每日处理的数据量达上百PB,总的数据量规模已经到达EP级。
    
  • Velocity —— 处理速度快

    指的是销售、交易、计量等等人们关心的事件发生的频率。2017年双11,支付成功峰值达25.6万笔/秒、实时数据处理峰值4.72亿条/秒。
    
  • Variety —— 数据源多样

    现在要处理的数据源包括各种各样的关系数据库、NoSQL、平面文件、XML文件、机器日志、图片、音视频等等,而且每天都会产生新的数据格式和数据源。
    
  • Veracity —— 真实性

    诸如软硬件异常、应用系统bug、人为错误等都会使数据不正确。大数据处理中应该分析并过滤掉这些有偏差的、伪造的、异常的部分,防止脏数据损害到数据准确性。
    

如何学习大数据

 在谈到学习大数据的时候,不得不提Hadoop和Spark。
  • Hadoop

Hadoop是一个由Apache基金会所开发的分布式系统基础架构。
用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。 [1]
Hadoop实现了一个分布式文件系统(Hadoop Distributed File
System),简称HDFS。HDFS有高容错性的特点,并且设计用来部署在低廉的(low-cost)硬件上;而且它提供高吞吐量(high
throughput)来访问应用程序的数据,适合那些有着超大数据集(large data
set)的应用程序。HDFS放宽了(relax)POSIX的要求,可以以流的形式访问(streaming access)文件系统中的数据。
Hadoop的框架最核心的设计就是:HDFS和MapReduce。HDFS为海量的数据提供了存储,则MapReduce为海量的数据提供了计算。

简而言之,Hadoop就是处理大数据的一个分布式系统基础架构。

  • Spark
  • Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行框架,Spark,拥有Hadoop
    MapReduce所具有的优点;但不同于MapReduce的是——Job中间输出结果可以保存在内存中,从而不再需要读写HDFS,因此Spark能更好地适用于数据挖掘与机器学习等需要迭代的MapReduce的算法。
    Spark 是一种与 Hadoop 相似的开源集群计算环境,但是两者之间还存在一些不同之处,这些有用的不同之处使 Spark
    在某些工作负载方面表现得更加优越,换句话说,Spark 启用了内存分布数据集,除了能够提供交互式查询外,它还可以优化迭代工作负载。
    Spark 是在 Scala 语言中实现的,它将 Scala 用作其应用程序框架。与 Hadoop 不同,Spark 和 Scala
    能够紧密集成,其中的 Scala 可以像操作本地集合对象一样轻松地操作分布式数据集。 尽管创建 Spark
    是为了支持分布式数据集上的迭代作业,但是实际上它是对 Hadoop 的补充,可以在 Hadoop 文件系统中并行运行。通过名为 Mesos
    的第三方集群框架可以支持此行为。Spark 由加州大学伯克利分校 AMP 实验室 (Algorithms, Machines, and
    People Lab) 开发,可用来构建大型的、低延迟的数据分析应用程序。

简而言之,Spark是那么一个专门用来对那些分布式存储的大数据进行处理的工具。

关于Hadoop和Spark学习这块,我也是个初学者,对于整体的学习路线目前无法给出很好的答案,但是可以推荐一些学习大数据不错的文章以及相关资源,这些可以在本文底部获取。

大数据的相关技术介绍

首先看张大数据的整体技术图吧,可以有个更直观的了解。
这里写图片描述

注:Shark 目前已经被Spark SQL取代了。

  看到了这么多相关技术,是不是眼花了了呢,这上面的技术别说都精通,全部都能用好的估计也多少。
  那么这些技术应该主要学习那些呢?

先将这些技术做个分类吧。

  • 文件存储:Hadoop HDFS、Tachyon、KFS
  • 离线计算:Hadoop MapReduce、Spark
  • 流式、实时计算:Storm、Spark Streaming、S4、Heron、Flink
  • K-V、NOSQL数据库:HBase、Redis、MongoDB
  • 资源管理:YARN、Mesos
  • 日志收集:Flume、Scribe、Logstash、Kibana
  • 消息系统:Kafka、StormMQ、ZeroMQ、RabbitMQ
  • 查询分析:Hive、Impala、Pig、Presto、Phoenix、SparkSQL、Drill、分布式协调服务:Zookeeper、Kylin、Druid
  • 集群管理与监控:Ambari、Ganglia、Nagios、Cloudera Manager
  • 数据挖掘、机器学习:Mahout、Spark MLLib
  • 数据同步:Sqoop
  • 任务调度:Oozie

这样整体之后,对于如何学习是不是有个更明确的路线了呢?

那么个人觉得初步学习的技术应该有以下这些:

  • HDFS

         HDFS(Hadoop Distributed File System,Hadoop分布式文件系统)是Hadoop体系中数据存储管理的基础。它是一个高度容错的系统,能检测和应对硬件故障,用于在低成本的通用硬件上运行。HDFS简化了文件的一致性模型,通过流式数据访问,提供高吞吐量应用程序数据访问功能,适合带有大型数据集的应用程序。
    
        HDFS存储相关角色与功能:
        Client:客户端,系统使用者,调用HDFS API操作文件;与NN交互获取文件元数据;与DN交互进行数据读写。
        Namenode:元数据节点,是系统唯一的管理者。负责元数据的管理;与client交互进行提供元数据查询;分配数据存储节点等。
        Datanode:数据存储节点,负责数据块的存储与冗余备份;执行数据块的读写操作等。
    
  • MapReduce

        MapReduce是一种计算模型,用以进行大数据量的计算。Hadoop的MapReduce实现,和Common、HDFS一起,构成了Hadoop发展初期的三个组件。MapReduce将应用划分为Map和Reduce两个步骤,其中Map对数据集上的独立元素进行指定的操作,生成键-值对形式中间结果。Reduce则对中间结果中相同“键”的所有“值”进行规约,以得到最终结果。MapReduce这样的功能划分,非常适合在大量计算机组成的分布式并行环境里进行数据处理。
    
  • YARN

      YARN是Hadoop最新的资源管理系统。除了Hadoop MapReduce外,Hadoop生态圈现在有很多应用操作HDFS中存储的数据。资源管理系统负责多个应用程序的多个作业可以同时运行。例如,在一个集群中一些用户可能提交MapReduce作业查询,另一些用户可能提交Spark 作业查询。资源管理的角色就是要保证两种计算框架都能获得所需的资源,并且如果多人同时提交查询,保证这些查询以合理的方式获得服务。
    
  • SparkStreaming

        SparkStreaming是一个对实时数据流进行高通量、容错处理的流式处理系统,可以对多种数据源(如Kdfka、Flume、Twitter、Zero和TCP 套接字)进行类似Map、Reduce和Join等复杂操作,并将结果保存到外部文件系统、数据库或应用到实时仪表盘。
    
  • SparkSQL

       SparkSQL是Hadoop中另一个著名的SQL引擎,正如名字所表示的,它以Spark作为底层计算框架,实际上是一个Scala程序语言的子集。Spark基本的数据结构是RDD,一个分布于集群节点的只读数据集合。传统的MapReduce框架强制在分布式编程中使用一种特定的线性数据流处理方式。MapReduce程序从磁盘读取输入数据,把数据分解成键/值对,经过混洗、排序、归并等数据处理后产生输出,并将最终结果保存在磁盘。Map阶段和Reduce阶段的结果均要写磁盘,这大大降低了系统性能。也是由于这个原因,MapReduce大都被用于执行批处理任务
    
  • Hive

       hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。 其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
    
  • Impala

       Impala是一个运行在Hadoop之上的大规模并行处理(MPP)查询引擎,提供对Hadoop集群数据的高性能、低延迟的SQL查询,使用HDFS作为底层存储。对查询的快速响应使交互式查询和对分析查询的调优成为可能,而这些在针对处理长时间批处理作业的SQL-on-Hadoop传统技术上是难以完成的。
        Impala的最大亮点在于它的执行速度。官方宣称大多数情况下它能在几秒或几分钟内返回查询结果,而相同的Hive查询通常需要几十分钟甚至几小时完成,因此Impala适合对Hadoop文件系统上的数据进行分析式查询。Impala缺省使用Parquet文件格式,这种列式存储对于典型数据仓库场景下的大查询是较为高效的。
    
  • HBase

        一个结构化数据的分布式存储系统。
        HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。
        HBase是一个针对结构化数据的可伸缩、高可靠、高性能、分布式和面向列的动态模式数据库。和传统关系数据库不同,HBase采用了BigTable的数据模型:增强的稀疏排序映射表(Key/Value),其中,键由行关键字、列关键字和时间戳构成。HBase提供了对大规模数据的随机、实时读写访问,同时,HBase中保存的数据可以使用MapReduce来处理,它将数据存储和并行计算完美地结合在一起。
    
  • Apache Kylin

        Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc. 开发并贡献至开源社区。它能在亚秒内查询巨大的Hive表。
    
  • Flume

        Flume是Cloudera提供的一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力。
    

参考文章

大数据初步了解
http://lxw1234.com/archives/2016/11/779.htm

大数据杂谈
http://lxw1234.com/archives/2016/12/823.htm

推荐文章

零基础学习Hadoop
http://blog.csdn.net/qazwsxpcm/article/details/78460840

HBase 应用场景
http://blog.csdn.net/lifuxiangcaohui/article/details/39894265

Hadoop硬件选择
http://bigdata.evget.com/post/1969.html

图解Spark:核心技术与案例实战
http://www.cnblogs.com/shishanyuan/category/925085.html

一个大数据项目的架构设计与实施方案
http://www.360doc.com/content/17/0603/22/22712168_659649698.shtml

相关文档

Hadoop-10-years
链接:http://pan.baidu.com/s/1nvBppQ5 密码:7i7m

Hadoop权威指南
链接:http://pan.baidu.com/s/1skJEzj3 密码:0ryw

Hadoop实战
链接:http://pan.baidu.com/s/1dEQi29V 密码:ddc7

Hadoop源代码分析
链接:http://pan.baidu.com/s/1bp8RTcN 密码:ju63

Spark最佳学习路径
链接:http://pan.baidu.com/s/1i5MmJVv 密码:qfbt

深入理解大数据+大数据处理与编程实践
链接:http://pan.baidu.com/s/1dFq6OSD 密码:7ggl

作者:qazwsxpcm 发表于2017/11/25 11:04:09 原文链接
阅读:62 评论:0 查看评论

python设计模式之代理模式

$
0
0

一、 理解代理设计模式

        使用场景:

               1. 它能够以更简单的方式表示一个复杂的系统。如:设计多个复杂计算或过程的系统应该提供一个更简单的接口,让它充当客户端的代理。

               2. 它提高了现有的实际对象的安全性。如:在许多情况下,都不允许客户端直接访问实际对象。这是因为实际对象可能受到恶意活动的危害。这时候,代理就能起到抵御恶意活动的盾牌作用,从未保护了实际的对象。

               3.它为不同服务器上的远程对象提供本地接口。如:客户端希望在远程系统上运行某些命令的分布式系统,但客户端可能没有直接的权限来实现这一点。因此它将请求 转交给本地对象(代理),然后由远程机器上的代理执行该请求。

               4.它为消耗大量内存的对象提供了一个轻量级句柄。有时,你可能不想加载主要对象,除非他们真的有必要。这是因为实际对象真的很笨重,可能需要消耗大量资源如:网站用户的个人简介头像。你最好在列表视图中显示简介头像的缩略图,当然,为了展示用户简介的详细介绍,你就需要加载实际图片了。


二、代理模式的UML类图


                          1.代理:它维护一个引用,允许代理(Proxy)通过这个引用来访问实际对象。它提供了一个与主题(Subject)相同的接口,以便代理可以替换真实的主   题。代理还负责创建和删除真实主题(RealSubject)

                      2.主题:它定义了RealSubject和Proxy的公共接口。以Proxy和RealSubject的形式实现主题(Subject),使用RealSubject的任何地方都可以使用代理     Proxy。

                      3.真实主题:它定义代理(Proxy)所代表的真实对象。

               数据结构的角度分析,UML表示:

                          1.代理:它是一个控制对RealSubject类访问的类。它处理客户端的请求,负责创建或删除RealSubject。

                           2.主题/真实主题:主题是定义真实主题(RealSubject)和代理(Proxy)相类似的接口。RealSubject是Subject接口的实际实现。它提供了真正的功能,然后由客户端使用。

                           3.客户端:它访问要完成工作的Proxy类。Proxy类在内部控制对RealSubject的访问,并引导客户端所请求的工作。


三、了解不同类型的代理

               1.虚拟代理:

                         如果一个对象实例化后会占用大量内存的话,可以先利用占位符来表示,这就是所谓的虚拟代理。如:网站加载大型图片,而这个图片需要很长时间才能加载完  成。通常开发人员将在网页上创建一个占位符图标。以提示这里有图像。但是,只有当客户实际点击图标时才会加载图像,从而节省开销。因此,虚拟代理中,当客户端请求或访问对象时,才会创建实际对象。

               2.远程代理:

                        它给远程服务器或不同地址空间上的实际对象提供了一个本地表示。如:应用程序建立一个监控系统,应该设计多个web服务器,数据服务器,任务服务器,缓   存服务器。如果我们要监视这些服务器的COU和磁盘利用率,就需要建立一个对象,该对象能够用于监视应用程序运行的上下文中,同时还可以执行远程命令以获取实际的参数值。在这种情况下,建立一个作为远程对象的本地表示的远程代理对象将可以帮助我们实现这个目标。

               3.保护代理:

                         这种代理能够控制RealSubject的敏感对象的访问。如:分布式系统中,web提供多个服务,服务有相互合作提供各种功能。认证服务充当负责认证和授权的保   护性代理服务器。代理自然有助于保护网站的核心功能,防止无法识别或未授权的代理访问他们。因此,代理对象会检查调用者是否具有转发请求所需的访问权 限。

               4.智能代理:

                         智能代理在访问对象时插入其他操作。如:假设系统中有一个核心组件,它将状态信息集中保存在一个地点。通常情况下,这样的组件会被多个不同的服务器调 用以完成他们的任务,并且可能导致资源共享问题。与让服务器直接调用核心组件不同,智能代理是内置的,并且会在访问之前检查实际对象是否被锁定,以确保没有其他对象可以更改它。

四、现实世界中的代理模式

# -*- coding: UTF-8 -*-
from abc import ABCMeta, abstractmethod

# 主题
class Payment(metaclass=ABCMeta):
    @abstractmethod
    def do_pay(self):
        pass

# 真实主题
class Bank(Payment):
    def __init__(self):
        self.card = None
        self.account = None

    def __getAccount(self):
        self.account = self.card # 假定卡号就是账户
        return self.account

    def __hasFunds(self):
        print("银行:核对账户",self.__getAccount(),"钱够用")
        return True

    def setCard(self,card):
        self.card = card

    def do_pay(self):
        if self.__hasFunds():
            print("银行::商家付款")
            return True
        else:
            print("银行::对不起,没钱")
            return False

# 代理
class DebitCard(Payment):
    def __init__(self):
        self.bank = Bank()

    def do_pay(self):
        card = input("代理::输入卡号")
        self.bank.setCard(card)
        return self.bank.do_pay()

# 客户端
class You:
    def __init__(self):
        print("You:: Lets buy the Denim shirt")
        self.debitCard = DebitCard()
        self.isPurchased = None

    def make_payment(self):
        self.isPurchased = self.debitCard.do_pay()

    def __del__(self):
        if self.isPurchased:
            print("你:: 哦! 牛仔衫是老子的了 :-")
        else:
            print("你:: 我应该赚更多的钱:(")

you = You()
you.make_payment()

         

五、代理模式的优点

        1. 代理可以通过缓存笨重的对象或频繁访问的对象来提高应用程序的性能。

        2. 代理还提供对于真实主题的访问授权。因此,只有提供合适的权限的情况下,这个模式才会接受委派。

        3. 远程代理还便于与可用作网络连接和数据库连接的远程服务器进行交互,并可用与监视系统。


六、门面模式和代理模式之间的比较

        1. 代理模式

                   1.1 它为其他对象提供了代理或占位符,以控制对原始对象的访问。

                   1.2 代理对象具有与其目标对象相同的接口,并保存有目标对象的引用。

                   1.3 它充当客户端和被封装的对象之间的中介。

        2.门面模式

                   1.1 它为类的大型子系统提供了一个接口。

                   1.2 它实现了子系统之间的通信和依赖性的最小化。

                   1.3  门面对象提供了单一的简单接口。 


七、常见问答

        1. 装饰器模式和代理模式之间有什么区别?

                  装饰器向在运行时装饰的对象添加行为,而代理则是控制对对象的访问。代理和真实主题之间的关联是在编译时完成的,而不是动态的。

        2. 代理模式的缺点是什么?

                   代理模式会增加相应时间。例如,如果代理没有良好的体系结构或存在性能问题,它就会增加真实主题的响应时间。一般来说,这一切都取决于代理写的有多好。

        3. 客户端可以独立访问真实的主题吗?

                   是的,但是代理模式能够提供许多优势,例如:虚拟、远程等。所以使用代理模式会更好一些。

        4. 代理是否能给自己添加任何功能?

                   代理可以向真实主题添加额外的功能,而无需更改对象的代码。代理和真实主题可以实现相同的接口。




作者:u013584315 发表于2017/11/25 12:18:53 原文链接
阅读:57 评论:0 查看评论

IT笑话之那些年我们见过的奇葩产品经理

重新认识HashMap

$
0
0

摘要

HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化,例如引入红黑树的数据结构和扩容的优化等。本文结合JDK1.7和JDK1.8的区别,深入探讨HashMap的结构实现和功能原理。

简介

Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示:

java.util.map类图

下面针对各个实现类的特点做一些说明:

(1) HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。

(2) Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。

(3) LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

(4) TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

对于上述四种Map类型的类,要求映射中的key是不可变对象。不可变对象是该对象在创建后它的哈希值不会被改变。如果对象的哈希值发生变化,Map对象很可能就定位不到映射的位置了。

通过上面的比较,我们知道了HashMap是Java的Map家族中一个普通成员,鉴于它可以满足大多数场景的使用条件,所以是使用频度最高的一个。下文我们主要结合源码,从存储结构、常用方法分析、扩容以及安全性等方面深入讲解HashMap的工作原理。

内部实现

搞清楚HashMap,首先需要知道HashMap是什么,即它的存储结构-字段;其次弄明白它能干什么,即它的功能实现-方法。下面我们针对这两个方面详细展开讲解。

存储结构-字段

从结构实现来讲,HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下如所示。

hashMap内存结构图

这里需要讲明白两个问题:数据底层具体存储的是什么?这样的存储方式有什么优点呢?

(1) 从源码可知,HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组,明显它是一个Node的数组。我们来看Node[JDK1.8]是何物。

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;    //用来定位数组索引位置
        final K key;
        V value;
        Node<K,V> next;   //链表的下一个node

        Node(int hash, K key, V value, Node<K,V> next) { ... }
        public final K getKey(){ ... }
        public final V getValue() { ... }
        public final String toString() { ... }
        public final int hashCode() { ... }
        public final V setValue(V newValue) { ... }
        public final boolean equals(Object o) { ... }
}

Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。上图中的每个黑色圆点就是一个Node对象。

(2) HashMap就是使用哈希表来存储的。哈希表为解决冲突,可以采用开放地址法和链地址法等来解决问题,Java中HashMap采用了链地址法。链地址法,简单来说,就是数组加链表的结合。在每个数组元素上都一个链表结构,当数据被Hash后,得到数组下标,把数据放在对应下标元素的链表上。例如程序执行下面代码:

    map.put("美团","小美");

系统将调用"美团"这个key的hashCode()方法得到其hashCode 值(该方法适用于每个Java对象),然后再通过Hash算法的后两步运算(高位运算和取模运算,下文有介绍)来定位该键值对的存储位置,有时两个key会定位到相同的位置,表示发生了Hash碰撞。当然Hash算法计算结果越分散均匀,Hash碰撞的概率就越小,map的存取效率就会越高。

如果哈希桶数组很大,即使较差的Hash算法也会比较分散,如果哈希桶数组数组很小,即使好的Hash算法也会出现较多碰撞,所以就需要在空间成本和时间成本之间权衡,其实就是在根据实际情况确定哈希桶数组的大小,并在此基础上设计好的hash算法减少Hash碰撞。那么通过什么方式来控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制。

在理解Hash和扩容流程之前,我们得先了解下HashMap的几个字段。从HashMap的默认构造函数源码可知,构造函数就是对下面几个字段进行初始化,源码如下:

     int threshold;             // 所能容纳的key-value对极限 
     final float loadFactor;    // 负载因子
     int modCount;  
     int size;

首先,Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳的最大数据量的Node(键值对)个数。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。

结合负载因子的定义公式可知,threshold就是在此Load factor和length(数组长度)对应下允许的最大元素数目,超过这个数目就重新resize(扩容),扩容后的HashMap容量是之前容量的两倍。默认的负载因子0.75是对空间和时间效率的一个平衡选择,建议大家不要修改,除非在时间和空间比较特殊的情况下,如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值;相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

size这个字段其实很好理解,就是HashMap中实际存在的键值对数量。注意和table的长度length、容纳最大键值对数量threshold的区别。而modCount字段主要用来记录HashMap内部结构发生变化的次数,主要用于迭代的快速失败。强调一点,内部结构发生变化指的是结构发生变化,例如put新键值对,但是某个key对应的value值被覆盖不属于结构变化。

在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数,具体证明可以参考http://blog.csdn.net/liuqiyao_01/article/details/14475159,Hashtable初始化桶大小为11,就是桶大小设计为素数的应用(Hashtable扩容后不能保证还是素数)。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。

这里存在一个问题,即使负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响HashMap的性能。于是,在JDK1.8版本中,对数据结构做了进一步的优化,引入了红黑树。而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论,想了解更多红黑树数据结构的工作原理可以参考http://blog.csdn.net/v_july_v/article/details/6105630

功能实现-方法

HashMap的内部功能实现很多,本文主要从根据key获取哈希桶数组索引位置、put方法的详细执行、扩容过程三个具有代表性的点深入展开讲解。

1. 确定哈希桶数组索引位置

不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。前面说过HashMap的数据结构是数组和链表的结合,所以我们当然希望这个HashMap里面的元素位置尽量分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,不用遍历链表,大大优化了查询的效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能。先看看源码的实现(方法一+方法二):

方法一:
static final int hash(Object key) {   //jdk1.8 & jdk1.7
     int h;
     // h = key.hashCode() 为第一步 取hashCode值
     // h ^ (h >>> 16)  为第二步 高位参与运算
     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
方法二:
static int indexFor(int h, int length) {  //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的
     return h & (length-1);  //第三步 取模运算
}

这里的Hash算法本质上就是三步:取key的hashCode值、高位运算、取模运算

对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用方法一所计算得到的Hash码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,模运算的消耗还是比较大的,在HashMap中是这样做的:调用方法二来计算该对象应该保存在table数组的哪个索引处。

这个方法非常巧妙,它通过h & (table.length -1)来得到该对象的保存位,而HashMap底层数组的长度总是2的n次方,这是HashMap在速度上的优化。当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。

在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。

下面举例说明下,n为table的长度。

hashMap哈希算法例图

2. 分析HashMap的put方法

HashMap的put方法执行过程可以通过下图来理解,自己有兴趣可以去对比源码更清楚地研究学习。

hashMap put方法执行流程图

①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

JDK1.8HashMap的put方法源码如下:

 1 public V put(K key, V value) {
 2     // 对key的hashCode()做hash
 3     return putVal(hash(key), key, value, false, true);
 4 }
 5 
 6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 7                boolean evict) {
 8     Node<K,V>[] tab; Node<K,V> p; int n, i;
 9     // 步骤①:tab为空则创建
10     if ((tab = table) == null || (n = tab.length) == 0)
11         n = (tab = resize()).length;
12     // 步骤②:计算index,并对null做处理 
13     if ((p = tab[i = (n - 1) & hash]) == null) 
14         tab[i] = newNode(hash, key, value, null);
15     else {
16         Node<K,V> e; K k;
17         // 步骤③:节点key存在,直接覆盖value
18         if (p.hash == hash &&
19             ((k = p.key) == key || (key != null && key.equals(k))))
20             e = p;
21         // 步骤④:判断该链为红黑树
22         else if (p instanceof TreeNode)
23             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
24         // 步骤⑤:该链为链表
25         else {
26             for (int binCount = 0; ; ++binCount) {
27                 if ((e = p.next) == null) {
28                     p.next = newNode(hash, key,value,null);
                        //链表长度大于8转换为红黑树进行处理
29                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  
30                         treeifyBin(tab, hash);
31                     break;
32                 }
                    // key已经存在直接覆盖value
33                 if (e.hash == hash &&
34                     ((k = e.key) == key || (key != null && key.equals(k)))) 
35                            break;
36                 p = e;
37             }
38         }
39         
40         if (e != null) { // existing mapping for key
41             V oldValue = e.value;
42             if (!onlyIfAbsent || oldValue == null)
43                 e.value = value;
44             afterNodeAccess(e);
45             return oldValue;
46         }
47     }

48     ++modCount;
49     // 步骤⑥:超过最大容量 就扩容
50     if (++size > threshold)
51         resize();
52     afterNodeInsertion(evict);
53     return null;
54 }

3. 扩容机制

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组,就像我们用一个小桶装水,如果想装更多的水,就得换大水桶。

我们分析下resize的源码,鉴于JDK1.8融入了红黑树,较复杂,为了便于理解我们仍然使用JDK1.7的代码,好理解一些,本质上区别不大,具体区别后文再说。

 1 void resize(int newCapacity) {   //传入新的容量
 2     Entry[] oldTable = table;    //引用扩容前的Entry数组
 3     int oldCapacity = oldTable.length;         
 4     if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了
 5         threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1),这样以后就不会扩容了
 6         return;
 7     }
 8  
 9     Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组
10     transfer(newTable);                         //!!将数据转移到新的Entry数组里
11     table = newTable;                           //HashMap的table属性引用新的Entry数组
12     threshold = (int)(newCapacity * loadFactor);//修改阈值
13 }

这里就是使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。

 1 void transfer(Entry[] newTable) {
 2     Entry[] src = table;                   //src引用了旧的Entry数组
 3     int newCapacity = newTable.length;
 4     for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
 5         Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素
 6         if (e != null) {
 7             src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
 8             do {
 9                 Entry<K,V> next = e.next;
10                 int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
11                 e.next = newTable[i]; //标记[1]
12                 newTable[i] = e;      //将元素放在数组上
13                 e = next;             //访问下一个Entry链上的元素
14             } while (e != null);
15         }
16     }
17 }

newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话),这一点和Jdk1.8有区别,下文详解。在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。

下面举个例子说明下扩容过程。假设了我们的hash算法就是简单的用key mod 一下表的大小(也就是数组的长度)。其中的哈希桶数组table的size=2, 所以key = 3、7、5,put顺序依次为 5、7、3。在mod 2以后都冲突在table[1]这里了。这里假设负载因子 loadFactor=1,即当键值对的实际大小size 大于 table的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize成4,然后所有的Node重新rehash的过程。

jdk1.7扩容例图

下面我们讲解下JDK1.8做了哪些优化。经过观测可以发现,我们使用的是2次幂的扩展(指长度扩为原来2倍),所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。看下图可以明白这句话的意思,n为table的长度,图(a)表示扩容前的key1和key2两种key确定索引位置的示例,图(b)表示扩容后key1和key2两种key确定索引位置的示例,其中hash1是key1对应的哈希与高位运算结果。

hashMap 1.8 哈希算法例图1

元素在重新计算hash之后,因为n变为2倍,那么n-1的mask范围在高位多1bit(红色),因此新的index就会发生这样的变化:

hashMap 1.8 哈希算法例图2

因此,我们在扩充HashMap的时候,不需要像JDK1.7的实现那样重新计算hash,只需要看看原来的hash值新增的那个bit是1还是0就好了,是0的话索引没变,是1的话索引变成“原索引+oldCap”,可以看看下图为16扩充为32的resize示意图:

jdk1.8 hashMap扩容例图

这个设计确实非常的巧妙,既省去了重新计算hash值的时间,而且同时,由于新增的1bit是0还是1可以认为是随机的,因此resize的过程,均匀的把之前的冲突的节点分散到新的bucket了。这一块就是JDK1.8新增的优化点。有一点注意区别,JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,但是从上图可以看出,JDK1.8不会倒置。有兴趣的同学可以研究下JDK1.8的resize源码,写的很赞,如下:

 1 final Node<K,V>[] resize() {
 2     Node<K,V>[] oldTab = table;
 3     int oldCap = (oldTab == null) ? 0 : oldTab.length;
 4     int oldThr = threshold;
 5     int newCap, newThr = 0;
 6     if (oldCap > 0) {
 7         // 超过最大值就不再扩充了,就只好随你碰撞去吧
 8         if (oldCap >= MAXIMUM_CAPACITY) {
 9             threshold = Integer.MAX_VALUE;
10             return oldTab;
11         }
12         // 没超过最大值,就扩充为原来的2倍
13         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
14                  oldCap >= DEFAULT_INITIAL_CAPACITY)
15             newThr = oldThr << 1; // double threshold
16     }
17     else if (oldThr > 0) // initial capacity was placed in threshold
18         newCap = oldThr;
19     else {               // zero initial threshold signifies using defaults
20         newCap = DEFAULT_INITIAL_CAPACITY;
21         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
22     }
23     // 计算新的resize上限
24     if (newThr == 0) {
25 
26         float ft = (float)newCap * loadFactor;
27         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
28                   (int)ft : Integer.MAX_VALUE);
29     }
30     threshold = newThr;
31     @SuppressWarnings({"rawtypes","unchecked"})
32         Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
33     table = newTab;
34     if (oldTab != null) {
35         // 把每个bucket都移动到新的buckets中
36         for (int j = 0; j < oldCap; ++j) {
37             Node<K,V> e;
38             if ((e = oldTab[j]) != null) {
39                 oldTab[j] = null;
40                 if (e.next == null)
41                     newTab[e.hash & (newCap - 1)] = e;
42                 else if (e instanceof TreeNode)
43                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
44                 else { // 链表优化重hash的代码块
45                     Node<K,V> loHead = null, loTail = null;
46                     Node<K,V> hiHead = null, hiTail = null;
47                     Node<K,V> next;
48                     do {
49                         next = e.next;
50                         // 原索引
51                         if ((e.hash & oldCap) == 0) {
52                             if (loTail == null)
53                                 loHead = e;
54                             else
55                                 loTail.next = e;
56                             loTail = e;
57                         }
58                         // 原索引+oldCap
59                         else {
60                             if (hiTail == null)
61                                 hiHead = e;
62                             else
63                                 hiTail.next = e;
64                             hiTail = e;
65                         }
66                     } while ((e = next) != null);
67                     // 原索引放到bucket里
68                     if (loTail != null) {
69                         loTail.next = null;
70                         newTab[j] = loHead;
71                     }
72                     // 原索引+oldCap放到bucket里
73                     if (hiTail != null) {
74                         hiTail.next = null;
75                         newTab[j + oldCap] = hiHead;
76                     }
77                 }
78             }
79         }
80     }
81     return newTab;
82 }

线程安全性

在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap。那么为什么说HashMap是线程不安全的,下面举例子说明在并发的多线程使用场景中使用HashMap可能造成死循环。代码例子如下(便于理解,仍然使用JDK1.7的环境):

public class HashMapInfiniteLoop {  

    private static HashMap<Integer,String> map = new HashMap<Integer,String>(2,0.75f);  
    public static void main(String[] args) {  
        map.put(5, "C");  

        new Thread("Thread1") {  
            public void run() {  
                map.put(7, "B");  
                System.out.println(map);  
            };  
        }.start();  
        new Thread("Thread2") {  
            public void run() {  
                map.put(3, "A);  
                System.out.println(map);  
            };  
        }.start();        
    }  
}

其中,map初始化为一个长度为2的数组,loadFactor=0.75,threshold=2*0.75=1,也就是说当put第二个key的时候,map就需要进行resize。

通过设置断点让线程1和线程2同时debug到transfer方法(3.3小节代码块)的首行。注意此时两个线程已经成功添加数据。放开thread1的断点至transfer方法的“Entry next = e.next;” 这一行;然后放开线程2的的断点,让线程2进行resize。结果如下图。

jdk1.7 hashMap死循环例图1

注意,Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。

线程一被调度回来执行,先是执行 newTalbe[i] = e, 然后是e = next,导致了e指向了key(7),而下一次循环的next = e.next导致了next指向了key(3)。

jdk1.7 hashMap死循环例图2

jdk1.7 hashMap死循环例图3

e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

jdk1.7 hashMap死循环例图4

于是,当我们用线程一调用map.get(11)时,悲剧就出现了——Infinite Loop。

JDK1.8与JDK1.7的性能对比

HashMap中,如果key经过hash算法得出的数组索引位置全部不相同,即Hash算法非常好,那样的话,getKey方法的时间复杂度就是O(1),如果Hash算法技术的结果碰撞非常多,假如Hash算极其差,所有的Hash算法结果得出的索引位置一样,那样所有的键值对都集中到一个桶中,或者在一个链表中,或者在一个红黑树中,时间复杂度分别为O(n)和O(lgn)。 鉴于JDK1.8做了多方面的优化,总体性能优于JDK1.7,下面我们从两个方面用例子证明这一点。

Hash较均匀的情况

为了便于测试,我们先写一个类Key,如下:

class Key implements Comparable<Key> {

    private final int value;

    Key(int value) {
        this.value = value;
    }

    @Override
    public int compareTo(Key o) {
        return Integer.compare(this.value, o.value);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Key key = (Key) o;
        return value == key.value;
    }

    @Override
    public int hashCode() {
        return value;
    }
}

这个类复写了equals方法,并且提供了相当好的hashCode函数,任何一个值的hashCode都不会相同,因为直接使用value当做hashcode。为了避免频繁的GC,我将不变的Key实例缓存了起来,而不是一遍一遍的创建它们。代码如下:

public class Keys {

    public static final int MAX_KEY = 10_000_000;
    private static final Key[] KEYS_CACHE = new Key[MAX_KEY];

    static {
        for (int i = 0; i < MAX_KEY; ++i) {
            KEYS_CACHE[i] = new Key(i);
        }
    }

    public static Key of(int value) {
        return KEYS_CACHE[value];
    }
}

现在开始我们的试验,测试需要做的仅仅是,创建不同size的HashMap(1、10、100、......10000000),屏蔽了扩容的情况,代码如下:

   static void test(int mapSize) {

        HashMap<Key, Integer> map = new HashMap<Key,Integer>(mapSize);
        for (int i = 0; i < mapSize; ++i) {
            map.put(Keys.of(i), i);
        }

        long beginTime = System.nanoTime(); //获取纳秒
        for (int i = 0; i < mapSize; i++) {
            map.get(Keys.of(i));
        }
        long endTime = System.nanoTime();
        System.out.println(endTime - beginTime);
    }

    public static void main(String[] args) {
        for(int i=10;i<= 1000 0000;i*= 10){
            test(i);
        }
    }

在测试中会查找不同的值,然后度量花费的时间,为了计算getKey的平均时间,我们遍历所有的get方法,计算总的时间,除以key的数量,计算一个平均值,主要用来比较,绝对值可能会受很多环境因素的影响。结果如下:

性能比较表1.png

通过观测测试结果可知,JDK1.8的性能要高于JDK1.7 15%以上,在某些size的区域上,甚至高于100%。由于Hash算法较均匀,JDK1.8引入的红黑树效果不明显,下面我们看看Hash不均匀的的情况。

Hash极不均匀的情况

假设我们又一个非常差的Key,它们所有的实例都返回相同的hashCode值。这是使用HashMap最坏的情况。代码修改如下:

class Key implements Comparable<Key> {

    //...

    @Override
    public int hashCode() {
        return 1;
    }
}

仍然执行main方法,得出的结果如下表所示:

性能比较表2.png

从表中结果中可知,随着size的变大,JDK1.7的花费时间是增长的趋势,而JDK1.8是明显的降低趋势,并且呈现对数增长稳定。当一个链表太长的时候,HashMap会动态的将它替换成一个红黑树,这话的话会将时间复杂度从O(n)降为O(logn)。hash算法均匀和不均匀所花费的时间明显也不相同,这两种情况的相对比较,可以说明一个好的hash算法的重要性。

      测试环境:处理器为2.2 GHz Intel Core i7,内存为16 GB 1600 MHz DDR3,SSD硬盘,使用默认的JVM参数,运行在64位的OS X 10.10.1上。

小结

(1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

(2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。

(3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。

(4) JDK1.8引入红黑树大程度优化了HashMap的性能。

(5) 还没升级JDK1.8的,现在开始升级吧。HashMap的性能提升仅仅是JDK1.8的冰山一角。

参考

  1. JDK1.7&JDK1.8 源码。
  2. CSDN博客频道,HashMap多线程死循环问题,2014。
  3. 红黑联盟,Java类集框架之HashMap(JDK1.8)源码剖析,2015。
  4. CSDN博客频道, 教你初步了解红黑树,2010。
  5. Java Code Geeks,HashMap performance improvements in Java 8,2014。
  6. Importnew,危险!在HashMap中将可变对象用作Key,2014。
  7. CSDN博客频道,为什么一般hashtable的桶数会取一个素数,2013。
作者:luozhonghua2014 发表于2017/11/25 13:18:16 原文链接
阅读:25 评论:0 查看评论

[bzoj2648][kd-tree]SJY摆棋子

$
0
0

Description

这天,SJY显得无聊。在家自己玩。在一个棋盘上,有N个黑色棋子。他每次要么放到棋盘上一个黑色棋子,要么放上一个白色棋子,如果是白色棋子,他会找出距离这个白色棋子最近的黑色棋子。此处的距离是
曼哈顿距离 即(|x1-x2|+|y1-y2|)
。现在给出N<=500000个初始棋子。和M<=500000个操作。对于每个白色棋子,输出距离这个白色棋子最近的黑色棋子的距离。同一个格子可能有多个棋子。

Input

第一行两个数 N M
以后N行 每行两个数x y,表示棋盘上原有的黑棋子的坐标
以后M行,每行3个数 t x y 如果t=1 那么放下一个黑色棋子 如果t=2 那么放下一个白色棋子

Output

对于每个T=2 输出一个最小距离

Sample Input

2 3
1 1
2 3
2 1 2
1 3 3
2 4 2

Sample Output

1
2

HINT

kdtree可以过

题解

算法这个Hint都这么明显的摆出来了哈哈哈哈哈你还去搞别的干啥快来一起学兹磁修改kdtree啊
对黑点建kdtree,然后每次输入黑点就往里面插,白点就在kdtree里面找
自己yy了一个修改的操作,就是递归插空找。对于每个点,记录划分他的平面编号,然后每次让插进去这个点在这个根记录的平面上和他比一比。比他小就去根的左孩子,反之就去右孩子
如果左孩子或者右孩子为0就代表可以在这里插啦。那么记录就好
于是就TLE
怎么办??
改int啊哈哈哈哈哈哈

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
int INF=(1<<30);
struct node
{
    int id;
    int lc,rc,d[2],mx[2],mn[2];
}tr[1111000];int len,root,n,m;
void update(int x)
{
    int lc=tr[x].lc,rc=tr[x].rc;
    if(lc)for(int i=0;i<=1;i++)tr[x].mx[i]=max(tr[x].mx[i],tr[lc].mx[i]),tr[x].mn[i]=min(tr[x].mn[i],tr[lc].mn[i]);
    if(rc)for(int i=0;i<=1;i++)tr[x].mx[i]=max(tr[x].mx[i],tr[rc].mx[i]),tr[x].mn[i]=min(tr[x].mn[i],tr[rc].mn[i]);
}
int p[2];int cmpd;
bool cmp(node n1,node n2)
{
    return n1.d[cmpd]<n2.d[cmpd] || n1.d[cmpd]==n2.d[cmpd] && n1.d[cmpd^1]<n2.d[cmpd^1];
}
int bt(int l,int r,int d)
{
    int mid=(l+r)/2,now;
    cmpd=d;now=mid;
    nth_element(tr+l,tr+mid,tr+r+1,cmp);
    tr[now].mx[0]=tr[now].mn[0]=tr[now].d[0];
    tr[now].mx[1]=tr[now].mn[1]=tr[now].d[1];
    if(l<mid)tr[now].lc=bt(l,mid-1,d^1);
    if(mid<r)tr[now].rc=bt(mid+1,r,d^1);
    update(now);
    return now;
}
int nowx,nowy,minn;
void ins(int now)
{
    int p=root;
    while(1)
    {
        if(tr[p].mx[0]<tr[now].mx[0])tr[p].mx[0]=tr[now].mx[0];
        if(tr[p].mx[1]<tr[now].mx[1])tr[p].mx[1]=tr[now].mx[1];
        if(tr[p].mn[0]>tr[now].mn[0])tr[p].mn[0]=tr[now].mn[0];
        if(tr[p].mn[1]>tr[now].mn[1])tr[p].mn[1]=tr[now].mn[1];
        if(tr[now].d[cmpd]<tr[p].d[cmpd])
        {
            if(tr[p].lc==0){tr[p].lc=now;return ;}
            else p=tr[p].lc;
        }
        else
        {
            if(tr[p].rc==0){tr[p].rc=now;return ;}
            else p=tr[p].rc;
        }
        cmpd^=1;
    }
}
int getmin(int now,int x,int y)
{
    int ret=0;
    if(x>tr[now].mx[0])ret+=x-tr[now].mx[0];
    if(x<tr[now].mn[0])ret+=tr[now].mn[0]-x;
    if(y>tr[now].mx[1])ret+=y-tr[now].mx[1];
    if(y<tr[now].mn[1])ret+=tr[now].mn[1]-y;
    return ret;
}
void solmin(int now)
{
    int d0=abs(nowx-tr[now].d[0])+abs(nowy-tr[now].d[1]);
    if(d0<minn)minn=d0;
    int dl=INF,dr=INF;
    if(tr[now].lc)dl=getmin(tr[now].lc,nowx,nowy);
    if(tr[now].rc)dr=getmin(tr[now].rc,nowx,nowy);
    if(dl<dr)
    {
        if(dl<minn)solmin(tr[now].lc);
        if(dr<minn)solmin(tr[now].rc);
    }
    else
    {
        if(dr<minn)solmin(tr[now].rc);
        if(dl<minn)solmin(tr[now].lc);
    }
}
int main()
{
    root=len=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d%d",&tr[i].d[0],&tr[i].d[1]);
    root=bt(1,n,0);len=n;
    while(m--)
    {
        int op;int x,y;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1 && root==0)
        {
            root=len=1;tr[1].id=0;
            tr[1].mx[0]=tr[1].mn[0]=tr[1].d[0]=x;
            tr[1].mx[1]=tr[1].mn[1]=tr[1].d[1]=y;
        }
        else if(op==1 && root!=0)
        {
            len++;
            tr[len].mx[0]=tr[len].mn[0]=tr[len].d[0]=x;
            tr[len].mx[1]=tr[len].mn[1]=tr[len].d[1]=y;
            cmpd=0;
            ins(len);
        }
        else
        {
            minn=INF;nowx=x;nowy=y;
            solmin(root);
            printf("%d\n",minn);
        }
    }
    return 0;
}
作者:Rose_max 发表于2017/11/25 13:37:13 原文链接
阅读:45 评论:0 查看评论

Unity Shader 学习笔记(25) 全局雾效

$
0
0

Unity Shader 学习笔记(25) 全局雾效

参考书籍:《Unity Shader 入门精要》
3D数学 学习笔记(7) 视图、视锥、视场(Field of View)、裁切空间、屏幕空间


雾效(Fog)

实现方法:
- Unity内置雾效可以产生基于距离的线性或指数雾效。
- 自行编写雾效需要添加#pragma multi_compile_fog指令,并使用相关宏。缺点是需要为所有物体添加渲染代码,实现效果有限。
- 根据深度纹理重建每个像素世界空间下位置,使用公式模拟雾效。


重建世界坐标

由世界空间下相机位置加上像素相对相机偏移量即可。
float worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;
- _WorldSpaceCameraPos:世界空间下相机位置,Unity内置变量。
- linearDepth:深度纹理的线性深度值。
- interpolatedRay:顶点着色器输出并插值后的射线,包含方向和距离。

interpolatedRay

实质是存了近裁切面的四个角的向量中最靠近的一个。在顶点着色器中,根据当前位置,选择对应的向量,做插值,最后传递给片元着色器得到interpolatedRay。

相关计算:aspect为宽高比。



雾的计算

颜色混合类似透明度混合:f为混合系数。
float3 afterFog = f * fogColor + (1 - f) * origColor;

雾效系数f的计算方法:z为距离(一般是点的高度)。
- 线性:f = (dmax - |z|) / (dmax - dmin)。d表示受雾影响的距离。
- 指数:f = e-d * |z|。d是控制浓度的参数。
- 指数的平方:f = e-(d - |z|)2。d是控制浓度的参数。


实现

FogWithDepthTexture类:

using UnityEngine;

/// <summary>
/// 雾效
/// </summary>
public class FogWithDepthTexture : PostEffectsBase
{
    private Camera targetCamera;
    public Camera TargetCamera { get { return targetCamera = targetCamera == null ? GetComponent<Camera>() : targetCamera; } }

    private Transform cameraTransform;
    public Transform CameraTransform { get { return cameraTransform = cameraTransform == null ? TargetCamera.transform : cameraTransform; } }

    [Range(0.0f, 3.0f)]
    public float fogDensity = 1.0f;         // 雾的密度

    public Color fogColor = Color.white;    // 雾的颜色

    public float fogStart = 0.0f;           // 起始高度
    public float fogEnd = 2.0f;             // 终止高度

    void OnEnable()
    {
        TargetCamera.depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnRenderImage(RenderTexture src, RenderTexture dest)
    {
        if (TargetMaterial != null)
        {
            Matrix4x4 frustumCorners = Matrix4x4.identity;  // 存近裁切平面的四个角

            float fov = TargetCamera.fieldOfView;         // 竖直方向视角范围(角度)
            float near = TargetCamera.nearClipPlane;      // 近平面距离 
            float aspect = TargetCamera.aspect;           // 宽高比

            // 计算四个角对应的向量,存到frustumCorners
            float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
            Vector3 toRight = CameraTransform.right * halfHeight * aspect;
            Vector3 toTop = CameraTransform.up * halfHeight;

            Vector3 topLeft = CameraTransform.forward * near + toTop - toRight;
            float scale = topLeft.magnitude / near;

            topLeft.Normalize();
            topLeft *= scale;

            ...  // 同样方法计算其他三个角

            frustumCorners.SetRow(0, bottomLeft);
            frustumCorners.SetRow(1, bottomRight);
            frustumCorners.SetRow(2, topRight);
            frustumCorners.SetRow(3, topLeft);

            TargetMaterial.SetMatrix("_FrustumCornersRay", frustumCorners);
            TargetMaterial.SetFloat("_FogDensity", fogDensity);
            TargetMaterial.SetColor("_FogColor", fogColor);
            TargetMaterial.SetFloat("_FogStart", fogStart);
            TargetMaterial.SetFloat("_FogEnd", fogEnd);

        }
        Graphics.Blit(src, dest, TargetMaterial);
    }
}

Shader:

Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _FogDensity ("Fog Density", Float) = 1.0
    _FogColor ("Fog Color", Color) = (1, 1, 1, 1)
    _FogStart ("Fog Start", Float) = 0.0
    _FogEnd ("Fog End", Float) = 1.0
}
SubShader {
    ...

    struct v2f {
        float4 pos : SV_POSITION;
        half2 uv : TEXCOORD0;
        half2 uv_depth : TEXCOORD1;             // 深度纹理
        float4 interpolatedRay : TEXCOORD2;     // 插值后的像素向量
    };

    v2f vert(appdata_img v) {
        ...

        // 靠近哪个角选哪个
        int index = 0;
        if (v.texcoord.x < 0.5 && v.texcoord.y < 0.5) {
            index = 0;
        } else if (v.texcoord.x > 0.5 && v.texcoord.y < 0.5) {
            index = 1;
        } else if (v.texcoord.x > 0.5 && v.texcoord.y > 0.5) {
            index = 2;
        } else {
            index = 3;
        }

        #if UNITY_UV_STARTS_AT_TOP
        if (_MainTex_TexelSize.y < 0)
            index = 3 - index;
        #endif

        o.interpolatedRay = _FrustumCornersRay[index];
        return o;
    }

    fixed4 frag(v2f i) : SV_Target {
        // 采样然后得到视角空间的线性深度值
        float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
        float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;   // 世界空间相机相加

        float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); 
        fogDensity = saturate(fogDensity * _FogDensity);                    // 限制在0~1

        fixed4 finalColor = tex2D(_MainTex, i.uv);
        finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

        return finalColor;
    }

    ENDCG

    Pass {
        ZTest Always Cull Off ZWrite Off

        CGPROGRAM  

        #pragma vertex vert  
        #pragma fragment frag  

        ENDCG  
    }
} 
作者:l773575310 发表于2017/11/25 13:56:18 原文链接
阅读:0 评论:0 查看评论

Qml列表项拖放

$
0
0

ListModel的move(int from, int to, int n)
可以将列表项进行移动
根据鼠标的拖动位置, 可以判断出需要移动项的序号

                    var lastIndex = listview.indexAt(mousearea.mouseX + listItem.x,
                                                     mousearea.mouseY + listItem.y);
                    if ((lastIndex < 0) || (lastIndex > listModel.rowCount()))
                        return;
                    if (index !== lastIndex){
                        listModel.move(index, lastIndex, 1);
                    }
                    listItem.toIndex = lastIndex;

这里写图片描述

需要完整代码请访问QtQuickExamples

作者:zhengtianzuo06 发表于2017/11/25 15:10:47 原文链接
阅读:17 评论:0 查看评论

数独的Java版解法

$
0
0

最近偶尔有玩数独,有的题太复杂了不好解,刚好看到LeetCode上有这样的题,就尝试写了个Java的解法。

1. 数独介绍

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。

左边是数独的题目,右边是完成后的结果。
数独题目 数独解法

2. 求解思路

2.1 方法定义与初始化

传入的是一个char[9][9]的数组,其中空白的部分用点号’.’代替,算出的解直接填进去即可。

public void solveSudoku(char[][] board)

我定义了一个Map<Integer, List<Character>> unsolve,它的key是当前未得到解的格子下标(从左上角开始到右下角分别是0-80),它的value是该格子可能的数值。

定义了一个初始化方法,用于初始化上面的那个map,即将每个未得到解的格子的可能数字填入1-9这9个数字。

private Map<Integer, List<Character>> initUnsolveMap(char[][] board) {
    Map<Integer, List<Character>> unsolve = new TreeMap<>();
    final List<Character> initChars = Arrays.asList('1', '2', '3', '4', '5', '6', '7', '8', '9');
    for (int y = 0; y < board.length; y++) {
        char[] chars = board[y];
        for (int x = 0; x < chars.length; x++) {
            char aChar = chars[x];
            if (aChar == '.') {
                unsolve.put(y * 9 + x, new ArrayList<>(initChars));
            }
        }
    }
    return unsolve;
}

当这个map的某个key的值只有一个元素时,那这个格子就确定了,我会将它从map中删除,并更新board。

数独最基本的解法是唯一余数法和摒除法。我采取的做法是用程序模拟人工去求解的办法。
下面开始正式求解过程。

2.1 唯一余数法

  1. 唯一余数法:用格位去找唯一可填数字,称为余数法,格位唯一可填数字称为唯余解。比如一行9个格子其中8个都填满了,那剩下的一个毫无疑问已经确定了。
  2. 我这里的做法是,依次遍历map的每个key,也就是每个未获得解的格子。
  3. 检查它所在的行、列、小方格,将不可能的数字剔除(见下面的代码)。例如所在行已经有2、5、7了,那这个格子肯定不会是这3个数字中的一个。这个步骤可以剔除不少数字。
  4. 可以看到代码中我定义了一个变量叫change,这个用于记录本轮执行过程中,map是否有发生变化。这个用途后面会说到。
  5. 对那些剔除后,只剩下一种可能数字的格子,进行移除操作,并更新board。
// 检查未解出的格子的每行、每列、每个方块,剔除已经出现的数字
for (Integer integer : unsolve.keySet()) {
    boolean thisChange = false;
    List<Character> resX = checkX(board, unsolve, integer);
    if (resX.size() > 0) {
        thisChange = true;
    }
    List<Character> resY = checkY(board, unsolve, integer);
    if (resY.size() > 0) {
        thisChange = true;
    }
    List<Character> resC = checkCube(board, unsolve, integer);
    if (resC.size() > 0) {
        thisChange = true;
    }
    if (thisChange) {
        change = true;
    }
}
// 对那些剔除后,只剩下一种可能数字的格子,进行移除操作,并更新board
List<Integer> needRemove = new ArrayList<>();
for (Integer integer : unsolve.keySet()) {
    List<Character> integers = unsolve.get(integer);
    if (integers.size() == 1) {
        board[integer / 9][integer % 9] = integers.get(0);
        needRemove.add(integer);
    }
    if (integers.size() == 0) {
        return FAIL;
    }
}
for (Integer integer : needRemove) {
    unsolve.remove(integer);
}

2.2 摒除法

  1. 摒除法:用数字去找单元内唯一可填空格,称为摒除法。例如9这个数字,在第一行和第二行都出现过了,那第三行的9一定不会在前2行出现过的宫格(9个格子的小方块)中,所以只剩下一个宫格的第三行会出现9,那如果那个宫格的那一行只有一个空位,那9就确定了。
  2. 与唯一余数法不同的是,这个时候可能该行、该列、该宫格都不止一个空位。这2种方法其实是使用不同的维度求解,一个从格子的维度,一个是从数字的维度,是互相补充的。一种方法无法继续求解后,可尝试第二种方法,就有可能求解了。
  3. 具体做法是,遍历1-9这9个数字,例如我们选择1这个数字进行说明
  4. 对1这个数字,遍历每一行的每个格子,如果该行没有1的话,那我们会检查剩下的格子的可能数字。
  5. 如果剩下的格子中,只有一个格子可能是1,那1就确认了。确认后进行删除map中的key并更新board。
//进行摒除法
for (char i = '1'; i <= '9'; i++) {
    boolean thisChange = checkNumX(board, unsolve, i);
    if (thisChange) change = true;
}

checkNumX方法:

private boolean checkNumX(char[][] board, Map<Integer, List<Character>> unsolve, char c) {
    boolean change = false;
    for (int y = 0; y < 9; y++) {
        boolean found = false;
        List<Integer> poss = new ArrayList<>();
        for (int x = 0; x < 9; x++) {
            if (board[y][x] == c) {
                found = true;
                break;
            }
            if (board[y][x] == '.') {
                int num = y * 9 + x;
                List<Character> characters = unsolve.get(num);
                if (characters.contains(c)) {
                    poss.add(num);
                }
            }
        }
        if (!found) {
            if (poss.size() == 1) {
                int integer = poss.get(0);
                board[integer / 9][integer % 9] = c;
                unsolve.remove(integer);
                change = true;
            }
        }
    }
    return change;
}

2.3 递归调用

  • 当使用上述方法求解后,可能board和unsolve这个map已经发生了变化(例如某个格子已经解出来了),那这个时候,重新进行上述两种方法,将可以排除新的数字。这也是我们前2个方法中有记录change变量的用处。
  • 在下面的代码中,我们判断change是否为true,也就是本轮是否发生了变化。如果发生了变化,我们进行新一轮求解。
  • 如果没有发生变化,有3种可能:
    • 已经全部解出来了,这个时候我们标记为SUCCESS
    • 前2种方法已经无法找出解了,这个时候我标记为UNDONE
    • 求解失败,当前无解,标记为FAIL
  • 这里我们用到了一个方法isValidSudoku,用来校验当前board的有效性,比较简单,就是检查一下每行、每列、每宫格有没有重复的数字,这里就不说了。
  • 当本轮change为true时,将重新递归调用本方法进行求解,直至某一轮未改变停止,将状态归为上面3种中的一种。
if (change) {
    return solve(board, unsolve);
} else {
    if (unsolve.size() > 0) {
        return UNDONE;
    } else {
        boolean valid = isValidSudoku(board);
        if (valid) {
            return SUCCESS;
        } else {
            return FAIL;
        }
    }
}

2.4 假设法

  1. 经过上面的递归尝试数独的2种基本求解方法,我发现这种方式,可以应对软件中的难度为简单和中等的数独题目,但是针对难度为困难的题目,就会卡住无法继续求解了(也就是状态为UNDONE)。
  2. 数独还有一些其他的复杂解法,例如区块摒除法)、数组、二链列、唯一矩形、全双值格致死解法、同数链、异数链及其他数链的高级技巧等等,这些解法代码层面实现起来较为复杂,故放弃。
  3. 这里我选择了比较适合代码编写的方式,也就是假设法,或者叫暴力求解法。也就是在上述方法得到的结果的基础上,找出那些可能数字比较少的一个格子。
  4. 针对这个格子,我分别设它为可能的每一个数字,并各自启动一个新的实例去求解。例如某个格子可能为3、5这2个数字,那我创建2个新的board,分别填入3、5这2个数字,并重新调用上面的求解办法。
  5. 按上面的方式继续求解,那会得到一个结果列表,里面可能有的是UNDONE,有的是FAIL和有的是SUCCESS,如果有SUCCESS,那我们的求解过程就结束了,将成功的board返回即可。而其中如果出现了FAIL,说明这个实例不可能存在,那就排除掉。
  6. 比较复杂的是出现UNDONE了,说明这一轮的假设仍然求不出解。那我们可以再继续假设。例如第一轮假设我们假设了3、5。然后返回了2个UNDONE,那我们还要继续在3的结果里面,继续假设,例如还是有2个可能性。这样我们可能就产生了4个假设的结果,再进行上面的判断。
  7. 说起来可能蛮复杂,但是代码实现起来挺简单的,也就是再次应用了递归的方法。如下所示:
private int recursion(char[][] rawBoard, Map<Integer, List<Character>> unsolve) {
    int status = solve(rawBoard, unsolve);//进行常规的求解过程,并获得当前求解的状态
    if (status == UNDONE) {//只有UNDONE的状态需要继续假设,SUCCESS和FAIL都不需要
        List<char[][]> boards = findPossBoard(rawBoard, unsolve);//根据当前结果进行假设,获得假设的board列表
        for (char[][] board : boards) {
            int newStatus = recursion(board, initUnsolveMap(board));//递归调用本方法,展开新的求解与假设过程
            if (newStatus == SUCCESS) {
                for (int i = 0; i < 9; i++) {
                    System.arraycopy(board[i], 0, rawBoard[i], 0, 9);//成功时,将成功的board赋值给原始的board数组。如果这不是最外层的递归,那将一层一层往上传递,直至传给最原始的board数组。
                }
                return SUCCESS;
            }
        }
        return FAIL;
    } else {
        return status;
    }
}

3. 总结

经过上面的4种方法共同求解,目前只要有解的数独都可以解出来。完整的代码见我的github:https://github.com/lnho/LeetCode_Java/blob/master/src/main/java/com/lnho/leetcode/solution/Solution037Simple.java ,里面还有一些测试的用例。

作者:Lnho2015 发表于2017/11/25 15:27:54 原文链接
阅读:53 评论:0 查看评论

KEIL调用GCC编译STM32

$
0
0

本篇记录KEIL调用GCC编译STM32

  1. 需要准备的工具有

    GCC编译器;我的版本5.4 下载地址https://launchpad.net/gcc-arm-embedded/

    KEIL;我的版本是5.23

    STM32对应的固件库;我的版本是F10x3.5

    一个已经配置好的STM32工程,led闪烁什么的都行,具体可参照正点原子的教程。

另外:版本问题不大,只需修改几个地方即可

  1. 解压GCC工具,编译ARM工具的名字应该是gcc-arm-none-eabi,解压到keil安装文件夹,可以随意放置,为了管理方便把他放在F:\Keil_v5\ARM\GCC,GCC文件夹是我新建的。如下图所示

  2. 在已经建立好的STM32工程中修改配置

    按照下图勾选Use GCC Compiler,并填入工具arm-none-eabi-和工具路径F:\Keil_v5\ARM\GCC\,然后点击确定

    接下来就是一些细节设置:

    首先是CC下的设置,Define中和原工程一样即可,中间全部勾选,然后是库文件的路径什么的和原工程一样,接着就是-ffunction-sections -fdata-sections,这个要加上去

     

    Linker中较为关键,首先是ld文件的路径.\Core\stm32_flash.ld,然后是

    -Wl,--gc-sections,将未调用的函数不编译进输出文件。接下来就说ld文件。

  3. 调用.ld文件、替换.S文件、修改core_cm3.c文件BUG

    1)调用.ld文件

.ld链接文件,里面规定了FLASH、RAM大小等信息,文件在固件库中STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template\TrueSTUDIO可找到,我选择的是10C里面的.ld文件。然后复制到工程下面,不能添加进工程,只在linker中做链接使用。打开文件第36行起修改相关信息

首先是RAM大小,然后是堆栈大小,FLASH等起始地址和长度,根据自己的实际情况修改。

/* Highest address of the user mode stack */

_estack = 0x20010000; /* end of 64K RAM */

 

/* Generate a link error if heap and stack don't fit into RAM */

_Min_Heap_Size = 0; /* required amount of heap */

_Min_Stack_Size = 0x200; /* required amount of stack */

 

/* Specify the memory areas */

MEMORY

{

FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K

RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K

MEMORY_B1 (rx) : ORIGIN = 0x60000000, LENGTH = 0K

}

2)替换.S文件

ARMCC和GCC所用的.S文件是不一样的,书写格式不一样,固件库STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\gcc_ride7中选择自己的.S文件并加入工程,替换掉原来的.S文件。

    3)修改core_cm3.c文件BUG

第736和753行分别修改为

__ASM volatile ("strexb %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );

__ASM volatile ("strexh %0, %2, [%1]" : "=&r" (result) : "r" (addr), "r" (value) );

 

 

闻道有先后,术业有专攻

作者:shaynerain 发表于2017/11/25 15:41:55 原文链接
阅读:18 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>