0%

思路指南

数据结构的存储方式

其实只有两种,顺序存储(数组)和链式存储(链表),要有递归的思想,自顶而下,从抽象到具体;

队列、栈:用数组实现,要处理扩容、缩容问题;用链表实现,需要更多内存空间存储节点指针;

图:用二维数组实现,邻接矩阵,判断连通性迅速单图如果稀疏则会耗费时间;链表实现:邻接表,节省空间,但操作效率不够;

散列表:通过散列函数将键映射到一个大数组中,拉链法:链表特性,操作简单但需要额外空间存储指针;线性探查法:数组特性,以便连续寻址,不需要指针存储空间但操作复杂;

树:用数组实现:堆,完全二叉树;链表实现,正常二叉树,二叉搜索树、AVL树、红黑树、区间树、B树;

二者优缺点

数组:紧凑连续存储,可以随机访问,通过索引快速搜索,相对节省空间,但需要一次分别配够空间,若需要扩容,则重新分配空间并拷贝过去,T(n) = O(n),且每次进行插入与删除时,必须搬移后面所有数据以保持连续,T(n) = O(N)。

链表:元素不连续,靠指针指向下一个元素的位置,故不存在数组的扩容问题;在知道前驱、后驱时操作指针插入、删除的T(n)=O(1),但同样因为不连续从而不能根据索引计算对应元素地址,无法随机访问,同样会消耗更多存储空间。

基本操作

遍历+访问;具体为:增删改查。

线性的,以for/while迭代为代表;

1
2
3
4
5
void traverse(int[] arr) { 
for (int i = 0; i < arr.length; i++) {
// 迭代访问 arr[i]
}
}

链表遍历框架,兼具迭代与递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 基本的单链表节点 */ 
class ListNode {
int val;
ListNode next;
}
void traverse(ListNode head) {
for (ListNode p = head; p != null; p = p.next) {
// 迭代访问 p.val
}
}
void traverse(ListNode head) {
// 递归访问 head.val
traverse(head.next);
}

二叉树遍历框架:典型的非线性递归遍历结构

1
2
3
4
5
6
7
8
9
/* 基本的⼆叉树节点 */ 
class TreeNode {
int val;
TreeNode left, right;
}
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
}

N叉树的遍历:非线性递归

1
2
3
4
5
6
7
8
9
/* 基本的 N 叉树节点 */ 
class TreeNode {
int val;
TreeNode[] children;
}
void traverse(TreeNode root) {
for (TreeNode child : root.children)
traverse(child);
}

N叉树的遍历可扩展为图的遍历,如何实现图的环?:用个布尔数组visited做标记。

你就会发现只要涉及递归的问题,都是树的问题;其实很多动态规划问题就是在遍历⼀棵树,你如果对树的遍历操作烂熟于心,起码知道怎么把思路转化成代码,也知道如何提取别⼈解法的核⼼思 路。再看看回溯算法,前⽂回溯算法详解⼲脆直接说了,回溯算法就是个 N 叉 树的前后序遍历问题,没有例外。

medium二叉树刷题(树的核心是递归遍历)

二叉树中序遍历

方法一:基于栈的遍历

1、如果left节点存在,就入栈,然后跳left;

2、如果left和right都不存在,则保存当前节点,然后出栈,并让left等于null;

3、如果right存在,且left不存在,则保存当前节点,然后跳right。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
// 中序遍历
const inorderTraversal = (root) => {
let list = [];
let stack = [];
let node = root;

while(node || stack.length) {
// 遍历左子树
while(node) {
stack.push(node);
node = node.left;
}
node = stack.pop();
list.push(node.val);
node = node.right;
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const inorderTraversal = root => {
let res = [], stack = []
while (root || stack.length) {
if (root.left) {
stack.push(root);
root = root.left;
} else if (!root.left && !root.right) {
res.push(root.val);
root = stack.pop();
root && (root.left = null);
} else if (root.right) {
res.push(root.val);
root = root.right;
}
}
return res
}

方法二:递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var inorderTraversal = function(root) {
let arr = [];
if(!root) return [];

let nums = [root];
//递归方法实现
MiddleOrder(root,arr);
return arr;
};

function MiddleOrder(root,arr){
if(root.left){
MiddleOrder(root.left,arr);
}
arr.push(root.val);
if(root.right){
MiddleOrder(root.right,arr);
}
}

前序遍历二叉树

方法一:基于栈的迭代实现

首先根入栈将根节点出栈,将根节点值放入结果数组中

然后遍历左子树、右子树,因为栈是先入后出,所以,我们先右子树入栈,然后左子树入栈

继续出栈(左子树被出栈)…….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 前序遍历
const preorderTraversal = (root) => {
const list = [];
const stack = [];
let node = root;

while (node || stack.length){
while (node){
stack.push(node.right);
list.push(node.val);
node = node.left;
}
node = stack.pop();
}
return list;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 前序遍历
var preorderTraversal = (root) => {
let result = []
var preOrderTraverseNode = (node) => {
if(node) {
// 先根节点
result.push(node.val)
// 然后遍历左子树
preOrderTraverseNode(node.left)
// 再遍历右子树
preOrderTraverseNode(node.right)
}
}
preOrderTraverseNode(root)
return result
};

后序遍历二叉树

按照左子树-根-右子树的方式,将其转换成迭代方式。

思路:每到一个节点 A,因为根要最后访问,将其入栈。然后遍历左子树,遍历右子树,最后返回到 A。

但是出现一个问题,无法区分是从左子树返回,还是从右子树返回。

因此,给 A 节点附加一个标记T。在访问其右子树前,T 置为 True。之后子树返回时,当 T 为 True表示从右子树返回,否则从左子树返回。

当 T 为 false 时,表示 A 的左子树遍历完,还要访问右子树。

同时,当 T 为 True 时,表示 A 的两棵子树都遍历过了,要访问 A 了。并且在 A 访问完后,A 这棵子树都访问完成了。

1, 先遍历左节点, 当遍历到末尾节点时, 记录值

2, 然后跳回上一层节点, 顺便让left等于null

3, 再遍历右节点, 同样是遍历到末尾节点时, 记录值

4, 第二次返回时, 让右节点等于null

通过这种人为的方式,不断创造末尾节点值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const postorderTraversal = root => {
let res = [], stack = [];
while (root || stack.length) {
if (root.left) {
stack.push(root);
root = root.left;
}
else if (root.right) {
stack.push(root);
root = root.right;
}
else {
res.push(root.val);
root = stack.pop();
if (root && root.left) root.left = null;
else if (root && root.right) root.right = null;
}
}
return res;
}

不同的二叉搜索树

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

思路:动态规划;为了构建一颗二叉搜索树,可以遍历每一个数字i,将其作为树根,并将1…i-1作为左子树,i+1…n作为右子树,接着可以按照同样方式递归构建左右子树。原问题可以分成规模较小的两个子问题,且解可以复用,因此用动态规划处理。

具体步骤:题目要求是计算不同二叉搜索树的个数。为此,我们可以定义两个函数:G(n): 长度为 n 的序列能构成的不同二叉搜索树的个数。F(i, n): 以 i 为根、序列长度为 n 的不同二叉搜索树个数 (1 \leq i \leq n)(1≤i≤n)。可见,G(n) 是我们求解需要的函数。

动态规划问题的关键在于构建状态转移方程。

1
2
3
4
5
6
7
8
9
10
11
12
var numTrees = function(n) {
const G = new Array(n + 1).fill(0);
G[0] = 1;
G[1] = 1;

for (let i = 2; i <= n; ++i) {
for (let j = 1; j <= i; ++j) {
G[i] += G[j - 1] * G[i - j];
}
}
return G[n];
};

在本题目的扩展下,如果要求生成所有由1-n节点所组成的二叉搜索树。

和二叉搜索树一的解法不同,一的目的是求数量,而这道题是求具体的解,所以不能从树的规律来简化;利用树的递归特性,采用DFS来递归求解;

n个数组成的二叉树,分为分别以1,2,3…,n为顶点的树;以i为例:f[i]的左边树就是1到i-1个数组成的二叉搜索树,因为左边的节点都比顶点小;f[i]的右边子树就是[i+1]到n的数组成的二叉搜索树,因为右边的节点都比顶点大;左右两侧都是树的数组;组合之后,f[i]的数组组合就得到了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {number} n
* @return {TreeNode[]}
*/
var generateTrees = function (n) {
if (n == 0) return [];
function getTree(s, e) {
if (s > e) return [null];
if (s == e) return [new TreeNode(s)];
var tree = [];
var i = s;
while (i <= e) {
var lefts = getTree(s, i - 1), rights = getTree(i + 1, e);
while (lefts.length) {
var left = lefts.pop(), j = 0;
while (j < rights.length) {
var right = rights[j];
j++;
var node = new TreeNode(i);
node.left = left;
node.right = right;
tree.push(node);
}
}
i++;
}
return tree;
}
return getTree(1, n);
};

从构建1棵树到构建n棵树,该递归思路其实清晰可见。

验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。假设一个二叉搜索树具有如下特征:1、节点的左子树只包含小于当前节点的数。2、节点的右子树只包含大于当前节点的数。3、所有左子树和右子树自身必须也是二叉搜索树。

方法一:设计一个递归函数来判断,函数表示考虑以root为跟的子树,其子树所有节点是否都在(l,r)的范围内。不在则直接返回,在的话则继续递归调用其左右子树是否满足。

1
2
3
4
5
6
7
8
const helper = (root, lower, upper)=>{
if (root == null) return ture;
if (root.val <= lower || root.val >= upper) return false;
return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
}
var isValidBST = function(root) {
return helper(root, -Infinity, Infinity);
};

方法二:中序遍历,二叉搜索树的中序遍历结果一定是升序序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var isValidBST = function(root) {
let stack = [];
let inorder = -Infinity;

while (stack.length || root !== null) {
while (root !== null) {
stack.push(root);
root = root.left;
}
root = stack.pop();
// 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
if (root.val <= inorder) return false;
inorder = root.val;
root = root.right;
}
return true;
};

对称二叉树

验证一个二叉树是否对称

方法一:递归;左子树和右子树镜像对称则这个树是对称的,1、根节点的值相同;2、一个树的右子树跟另一个树的左子树镜像对称。其实本质跟二叉树相等的递归一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const isSymmetric = (root) => {
const check = (left, right) => {
if (left == null && right == null) return true;
if (left && right) {
return left.val == right.val &&
check(left.left, right.right) &&
check(left.right, right.left);
}
return false;
};

if (root) {
return check(root.left, root.right);
} else {
return true;
}
};

方法二:BFS广度遍历法;入队列的顺序:1、左子树的左子树,右子树的右子树;2、左子树的右子树,右子树的左子树;出队列的时候,检查两两是否对称。

JS中array类型提供了pop()和push()方法来模仿栈这个数据结构的方法;push()方法接受任意数量的参数,并将其逐个添加到数组尾部,并返回修改后数组的长度;pop()方法则从数组末尾溢出最后一项,减少数组的length值,然后返回溢出的项。

而JS用push()和shift()方法结合来模仿队列;shift()方法溢出数组中的第一项并返回该项,同时将数组长度减一;同时也提供了unshift()方法,能在数组前端添加任意项并返回新数组的长度。因此同时使用unshift()和pop()可以从相反方向模拟数列,在前端添加项,从末端移除项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const isSymmetric = (root) => {
if (root == null) return true;

const leftQueue = [root.left]; // 队列存放左子树
const rightQueue = [root.right]; // 队列存放右子树

while (leftQueue.length && rightQueue.length) {
const left = leftQueue.shift();
const right = rightQueue.shift();
// 左右子树都为空,没有子节点可入列,continue
if (left == null && right == null) {
continue;
}
if ((left == null && right) || (left && right == null)) {
return false;
}
if (left.val != right.val) {
return false;
}
leftQueue.push(left.left);
rightQueue.push(right.right);
leftQueue.push(left.right);
rightQueue.push(right.left);
}
// 其中一个子树还有节点没遍历,说明不对称
if (leftQueue.length || leftQueue.length) {
return false;
} else {
return true;
}
};

方法三:利用两个栈来对递归进行模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var isSymmetric = (root) => {
if (!root) return true
let leftStack = [], rightStack = [] // 维护两个栈
let curLeft = root.left // 当前的左子树
let curRight = root.right // 当前的右子树
while (curLeft || curRight || leftStack.length || rightStack.length) {
while (curLeft) { // 左子树存在
leftStack.push(curLeft) // 推入leftStack栈
curLeft = curLeft.left // 不断将左子树入栈
}
while (curRight) { // 右子树存在
rightStack.push(curRight) // 推入rightStack栈
curRight = curRight.right // 不断将右子树压入栈
}
if (leftStack.length !== rightStack.length) return false
// 栈的高度不相等,说明结构不对称
curLeft = leftStack.pop() // 栈顶节点出栈,赋给curLeft
curRight = rightStack.pop() // 栈顶节点出栈,赋给curRight
if (curLeft.val !== curRight.val) return false
// 两个栈出栈的节点值不相等 不对称
curLeft = curLeft.right // 考察左子树的right
curRight = curRight.left // 考察右子树的left
}
return true
}

二叉树的BFS与DFS

DFS:深度优先搜索一般使用递归的方法进行实现。

1
2
3
4
5
6
7
var dfs = (root) => {
if (root == null){
return ;
}
dfs(root.left);
dfs(root.right);
}

BFS则是使用队列的数据结构来进行遍历;

1
2
3
4
5
6
7
8
9
10
11
12
13
var dfs = (root) => {
var queue = [];
queue.push(root);
while (queue.length != 0){
var node = queue.shift();
if (node.left != null){
queue.push(node.left);
}
if (node.right != null){
queue.push(node.right);
}
}
}

递归的方法其实隐含地使用了系统的栈,不需要自己去维护数据结构;BFS这种独特的遍历方式正式其用于求解层序遍历和最短路径问题的根本原因。

层序遍历与最小路径问题

1、层序遍历;利用BFS遍历二叉树无法区分队列中的节点来自哪一层,因此需要修改下代码记录队列中节点数量n(即这一层的节点数),然后一口气处理该层n个节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var dfs = (root) => {
var queue = [], res = [], level = [];
queue.push(root);
while (queue.length != 0){
var n = queue.length;
for (var i = 0; i < n; i++){
var node = queue.shift();
level.push(node.val);
if (node.left != null){
queue.push(node.left);
}
if (node.right != null){
queue.push(node.right);
}
}
res.pop(level);
}
return res;
}

其实根据上述层序遍历的代码,要实现二叉树层序遍历的反向输出,只需要修改最后res的入队列方式即可,用unshift来代替pop。

2、最短路径问题:在树中一个节点到一个节点的路径是唯一的,但在图中可能有多条路径,找寻哪一条路径最短。在BFS中,距离源点越近的点会先被遍历到。(Dijkstra算法解决的是带权最短路径问题,而BFS解决的是无权最短路径问题)

二叉树的锯齿形层次遍历

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)

思路:典型广度优先题目;广度优先通过队列处理 【深度优先用栈】

1、将一层记录在数组中 并记录数组长度找下一行所有数据将数组首位弹出 将首位的左右节点追在数组后;

2、按照记录的数组长度 将上层的结点全部弹出后 此时数组只剩下下一行结点了 此时就完成了一层的遍历;类似上题 与上题区别 每层输出反向 加个条件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
* @return {number[][]}
*
var zigzagLevelOrder = function(root) {
if(root==null)
return []
var arr=[root]
var res=[]
var go=true
while(arr.length>0){
var n=arr.length
var now=[]
if(go){
while(n-->0){
var node=arr.shift()
now.push(node.val)
if(node.left!=null)arr.push(node.left)
if(node.right!=null)arr.push(node.right)
}
res.push(now)
}else{
while(n-->0){
var node=arr.pop()
now.push(node.val)
if(node.right!=null)arr.unshift(node.right)
if(node.left!=null)arr.unshift(node.left)
}
res.push(now)
}
go=!go
}
return res
};

前序跟中序遍历构造二叉树

思路:构建一个二叉树需要构建三部分:root、左子树、右子树;左子树、右子树的构建,又包括:root、左子树、右子树解题关键在于定位出根节点,划分出左右子树,然后 递归 构建左右子树

具体做法:preorder 数组的第一项肯定是根节点 —— 因为前序遍历的顺序是 [根| 左|右 ][根∣左∣右]。由根节点,在 inorder [左 | 根 | 右][左∣根∣右] 中划分出左、右子树的 inorder 序列。
通过 inorder 中左右子树的节点个数,在 preorder 中确定左、右子树的 preorder 序列。得到左、右子树的 preorder 和 inorder 序列,就能递归构建左右子树。

1
2
3
4
5
6
7
8
const buildTree = (preorder, inorder) => {
if (inorder.length == 0) return null;
const root = new TreeNode(preorder[0]);
const mid = inorder.indexOf(preorder[0]);
root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid));
root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1));
return root;
};

优化一:字符串截取性能消耗比较大,没必要每次均将preorder、inorder切割;用两个指针表示即可,写一个接受指针的辅助函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
const buildTree = (preorder, inorder) => {
const helper = (p_start, p_end, i_start, i_end) => {
if (p_start > p_end) return null;
let rootVal = preorder[p_start]; // 根节点的值
let root = new TreeNode(rootVal); // 根节点
let mid = inorder.indexOf(rootVal); // 根节点在inorder的位置
let leftNum = mid - i_start; // 左子树的节点数
root.left = helper(p_start + 1, p_start + leftNum, i_start, mid - 1);
root.right = helper(p_start + leftNum + 1, p_end, mid + 1, i_end);
return root;
};
return helper(0, preorder.length - 1, 0, inorder.length - 1);
};

再次优化:每次递归都要indexof寻找根节点位置,耗费性能;可提前把inorder数组元素和索引存到hash表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const buildTree = (preorder, inorder) => {
const map = new Map();
for (let i = 0; i < inorder.length; i++) {
map.set(inorder[i], i);
}
const helper = (p_start, p_end, i_start, i_end) => {
if (p_start > p_end) return null;
let rootVal = preorder[p_start]; // 根节点的值
let root = new TreeNode(rootVal); // 根节点
let mid = map.get(rootVal); // 根节点在inorder的位置
let leftNum = mid - i_start; // 左子树的节点数
root.left = helper(p_start + 1, p_start + leftNum, i_start, mid - 1);
root.right = helper(p_start + leftNum + 1, p_end, mid + 1, i_end);
return root;
};
return helper(0, preorder.length - 1, 0, inorder.length - 1);
};

中序跟后序遍历构造二叉树

通常从先序序列或者后序序列开始,根据不同遍历方法的规律,选择合适的节点构造树。例如:先序序列的 第一个 节点是根节点,然后是它的左孩子,右孩子等等。后序序列的 最后一个 节点是根节点,然后是它的右孩子,左孩子等等。

从先序/后序序列中找到根节点,根据根节点将中序序列分为左子树和右子树。从中序序列中获得的信息是:如果当前子树为空(返回 None),否则继续构造子树。

创建 hashmap 存储中序序列:value -> its index 。

方法 helper 的参数是中序序列中当前子树的左右边界,该方法仅用于检查子树是否为空。下面分析 helper(in_left = 0, in_right = n - 1) 的逻辑:

1、如果 in_left > in_right,说明子树为空,返回 None。

2、选择后序遍历的最后一个节点作为根节点。

3、假设根节点在中序遍历中索引为 index。从 in_left 到 index - 1 属于左子树,从 index + 1 到 in_right 属于右子树。

4、根据后序遍历逻辑,递归创建右子树 helper(index + 1, in_right) 和左子树 helper(in_left, index - 1)。

5、返回根节点 root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var buildTree = function(inorder, postorder) {
let p = i = postorder.length - 1;
let build = (stop) => {
if(inorder[i] != stop) {
let root = new TreeNode(postorder[p--])
root.right = build(root.val)
i--
root.left = build(stop)
return root
}
return null
}
return build()
}

翻转二叉树

递归三步法:1、确定递归函数的参数和返回值;2、确定终止条件;3、确定单层递归的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if (root == null) return null;
const left = invertTree(root.left);
const right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
};

将有序数组转换为二叉搜索树

该解题的本质不在于二叉搜索树和中序遍历,本质是平衡。

如何满足平衡条件:每次把一组数最中间的位置作为树的头拎起来,剩余的部分平均分两份,剩余的一个随便给左子树或右子树;

1
2
3
4
5
6
def 做一棵树(数组的哪个段落要做成树):#这个段落用索引表示即可,与具体数字无关
#假设这个段落叫A吧
树的根部 = 这个段落A最中间的数字
树的左边 = 做一棵树(这个段落A的左边部分)
树的右边 = 做一棵树(这个段落A的右边部分)
return 这棵树

二叉搜索树中序遍历后正好是一个递增的数组;因此,其实这个树就是我们中序遍历二叉树的结果。因此我们以和中序遍历相反的方式,从中间开始取root进行构建二叉树即可。

#可以看到整个题解只和index有关,和数组里的具体数字无关, #因为题目给出的“有序数列”帮助我们满足了“二叉搜索树”的条件。因此只需要操作index来对节点进行重新构建即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const sortedArrayToBST = (nums) => {

const buildBST = (nums, start, end) => {
if (start > end) {
return null;
}
//无符号右移>>>的操作是:丢弃右边指定位数,并左边补上0,因此等于除以2。
const mid = (start + end) >>> 1;
const root = new TreeNode(nums[mid]);

root.left = buildBST(nums, start, mid - 1);
root.right = buildBST(nums, mid + 1, end);

return root;
};

return buildBST(nums, 0, nums.length - 1);
};

二叉树hard题目

恢复二叉搜索树

二叉搜索树中的两个节点被错误地交换。

请在不改变其结构的情况下,恢复这棵树。

找到二叉搜索树中序遍历得到值序列的不满足条件的位置。本方法开辟一个新数组 \textit{nums}nums 来记录中序遍历得到的值序列,然后线性遍历找到两个位置 i和 j,并重新遍历原二叉搜索树修改对应节点的值完成修复,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const inorder = (root, nums) => {
if (root === null) {
return;
}
inorder(root.left, nums);
nums.push(root.val);
inorder(root.right, nums);
}

const findTwoSwapped = (nums) => {
const n = nums.length;
let x = -1, y = -1;
for (let i = 0; i < n - 1; ++i) {
if (nums[i + 1] < nums[i]) {
y = nums[i + 1];
if (x === -1) {
x = nums[i];
}
else break;
}
}
return [x, y];
}

const recover = (r, count, x, y) => {
if (r !== null) {
if (r.val === x || r.val === y) {
r.val = r.val === x ? y : x;
if (--count === 0) {
return;
}
}
recover(r.left, count, x, y);
recover(r.right, count, x, y);
}
}

var recoverTree = function(root) {
const nums = [];
inorder(root, nums);
const [first, second] = findTwoSwapped(nums);
recover(root, 2, first, second);
};

HTML定义了网页的内容;CSS 描述了网页的布局;JavaScript确定了网页的行为。

一个完成的JS实现由下面三部分组成:1、核心ECMAScript;2、文档对象模型DOM;2、浏览器对象模型BOM。

1、ECMAScript与Web浏览器没有依赖关系,就是对实现该标准规定的各方面内容的语言的描述;

2、文档对象模型DOM:针对XML但经过扩展用于HTML的应用程序编程接口API;DOM把整个页面映射为一个多层节点结构,HTML页面中每个组成部分都是某种类型的节点,这些节点又包含着不同类型的数据;通过DOM创建的这个表示文档的树形图,开发者获得了控制页面内容和结构的主动权。借助DOM提供的API,开发人员可以自如地删除、添加、替换或修改任何节点。

3、浏览器对象模型BOM:开发人员使用BOM控制浏览器显示的界面以外的部分,

1、使用script元素嵌入JavaScript代码时,只须为script指定type属性,type=”text/javascript”,之后将JS代码直接放在元素内部,且包含在script内部的JS代码将从上至下被解释;即在解释器对script页面内的所有代码求值完以前,页面中其余内容都不会被浏览器加载或显示。

2、如果要通过script元素来包含外部JS文件,那么需要SRC属性,是一个指向外部JS文件的链接;

且带有src属性的script元素不应该在其script和/script标签之间再包含额外的JS代码,如果包含则只会下载并执行外部脚本文件而忽略内部嵌入的代码。

甚至SRC属性可以包含外部域的JS

1
2
<script type="text/javascript" src="example.js" />
<script type="text/javascript" src="http://www.somewhere.com/afile.js"></script>

现代WEB应用一般把全部JS引用放在body元素中页面内容的后面,以免加载时间过长;或者加上defer作为延迟脚本(延迟至html页面加载完毕),加上async作为异步脚本(不让页面等待两个脚本下载、执行,从而异步加载其他内容)。

一般还是尽可能得用外部文件来包含JS代码,可维护性、可缓存、适应未来。

1、基本概念

变量

松散类型变量,定义时用var后跟变量名,该变量可以用于保存任何值;但用var操作符定义的变量将成为定义该变量作用域中得局部变量,如果在函数中定义,则函数退出后便会销毁该变量。省略var操作符时可定义全局变量,但及其不推荐。

数据类型

5种简单得:Undefined(未定义)、Null(空)、Boolean(布尔型)、Number(数值)、String(字符串);还有一种复杂的:Object(对象),数据类型具有动态性,无需再定义。用typeof操作符来返回给定变量的数据类型。

Undefined:只有一个值undefined,使用var声明变量时未对其初始化;

Null:只有一个值null,逻辑上表示一个空对象指针,因此使用typeof检测null值时会返回object,定义的变量要存储值则先初始化为null,可直接检查该值判断是否已经保存了一个对象的引用。事实上,undefined值派生自null值,因此它们的相等性测试返回true。

Boolean类型:只有两个字面值:true、false,且区分大小写,即True和False不是布尔类型,只是标识符,调用转型函数Boolean()可以转换成对应的布尔值。

Number类型:表示整型和浮点数值,十进制、八进制(字面值第一位为0)、十六进制(字面值第一位为0X),ECMAScript会适时地自动将浮点型变为整型,对于极大、极小的数可用e来表示法表示的浮点数值表示。数值范围:Number.MIN_VALUE到Number.MAX_VALUE。

NaN:非数值,是一个特殊的数值,用于表示一个本来要返回数值的操作数未返回数值的情况。任何涉及NaN的操作都会返回NaN,因此用isNaN()函数,不能转换成数值的值都会导致该函数返回true,字符串”blue“不能转换成数值,因此返回true。

1
2
var floatNum = 3.125e7
//e表示法表示的数值等于e前面的数乘以10的指数次幂

Number():用于任何数据类型转换成数值;parseInt():处理整数;parseFloat():处理浮点数字字符。

String类型:单、双引号没有区别,包含一些转义序列;ECMAScript中字符串不可变,要改变一个字符串,首先要销毁原来的字符串,再用一个包含新值得字符串填充该变量。

toString()方法,返回对应值字符串;String()转型函数。

object类型:对象其实就是一组数据和功能的集合,对象可以通过执行new操作符后跟要创建的对象类型的名称来创建。Object的每个实例都具有下列方法、属性:

constructor:保存着用于创建当前对象的函数,构造函数;

hasOwnProperty(propertyName):用于检查给定的属性在当前对象实例中是否存在,其中作为参数的属性名propertyName必须以字符串的形式指定;

isPrototypeOf(object):用于检查传入的对象是否是当前对象的原型;

propertyIsEnumerable(propertyName):用于检查给定的属性是否能够使用for-in语句来枚举,与hasOwnProperty(propertyName)方法一样,作为参数的属性名必须以字符串形式指定;

toLocaleString():返回对象的字符串表示,该字符串与执行环境的地区对应;

toString():返回对象的字符串表示;

valueOf():返回对象的字符串、数值或布尔值表示。

操作符

一元加操作符:对非数值应用时,该操作符会像Number转型函数一样对这个值执行转换;一元减操作符主要用于表示负数;

位操作符:用于最基本的层次上,即按内存中表示数值的位来操作数据,按位非~;按位与&;按位或 | ;按位异或 ^;左移 <<;有符号的

1、CSS语法

两个主要部分:选择器 + 一条或者多条声明;每条声明由一个属性和一个值组成,属性是希望设置的样式属性,每个属性有一个值。CSS声明总是以分号结束,声明总以大括号括起来。

如果要在HTML元素中设置CSS样式,需要在元素中设置id、class选择器。

id选择器可以为标有特定id的HTML元素指定特定的样式,HTML元素以id属性来设置id选择器,CSS中id选择器以#来定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
#para1
{
text-align:center;
color:red;
}
</style>
</head>

<body>
<p id="para1">Hello World!</p>
<p>这个段落不受该样式的影响。</p>
</body>
</html>

class选择器用于描述一组元素的样式,区别在于class可以在多个元素中使用,

插入CSS

外部样式表:改变一个文件来改变一个站点的外观;每个页面使用 link标签链接到样式表。link标签在文档的头部:而浏览器会从文件mystyle.css中读到样式,并根据它来格式文档。外部样式表可在任何文本编辑器进行编辑,不能包含任何html标签,应该以CSS的扩展名进行保存,

1
2
3
hr {color:sienna;}
p {margin-left:20px;}
body {background-image:url("/images/back40.gif");}

内部样式表:当单个文档需要特殊格式时,可以使用,可用style标签在文档头部定义内部样式表。

1
2
3
4
5
6
7
<head>
<style>
hr {color:sienna;}
p {margin-left:20px;}
body {background-image:url("images/back40.gif");}
</style>
</head>

内联样式:

多重样式:某些属性在不同样式表中被同样的选择器定义,那么属性值将从更具体的样式表中被继承过来。

多重样式优先级:可以在同一个HTML文档内部引用多个外部样式表。优先级如下:内联样式>内部样式>外部样式>浏览器默认样式

CSS背景

背景属性用于定义HTML元素的背景:

background-color:背景颜色;background-image:背景图像;background-repeat:设置背景不平铺;background-position:背景定位;

CSS文本格式

格式:颜色color、对齐方式text-align、文本修饰text-decoration、文本大小写转换text-transform、文本缩进text-indent。

字体:两种类型的字体系列名称,通用字体、特定字体。font-family属性设置文本的字体系列,

CSS链接

四个基本的链接样式实例:

a:link - 正常,未访问过的链接

a:visited - 用户已访问过的链接

a:hover - 当用户鼠标放在链接上时

a:active - 链接被点击的那一刻

CSS盒子模型

所有HTML元素可以看作盒子,在CSS中,box model这一术语是用来设计和布局时使用的,CSS盒模型本质上是一个盒子,封装周围的HTML元素,包括:边框、边距、填充、实际内容。盒模型允许我们在其他元素和周围元素边框之间的空间放置元素。

不同部分的说明:

outline轮廓:绘制于元素周围的一条线,位于边框边缘的外围,起突出元素的作用。

Margin(外边距)** - 清除边框外的区域,外边距是透明的。可以单独改变元素四周边框,也可以一次改变所有属性。

Border(边框)** - 围绕在内边距和内容外的边框,允许一个元素边框的样式和颜色。

Padding(内边距)** - 清除内容周围的区域,内边距是透明的。

Content(内容)** - 盒子的内容,显示文本和图像

CSS分组与嵌套

分组选择器:在样式表中有很多具有相同样式的元素,可使用分组选择器,每个选择器用逗号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
h1,h2,p
{
color:green;
}
</style>
</head>

<body>
<h1>Hello World!</h1>
<h2>Smaller heading!</h2>
<p>This is a paragraph.</p>
</body>
</html>

嵌套选择器:适用于选择器内部的选择器样式,

  • p{ }: 为所有 p 元素指定一个样式。
  • .marked{ }: 为所有 class=”marked” 的元素指定一个样式。
  • .marked p{ }: 为所有 class=”marked” 元素内的 p 元素指定一个样式。
  • p.marked{ }: 为所有 class=”marked”p 元素指定一个样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
p
{
color:blue;
text-align:center;
}
.marked
{
background-color:red;
}
.marked p
{
color:white;
}
p.marked{
text-decoration:underline;
}
</style>
</head>

<body>
<p>这个段落是蓝色文本,居中对齐。</p>
<div class="marked">
<p>这个段落不是蓝色文本。</p>
</div>
<p>所有 class="marked"元素内的 p 元素指定一个样式,但有不同的文本颜色。</p>

<p class="marked">带下划线的 p 段落。</p>
</body>
</html>

CSS嵌套

CSS显示与定位

display元素设置一个元素应如何显示,visibility属性指定一个元素可见还是隐藏。

visibility:hidden可以隐藏某个元素,但隐藏的元素仍需占用与未隐藏之前一样的空间,虽被隐藏但仍旧影响布局。display:none可以隐藏某个元素,且隐藏的元素不会占用任何空间。

块元素:h1(标题)、p(段落)、div(文档中的块级元素)

内联元素:span(文档中的内联元素)、a(书签)

可以随时改变元素的种类,从而使页面以不同的方式进行组合。

Position属性:static(默认位置)、relative(相对正常位置的相对位置)、fixed(相对浏览器是固定位置,即使窗口滚动它也不会滚动)、absolute(绝对定位的元素相对于已定位的父元素,如果没有已定位的父元素,则其位置相对于html)、sticky(粘性定位:依赖于用户的滚动,在relative与fixed之间切换)。

元素的定位与文档流无关,所以可以覆盖页面上其他元素,z-index属性指定了一个元素的堆叠顺序,实现重叠。

CSS布局

overflow属性用于控制内容溢出元素框时显示的方式,在对应的区间内添加滚动条。

float属性会使元素向左或者向右移动,其周围的元素也会重新排列,往往用于图像或者布局。一个浮动元素会尽量向左或向右移动,直至其外边缘碰到包含框或另一个浮动框的边框。

对齐:1、要水平居中对齐一个元素,可使用margin:auto;并设置到元素的宽度放置它溢出到容器的边缘。2、文本居中对齐,可使用text-align:center;3、图片居中对齐:margin:auto;4、左右对齐:使用定位方式,position:absolute;5、左右对齐:使用float方式;6、垂直居中对齐

CSS组合选择符

说明了两个选择器直接的关系,包含了四种组合方式:后代选择器、子元素选择器、相邻兄弟选择器、普通兄弟选择器

CSS伪类、伪元素

添加一些选择器的特殊效果,伪类的语法:selector:pseudo-class {property:value;};伪元素的语法:selector:pseudo-element {property:value;}

在支持CSS的浏览器中,链接的不同状态可以以不同方式进行显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
a:link {color:#000000;} /* 未访问链接*/
a:visited {color:#00FF00;} /* 已访问链接 */
a:hover {color:#FF00FF;} /* 鼠标移动到链接上 */
a:active {color:#0000FF;} /* 鼠标点击时 */
</style>
</head>
<body>
<p><b><a href="/css/" target="_blank">这是一个链接</a></b></p>
<p><b>注意:</b> a:hover 必须在 a:link 和 a:visited 之后,需要严格按顺序才能看到效果。</p>
<p><b>注意:</b> a:active 必须在 a:hover 之后。</p>
</body>
</html>

伪类可以和CSS类配合使用,以根据链接的被访问与否来判断链接的颜色。

first-child伪类来选择父类的第一个子元素;lang伪类:有能力为不同的语言定义特殊的规则;

first-line伪元素用于向文本的首行设置特殊样式,first-letter伪元素用于向文本首字母设置特殊样式;before伪元素可以在元素内容前面插入新元素;after伪元素可以在元素的内容后面插入新内容;

CSS各类工具

垂直导航栏实例

1、先用ul、ui元素构建一个链接列表;

2、利用CSS格式在列表中删除边距和填充;

3、只用a元素的样式,建立一个垂直的导航栏;

4、在点击了选项后,可以添加active类来标准哪个选项被选中;

5、在li、a上添加text-align:center来让链接居中,并在border ul上添加border属性来让导航栏有边框;

6、创建一个左边是全屏高度的固定导航条,右边是可滚动的内容;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
body {
margin: 0;
}

ul {
list-style-type: none;
margin: 0;
padding: 0;
width: 25%;
background-color: #f1f1f1;
position: fixed;
height: 100%;
overflow: auto;
}

li a {
display: block;
color: #000;
padding: 8px 16px;
text-decoration: none;
}

li a.active {
background-color: #4CAF50;
color: white;
}

li a:hover:not(.active) {
background-color: #555;
color: white;
}
</style>
</head>
<body>

<ul>
<li><a class="active" href="#home">主页</a></li>
<li><a href="#news">新闻</a></li>
<li><a href="#contact">联系</a></li>
<li><a href="#about">关于</a></li>
</ul>

<div style="margin-left:25%;padding:1px 16px;height:1000px;">
<h2>Fixed Full-height Side Nav</h2>
<h3>Try to scroll this area, and see how the sidenav sticks to the page</h3>
<p>Notice that this div element has a left margin of 25%. This is because the side navigation is set to 25% width. If you remove the margin, the sidenav will overlay/sit on top of this div.</p>

<p>Some text..</p>
<p>Some text..</p>
<p>Some text..</p>
<p>Some text..</p>
<p>Some text..</p>
<p>Some text..</p>
<p>Some text..</p>
</div>

</body>
</html>

水平导航栏实例

1、指定元素,使用float浮动元素;

2、创建一个水平导航条实例,并在鼠标移动到选项之后修改背景颜色,点击选项后添加active类来标准哪个选项被选中;

3、将导航条的最右边选项设置设置右对齐,通过border-right样式来添加分割线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
border: 1px solid #e7e7e7;
background-color: #f3f3f3;
}

li {
float: left;
}

li a {
display: block;
color: #666;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}

li a:hover:not(.active) {
background-color: #ddd;
}

li a.active {
color: white;
background-color: #4CAF50;
}
</style>
</head>
<body>

<ul>
<li><a class="active" href="#home">主页</a></li>
<li><a href="#news">新闻</a></li>
<li><a href="#contact">联系</a></li>
<li><a href="#about">关于</a></li>
</ul>

</body>
</html>

下拉菜单实例

使用CSS创建一个鼠标移上去后显示下拉菜单的效果,

HTML部分:我们可以使用任何的HTML元素来打开下拉菜单,如span、button元素;使用容器元素来创建下拉菜单的内容,并放在你相放的位置;使用div元素来包裹这些元素并使用CSS来设置下拉内容的样式。

CSS部分:.dropddown类使用position:relative,将设置下拉菜单的内容放置在下拉按钮(position:absolute)的右下位置。.dropdown-content类中是实际的下拉菜单,默认是隐藏的,在鼠标移动到指定元素后会显示。:hover选择器将用于用户将鼠标移动到下拉按钮上时显示下拉菜单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<style>
/* 下拉按钮样式 */
.dropbtn {
background-color: #4CAF50;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}

/* 容器 <div> - 需要定位下拉内容 */
.dropdown {
position: relative;
display: inline-block;
}

/* 下拉内容 (默认隐藏) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
}

/* 下拉菜单的链接 */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}

/* 鼠标移上去后修改下拉菜单链接颜色 */
.dropdown-content a:hover {background-color: #f1f1f1}

/* 在鼠标移上去后显示下拉菜单 */
.dropdown:hover .dropdown-content {
display: block;
}

/* 当下拉内容显示后修改下拉按钮的背景颜色 */
.dropdown:hover .dropbtn {
background-color: #3e8e41;
}
</style>

<div class="dropdown">
<button class="dropbtn">下拉菜单</button>
<div class="dropdown-content">
<a href="#">菜鸟教程 1</a>
<a href="#">菜鸟教程 2</a>
<a href="#">菜鸟教程 3</a>
</div>
</div>

CSS提示工具实例

1、基础提示框,提示框在鼠标移动到指定元素上显示。

HTML使用容器元素(like div)并添加tooltip类,在鼠标移动到div时显示提示信息;提示文本放在内联元素上(span)并使用class=”tooltiptext”。

CSS中tooltip类使用position:relative,提示文本需要设置定位置position:absolute。tooltiptext类用于实际的提示文本,模式为隐藏的,:hover选择器用于鼠标移动到指定元素div时显示的提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<style>
/* Tooltip 容器 */
.tooltip {
position: relative;
display: inline-block;
border-bottom: 1px dotted black; /* 悬停元素上显示点线 */
}

/* Tooltip 文本 */
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 6px;

/* 定位 */
position: absolute;
z-index: 1;
}

/* 鼠标移动上去后显示提示框 */
.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>

<div class="tooltip">鼠标移动到这
<span class="tooltiptext">提示文本</span>
</div>

2、定位提示工具:通过修改容器元素的top、left、right值来修改提示框显示的位置;如果想要提示框显示在头部和底部,需要使用margin-left属性,并设置为-60px

3、可以使用CSS伪元素::after以及content属性为提示工具创建一个小箭头标志,箭头由边框组成,但组合起来后提示工具像语音提示框。

1
2
3
4
5
6
7
8
9
10
.tooltip .tooltiptext::after {
content: " ";
position: absolute;
top: 100%; /* 提示工具底部 */
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: black transparent transparent transparent;
}

图片廊

1、CSS创建图片廊:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div class="responsive">
<div class="img">
<a target="_blank" href="http://static.runoob.com/images/demo/demo1.jpg">
<img src="http://static.runoob.com/images/demo/demo1.jpg" alt="图片文本描述" width="300" height="200">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="responsive">
<div class="img">
<a target="_blank" href="http://static.runoob.com/images/demo/demo2.jpg">
<img src="http://static.runoob.com/images/demo/demo2.jpg" alt="图片文本描述" width="300" height="200">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="responsive">
<div class="img">
<a target="_blank" href="http://static.runoob.com/images/demo/demo3.jpg">
<img src="http://static.runoob.com/images/demo/demo3.jpg" alt="图片文本描述" width="300" height="200">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="responsive">
<div class="img">
<a target="_blank" href="http://static.runoob.com/images/demo/demo4.jpg">
<img src="http://static.runoob.com/images/demo/demo4.jpg" alt="图片文本描述" width="300" height="200">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

2、使用CSS3的媒体查询来创建响应式图片廊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<div class="responsive">
<div class="img">
<a target="_blank" href="img_fjords.jpg">
<img src="http://www.runoob.com/wp-content/uploads/2016/04/img_fjords.jpg" alt="Trolltunga Norway" width="300" height="200">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>


<div class="responsive">
<div class="img">
<a target="_blank" href="img_forest.jpg">
<img src="http://www.runoob.com/wp-content/uploads/2016/04/img_forest.jpg" alt="Forest" width="600" height="400">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="responsive">
<div class="img">
<a target="_blank" href="img_lights.jpg">
<img src="http://www.runoob.com/wp-content/uploads/2016/04/img_lights.jpg" alt="Northern Lights" width="600" height="400">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="responsive">
<div class="img">
<a target="_blank" href="img_mountains.jpg">
<img src="http://www.runoob.com/wp-content/uploads/2016/04/img_mountains.jpg" alt="Mountains" width="600" height="400">
</a>
<div class="desc">这里添加图片文本描述</div>
</div>
</div>

<div class="clearfix"></div>

<div style="padding:6px;">

<h4>重置浏览器大小查看效果</h4>
</div>

CSS3中属性的透明度是opacity,同时可以利用hover属性增加当用户将鼠标悬停在其中一个图像时会发生什么的时间,此时调为opacity=1

1
2
3
4
5
6
7
8
9
10
img
{
opacity:0.4;
filter:alpha(opacity=40); /* IE8 及其更早版本 */
}
img:hover
{
opacity:1.0;
filter:alpha(opacity=100); /* IE8 及其更早版本 */
}

透明盒子中的文字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
div.background
{
width:500px;
height:250px;
background:url(https://www.runoob.com/images/klematis.jpg) repeat;
border:2px solid black;
}
div.transbox
{
width:400px;
height:180px;
margin:30px 50px;
background-color:#ffffff;
border:1px solid black;
opacity:0.6;
filter:alpha(opacity=60); /* IE8 及更早版本 */
}
div.transbox p
{
margin:30px 40px;
font-weight:bold;
color:#000000;
}
</style>
</head>

<body>

<div class="background">
<div class="transbox">
<p>这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。这些文本在透明框里。
</p>
</div>
</div>

</body>
</html>

图像拼合技术:单个图像的集合,多个图像的网页会降低服务器的请求数量,并节省带宽。与其使用三个独立图像不如使用这种单个图像。

1
2
3
4
5
6
img.home
{
width:46px;
height:44px;
background:url(img_navsprites.gif) 0 0;
}

:hover选择器可以运用于所有元素,因此所有元素都可以显示鼠标悬停在元素上的显示效果。

CSS媒体

允许指定文件如何在不同媒体中实现,而一些CSS属性只设计了某些媒体,而其他的属性可用于许多不同的媒体类型。

@media规则允许在相同样式表为不同媒体设置不同的样式

1
2
3
4
5
6
7
8
9
10
11
12
@media screen
{
p.test {font-family:verdana,sans-serif;font-size:14px;}
}
@media print
{
p.test {font-family:times,serif;font-size:10px;}
}
@media screen,print
{
p.test {font-weight:bold;}
}

CSS属性选择性

具有特定属性的HTML元素样式不仅仅是class和id,下面的实例改变了标题title=’runoob‘元素的边框样式

1
2
3
4
[title=runoob]
{
border:5px solid green;
}

表单样式:属性选择器无需使用class或id的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
input[type="text"]
{
width:150px;
display:block;
margin-bottom:10px;
background-color:yellow;
}
input[type="button"]
{
width:120px;
margin-left:35px;
display:block;
}

可用CSS属性选择器来渲染HTML的表单元素,使用width属性设置输入框的宽度;使用padding属性可以在输入框中添加内边距;使用border属性可以修改input边框得大小或颜色;使用background-color属性设置输入框得背景颜色;

CSS计数器

CSS计数器根据规则来递增变量,以下实例在页面创建一个计数器,且每个h2元素计数值都会递归,并在每个h2元素前添加Section计数值。

1
2
3
4
5
6
7
8
body {
counter-reset: section;
}

h2::before {
counter-increment: section;
content: "Section " counter(section) ": ";
}

嵌套计数器:在每一个h1元素前添加计数值Section,嵌套得计数值则放在h2元素得前面,内容为 主标题计数值、副标题计数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
body {
counter-reset: section;
}

h1 {
counter-reset: subsection;
}

h1::before {
counter-increment: section;
content: "Section " counter(section) ". ";
}

h2::before {
counter-increment: subsection;
content: counter(section) "." counter(subsection) " ";
}

2、CSS网页布局

网页布局一般分为以下几个部分:头部区域、菜单导航区域、内容区域、底部区域。

1、头部区域一般位于整个网页得顶部,用于设置网页得标题或LOGO;

2、菜单导航区域包含了一些链接,引导用户浏览其他页面;

3、内容区域一般有三种形式,1列用于移动端;2列用于平板设备;3列用于PC桌面设备。

4、底部区域在网页的最下方,一般包含版权信息与联系方式;

通过以上的方式,我们创建了一个响应式等页面,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
* {
box-sizing: border-box;
}

body {
font-family: Arial;
padding: 10px;
background: #f1f1f1;
}

/* 头部标题 */
.header {
padding: 30px;
text-align: center;
background: white;
}

.header h1 {
font-size: 50px;
}

/* 导航条 */
.topnav {
overflow: hidden;
background-color: #333;
}

/* 导航条链接 */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}

/* 链接颜色修改 */
.topnav a:hover {
background-color: #ddd;
color: black;
}

/* 创建两列 */
/* Left column */
.leftcolumn {
float: left;
width: 75%;
}

/* 右侧栏 */
.rightcolumn {
float: left;
width: 25%;
background-color: #f1f1f1;
padding-left: 20px;
}

/* 图像部分 */
.fakeimg {
background-color: #aaa;
width: 100%;
padding: 20px;
}

/* 文章卡片效果 */
.card {
background-color: white;
padding: 20px;
margin-top: 20px;
}

/* 列后面清除浮动 */
.row:after {
content: "";
display: table;
clear: both;
}

/* 底部 */
.footer {
padding: 20px;
text-align: center;
background: #ddd;
margin-top: 20px;
}

/* 响应式布局 - 屏幕尺寸小于 800px 时,两列布局改为上下布局 */
@media screen and (max-width: 800px) {
.leftcolumn, .rightcolumn {
width: 100%;
padding: 0;
}
}

/* 响应式布局 -屏幕尺寸小于 400px 时,导航等布局改为上下布局 */
@media screen and (max-width: 400px) {
.topnav a {
float: none;
width: 100%;
}
}

3、CSS总结

已经实现了,如何创建样式表来同时控制多重页面的样式和布局,比如如何定位元素、控制元素的可见性和尺寸、设置元素的形状、将一个元素置于另一个元素之后,以及向某些选择器添加特殊的效果,比如;链接。

CSS实例类型索引:runoob.com/css/css-examples.html。

4、CSS3教程

CSS3用于控制网页样式和布局,CSS3被拆分为“模块”,旧规范已拆分为小块,还增加了新的。最重要的CSS3模块如下:选择器、盒模型、背景和边框、文字特效、2D3D转换、动画、多列布局、用户界面。

CSS格式背景

边框:在CSS3中可添加圆角边框,添加阴影框,并作为边界形象而不使用设计程序。border-radius属性用于创建圆角;box-shadow属性用来添加阴影;border-image属性用于创建边框,允许你指定一个图片作为边框,用于创建上文边框的原始图像。

背景:包含新背景属性,提供更大背景元素控制。background-image添加背景图片;background-size指定背景图像大小;background-origin指定背景图像的位置区域;且CSS3允许在元素上添加多个背景图像。background-clip背景裁剪属性是从指定位置开始绘制。

渐变:可在两个或多个指定颜色之间显示平稳的过渡,linear gradients线性渐变:上、下、左、右、对角,radial gradients由中心定义。同样也可以定义一个角度而不用预定义方向:background-image: linear-gradient(angle, color-stop1, color-stop2);同样也可以使用多个颜色节点的定义、支持透明度以创建减弱变淡效果transparent,repeating-linear-gradient()函数用于重复线性渐变。

文本效果:text-shadow文本阴影、box-shadow盒子阴影、也可以在::before和::after两个伪元素中添加阴影效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
#boxshadow {
position: relative;
-moz-box-shadow: 1px 2px 4px rgba(0, 0, 0,0.5);
-webkit-box-shadow: 1px 2px 4px rgba(0, 0, 0, .5);
box-shadow: 1px 2px 4px rgba(0, 0, 0, .5);
padding: 10px;
background: white;
}

/* Make the image fit the box */
#boxshadow img {
width: 100%;
border: 1px solid #8a4419;
border-style: inset;
}

#boxshadow::after {
content: '';
position: absolute;
z-index: -1; /* hide shadow behind image */
-webkit-box-shadow: 0 15px 20px rgba(0, 0, 0, 0.3);
-moz-box-shadow: 0 15px 20px rgba(0, 0, 0, 0.3);
box-shadow: 0 15px 20px rgba(0, 0, 0, 0.3);
width: 70%;
left: 15%; /* one half of the remaining 30% */
height: 100px;
bottom: 0;
}
</style>
</head>
<body>

<div id="boxshadow">
<img src="rock600x400.jpg" alt="Norway" width="600" height="400">
</div>

</body>
</html>

阴影使用的一个特例是卡片效果,

CSS文本溢出属性Text Overflow指定应向用户如何显示溢出内容;自动换行属性word-wrap允许强制文本换行,即使分裂中间一个字。

CSS3单词拆分换行属性指定换行规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
p.test1
{
width:9em;
border:1px solid #000000;
word-break:keep-all;
}

p.test2
{
width:9em;
border:1px solid #000000;
word-break:break-all;
}
</style>
</head>
<body>

<p class="test1"> This paragraph contains some text. This line will-break-at-hyphenates.</p>
<p class="test2"> This paragraph contains some text: The lines will break at any character.</p>

<p><b>注意:</b> word-break 属性不兼容 Opera.</p>

</body>
</html>

CSS3字体:自己的字体是在@font-face规则中描述定义的,必须首先定义字体的名称然后指向该字体文字文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
@font-face
{
font-family: myFirstFont;
src: url('Sansation_Light.ttf')
,url('Sansation_Light.eot'); /* IE9 */
}

div
{
font-family:myFirstFont;
}
</style>
</head>
<body>

<p><b>注意:</b> Internet Explorer 9 只支持 .eot 格式的字体.</p>

<div>
使用 CSS3,网站终于可以使用字体以外的预先选择“合法”字体
</div>

</body>
</html>

CSS32D、3D转换

CSS3转换可以对元素进行移动、缩放、转动、拉长或拉伸,转换的效果是让某个元素改变形状、大小和位置。

2D变换:

translate():根据X、Y轴位置给定的参数,从当前元素位置移动;rotate():在一个给定度数顺时针旋转的元素,参数为负值则为逆时针;scale():增减元素的大小;skew()方法:包含两个参数值,分别表示X轴和Y轴倾斜的角度;matrix()方法和2D变换方法合并成一个,有6个参数包含旋转、缩放、移动、倾斜功能;

3D变换:能够将图片视为一个3D的小纸片进行翻转、变换运动,而不只是旋转与改变大小。

rotateX()方法:围绕其在一个给定度数X轴旋转的元素;

rotateY()方法:围绕其在一个给定度数Y轴旋转的元素;

5、CSS3动画

CSS3过渡

元素从一种样式逐渐改变成另一种的效果,指定添加效果的CSS属性、指定效果的持续时间。要添加多个样式的变换效果,添加的属性由逗号分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
div
{
width:100px;
height:100px;
background:red;
transition:width 2s;
-webkit-transition:width 2s; /* Safari */
}

div:hover
{
width:300px;
}
</style>
</head>
<body>

<p><b>注意:</b>该实例无法在 Internet Explorer 9 及更早 IE 版本上工作。</p>

<div></div>

<p>鼠标移动到 div 元素上,查看过渡效果。</p>

</body>
</html>

CSS3创建动画

@keyframes规则:创建动画,其内指定一个CSS样式和动画将逐步从目前的样式更改为新的样式;

当在@keyframes创建动画,把它绑定到一个选择器,否则动画不会有任何效果,指定这2个CSS3的动画属性绑定向一个选择器:规定动画名称与时长。下例将myfirst动画捆绑到div元素,时长为5秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
div
{
width:100px;
height:100px;
background:red;
animation:myfirst 5s;
-webkit-animation:myfirst 5s; /* Safari and Chrome */
}

@keyframes myfirst
{
from {background:red;}
to {background:yellow;}
}

@-webkit-keyframes myfirst /* Safari and Chrome */
{
from {background:red;}
to {background:yellow;}
}
</style>
</head>
<body>

<p><b>注意:</b> 该实例在 Internet Explorer 9 及更早 IE 版本是无效的。</p>

<div></div>

</body>
</html>

动画的实质是元素从一种样式逐渐变化为另一种样式的效果,可以改变任意多的样式、次数,并用百分比来规定变化发生的事件。以下可以改变元素背景色与位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
div
{
width:100px;
height:100px;
background:red;
position:relative;
animation:myfirst 5s;
-webkit-animation:myfirst 5s; /* Safari and Chrome */
}

@keyframes myfirst
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}

@-webkit-keyframes myfirst /* Safari and Chrome */
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}
</style>
</head>
<body>

<p><b>注意:</b> 该实例在 Internet Explorer 9 及更早 IE 版本是无效的。</p>

<div></div>

</body>
</html>

使用下例设置所有的属性,并使用了简写的动画animation属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
div
{
width:100px;
height:100px;
background:red;
position:relative;
animation:myfirst 5s linear 2s infinite alternate;
/* Firefox: */
-moz-animation:myfirst 5s linear 2s infinite alternate;
/* Safari and Chrome: */
-webkit-animation:myfirst 5s linear 2s infinite alternate;
/* Opera: */
-o-animation:myfirst 5s linear 2s infinite alternate;
}

@keyframes myfirst
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}

@-moz-keyframes myfirst /* Firefox */
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}

@-webkit-keyframes myfirst /* Safari and Chrome */
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}

@-o-keyframes myfirst /* Opera */
{
0% {background:red; left:0px; top:0px;}
25% {background:yellow; left:200px; top:0px;}
50% {background:blue; left:200px; top:200px;}
75% {background:green; left:0px; top:200px;}
100% {background:red; left:0px; top:0px;}
}
</style>
</head>
<body>

<p><b>注意:</b> 该实例在 Internet Explorer 9 及更早 IE 版本是无效的。</p>

<div></div>

</body>
</html>

CSS用户界面与布局

1、多列布局

CSS3可将文本内容设计成像报纸一样的多列布局;column-count:指定需要分割的列数,column-gap:指定列与列之间间隙;column-rule-style:指定列与列间边框样式;

2、resize

指定一个元素是否应该由用户去调整大小;box-sizing属性:允许以确切的方式适应某区域的具体内容;outline-offset属性对轮廓进行偏移,并在超出边缘的位置绘制轮廓。

3、CSS3来布局图片

:border-radius来设置圆角、椭圆形图片;border属性来创建缩略图,在图片外层添加一个链接;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
a {
display: inline-block;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px;
transition: 0.3s;
}

a:hover {
box-shadow: 0 0 2px 1px rgba(0, 140, 186, 0.5);
}
</style>
</head>
<body>

<h2>缩略图作为连接</h2>
<p>我们使用 border 属性来创建缩略图。在图片外层添加一个链接。</p>
<p>点击图片查看效果:</p>

<a target="_blank" href="paris.jpg">
<img src="paris.jpg" alt="Paris" width="400" height="300">
</a>

</body>
</html>

响应式图片会自动适应不同尺寸屏幕,

4、卡片式图片

并在图片下方添加图片描述文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
body {margin:25px;}

div.polaroid {
width: 80%;
background-color: white;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
margin-bottom: 25px;
}

div.container {
text-align: center;
padding: 10px 20px;
}
</style>
</head>
<body>

<h2>响应式卡片</h2>

<div class="polaroid">
<img src="rock600x400.jpg" alt="Norway" style="width:100%">
<div class="container">
<p>The Troll's tongue in Hardanger, Norway</p>
</div>
</div>

<div class="polaroid">
<img src="lights600x400.jpg" alt="Norway" style="width:100%">
<div class="container">
<p>Northern Lights in Norway</p>
</div>
</div>

</body>
</html>

CSS filter属性用于为元素添加可视效果,模糊、饱和度。

如何结合CSS和JavaScript来一起渲染图片:首先用CSS来创建modal窗口,默认为隐藏,然后使用JavaScript来显示模态窗口,当点击图片时,图片会在弹出的窗口中显示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
#myImg {
border-radius: 5px;
cursor: pointer;
transition: 0.3s;
}

#myImg:hover {opacity: 0.7;}

/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.9); /* Black w/ opacity */
}

/* Modal Content (image) */
.modal-content {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
}

/* Caption of Modal Image */
#caption {
margin: auto;
display: block;
width: 80%;
max-width: 700px;
text-align: center;
color: #ccc;
padding: 10px 0;
height: 150px;
}

/* Add Animation */
.modal-content, #caption {
-webkit-animation-name: zoom;
-webkit-animation-duration: 0.6s;
animation-name: zoom;
animation-duration: 0.6s;
}

@-webkit-keyframes zoom {
from {-webkit-transform: scale(0)}
to {-webkit-transform: scale(1)}
}

@keyframes zoom {
from {transform: scale(0.1)}
to {transform: scale(1)}
}

/* The Close Button */
.close {
position: absolute;
top: 15px;
right: 35px;
color: #f1f1f1;
font-size: 40px;
font-weight: bold;
transition: 0.3s;
}

.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}

/* 100% Image Width on Smaller Screens */
@media only screen and (max-width: 700px){
.modal-content {
width: 100%;
}
}
</style>
</head>
<body>

<h2>图片模态框</h2>
<p>本实例演示了如何结合 CSS 和 JavaScript 来一起渲染图片。</p><p>
首先,我们使用 CSS 来创建 modal 窗口 (对话框), 默认是隐藏的。<p>
<p>然后,我们使用 JavaScript 来显示模态窗口,当我们点击图片时,图片会在弹出的窗口中显示:</p>
<img id="myImg" src="//www.runoob.com/wp-content/uploads/2016/04/img_lights.jpg" alt="Northern Lights, Norway" width="300" height="200">

<!-- The Modal -->
<div id="myModal" class="modal">
<span class="close">×</span>
<img class="modal-content" id="img01">
<div id="caption"></div>
</div>

<script>
// 获取模态窗口
var modal = document.getElementById('myModal');

// 获取图片模态框,alt 属性作为图片弹出中文本描述
var img = document.getElementById('myImg');
var modalImg = document.getElementById("img01");
var captionText = document.getElementById("caption");
img.onclick = function(){
modal.style.display = "block";
modalImg.src = this.src;
modalImg.alt = this.alt;
captionText.innerHTML = this.alt;
}

// 获取 <span> 元素,设置关闭模态框按钮
var span = document.getElementsByClassName("close")[0];

// 点击 <span> 元素上的 (x), 关闭模态框
span.onclick = function() {
modal.style.display = "none";
}
</script>

</body>
</html>

5、CSS3按钮

back-ground属性设置颜色;font-size属性设置大小;border-radius属性设置圆角按钮;border属性设置边框颜色;:hover选择器来修改鼠标悬停在按钮上的样式,并用transition-duration属性来设置hover效果的速度;box-shadow属性为按钮添加阴影;opacity属性添加透明度,看上去类似禁用的效果。

按钮动画:1、鼠标移动时添加箭头标记;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
.button {
display: inline-block;
border-radius: 4px;
background-color: #f4511e;
border: none;
color: #FFFFFF;
text-align: center;
font-size: 28px;
padding: 20px;
width: 200px;
transition: all 0.5s;
cursor: pointer;
margin: 5px;
}

.button span {
cursor: pointer;
display: inline-block;
position: relative;
transition: 0.5s;
}

.button span:after {
content: '»';
position: absolute;
opacity: 0;
top: 0;
right: -20px;
transition: 0.5s;
}

.button:hover span {
padding-right: 25px;
}

.button:hover span:after {
opacity: 1;
right: 0;
}
</style>
</head>
<body>

<h2>按钮动画</h2>

<button class="button" style="vertical-align:middle"><span>Hover </span></button>

</body>
</html>

2、点击时添加压下效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
.button {
display: inline-block;
padding: 15px 25px;
font-size: 24px;
cursor: pointer;
text-align: center;
text-decoration: none;
outline: none;
color: #fff;
background-color: #4CAF50;
border: none;
border-radius: 15px;
box-shadow: 0 9px #999;
}

.button:hover {background-color: #3e8e41}

.button:active {
background-color: #3e8e41;
box-shadow: 0 5px #666;
transform: translateY(4px);
}
</style>
</head>
<body>

<h2>按钮动画 - "按压效果"</h2>

<button class="button">Click Me</button>

</body>
</html>

6、CSS分页

当网站有很多个页面时,需要使用分页来为每个页面做导航,以下实例演示如何用HTML和CSS来创建分页。

1、用.active来设置当前页样式,且鼠标悬停可用:hover选择器来修改样式;

2、用border-radius来为选中的页码添加圆角样式,添加transition属性添加鼠标移动到页码上时的过渡效果,border属性来添加带边框分页;

3、margin属性为每个页码间添加空格,font-size属性设置分页的字体大小,在容器元素上添加text-align:center样式实现分页居中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
ul.pagination {
display: inline-block;
padding: 0;
margin: 0;
}

ul.pagination li {display: inline;}

ul.pagination li a {
color: black;
float: left;
padding: 8px 16px;
text-decoration: none;
transition: background-color .3s;
border: 1px solid #ddd;
}

ul.pagination li a.active {
background-color: #4CAF50;
color: white;
border: 1px solid #4CAF50;
}

ul.pagination li a:hover:not(.active) {background-color: #ddd;}

div.center {text-align: center;}
</style>
</head>
<body>

<h2>分页居中</h2>

<div class="center">
<ul class="pagination">
<li><a href="#">«</a></li>
<li><a href="#">1</a></li>
<li><a class="active" href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">6</a></li>
<li><a href="#">7</a></li>
<li><a href="#">»</a></li>
</ul>
</div>

</body>
</html>

7、CSS框大小设置

CSS3中box-sizing属性可以设置width和height中,包含了padding(内边距)和border(边框);

1、不使用box-sizing属性时,高 = height + border + padding;宽 = width + padding + border。高度、宽度设置一样时,真实展示大小不一定一样,因为指定的padding不同。

2、使用box-sizing时,该属性同时包括内边框与边框,其实这样效果更好,在元素上添加box-sizing:border-box的简单实例。

8、CSS3弹性盒子

Flex Box是一种新的布局模式,当页面需要适应不同的屏幕大小以及设备类型时,确保元素拥有恰当的行为的布局方式,提供flexbox的目的是提供一种更加有效的方式来对一个容器中的子元素进行排列、对齐和分配空白空间。

由弹性容器container和弹性子元素item组成,弹性容器通过设置display值为flex,将其定义为弹性容器。弹性容器外及弹性子元素内是正常渲染的。弹性盒子只定义了弹性子元素如何在弹性容器内布局。

弹性子元素通常在弹性盒子内一行显示。默认情况每个容器只有一行。以下元素展示了弹性子元素在一行内显示,从左到右:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
.flex-container {
display: -webkit-flex;
display: flex;
width: 400px;
height: 250px;
background-color: lightgrey;
}

.flex-item {
background-color: cornflowerblue;
width: 100px;
height: 100px;
margin: 10px;
}
</style>
</head>
<body>

<div class="flex-container">
<div class="flex-item">flex item 1</div>
<div class="flex-item">flex item 2</div>
<div class="flex-item">flex item 3</div>
</div>

</body>
</html>

justify-content内容对齐属性应用在弹性容器上,把弹性项沿着容器的主轴线对齐;justify-content: flex-start | flex-end | center | space-between | space-around

align-items 设置或检索弹性盒子元素在侧轴(纵轴)方向上的对齐方式。align-items: flex-start | flex-end | center | baseline | stretch

flex-wrap 属性用于指定弹性盒子的子元素换行方式;flex-wrap: nowrap|wrap|wrap-reverse|initial|inherit;

align-content 属性用于修改 flex-wrap 属性的行为。类似于 align-items, 但它不是设置弹性子元素的对齐,而是设置各个行的对齐。align-content: flex-start | flex-end | center | space-between | space-around | stretch

弹性子元素的属性:

1、排序:order;用整数值来定义排列顺序,数值小的排在前面。可以为负值

2、对齐:margin;设置”margin”值为”auto”值,自动获取弹性容器中剩余的空间。所以设置垂直方向margin值为”auto”,可以使弹性子元素在弹性容器的两上轴方向都完全居中。

3、align-self 属性用于设置弹性元素自身在侧轴(纵轴)方向上的对齐方式。align-self: auto | flex-start | flex-end | center | baseline | stretch

4、flex 属性用于指定弹性子元素如何分配空间。flex: auto | initial | none | inherit | [ flex-grow ] || [ flex-shrink ] || [ flex-basis ]

9、CSS3多媒体查询

@media:CSS3的多媒体查询继承了CSS2中所有思想:取代了查找设备的类型,根据设置自适应显示。

多媒体查询由多种媒体组成,可以包含一个或多个表达式,表达式根据条件是否成立返回 true 或 false。

@media not|only mediatype and (expressions) { CSS 代码…; }

如果指定的多媒体类型匹配设备类型则查询结果返回 true,文档会在匹配的设备上显示指定样式效果。除非你使用了 not 或 only 操作符,否则所有的样式会适应在所有设备上显示效果。

not是用来排除掉某些特定的设备的,比如 @media not print(非打印设备)。

only: 用来定某种特别的媒体类型。对于支持Media Queries的移动设备来说,如果存在only关键字,移动设备的Web浏览器会忽略only关键字并直接根据后面的表达式应用样式文件。对于不支持Media Queries的设备但能够读取Media Type类型的Web浏览器,遇到only关键字时会忽略这个样式文件。

all:所有设备,这个应该经常看到。

以下实例在屏幕可视窗口尺寸小于 600 像素时将 div 元素隐藏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
div.example {
background-color: yellow;
padding: 20px;
}

@media screen and (max-width: 600px) {
div.example {
display: none;
}
}
</style>
</head>
<body>

<h2>屏幕可视尺寸小于 600 px 时,隐藏以下元素。</h2>

<div class="example">我是会隐藏的元素。</div>

<p>重置浏览器大小,查看效果。</p>

</body>
</html>

实例:制作一个电子邮箱的链接列表,注意 data-email 属性。在 HTML 中我们可以使用带 data- 前缀的属性来存储信息。当浏览器的宽度在 520 到 699px, 邮箱链接前添加邮件图标;当浏览器的宽度在 700 到 1000px, 会在邮箱链接前添加 “Email”;当浏览器的宽度大于 1001px 时,会在链接后添加邮件地址。我们会使用 data- 属性来为每个人名后添加邮件地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style>
ul {
list-style-type: none;
}

ul li a {
color: green;
text-decoration: none;
padding: 3px;
display: block;
}

@media screen and (max-width: 699px) and (min-width: 520px), (min-width: 1151px) {
ul li a {
padding-left: 30px;
background: url(email-icon.png) left center no-repeat;
}
}

@media screen and (max-width: 1000px) and (min-width: 700px) {
ul li a:before {
content: "Email: ";
font-style: italic;
color: #666666;
}
}

@media screen and (min-width: 1001px) {
ul li a:after {
content: " (" attr(data-email) ")";
font-size: 12px;
font-style: italic;
color: #666666;
}
}
</style>
</head>
<body>

<h1>重置浏览器窗口,查看效果!</h1>

<ul>
<li><a data-email="johndoe@example.com" href="mailto:johndoe@example.com">John Doe</a></li>
<li><a data-email="marymoe@example.com" href="mailto:marymoe@example.com">Mary Moe</a></li>
<li><a data-email="amandapanda@example.com" href="mailto:amandapanda@example.com">Amanda Panda</a></li>
</ul>

</body>
</html>

使用堆的数据结构来对数组进行排序,找出第k大的数据,时间复杂度为O(n)

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆是非线性数据结构,相当于一维数组,有两个直接后继。

1、基础知识

可视化的html页面结构中,只有< body >区域才会在浏览器中显示。

目前在大部分浏览器中,直接输出中文会出现中文乱码的情况,需要在头部将字符声明为UTF-8或GBK。

1
<meta charset="UTF-8">

html元素语法:

1、HTML元素以开始标签起始,以结束标签终止;2、元素的内容是开始标签与结束标签之间的内容;3、某些HTML元素具有空内容,空内容在开始标签中进行关闭,以开始标签的结束而结束;4、大多数HTML可拥有属性

嵌套的HTML元素

大多数HTML元素可以嵌套,HTML元素可包含其他HTML元素,且HTML文档由相互嵌套的HTML元素组成。

通常不要忘了用结束标签,虽然可以正确显示,但忘记使用结束标签往往会产生不可预料的结果或错误。

HTML标签对大小写不敏感,请一般使用小写标签。

HTML属性

元素可设置属性;属性可以在元素中添加附加信息;属性一般描述于开始标签;属性总是以名称、值对的形式出现。比如:name=”value”。

HTML链接由< a >标签定义,链接地址在href属性中指定。

1
<a href="http://www.runoob.com">这是一个链接</a>

HTML格式

< hr >标签在HTML页面中创建水平线,用于分隔内容;

HTML注释:在开始括号之后紧跟一个叹号,结束括号前不需要

1
<!-- 这是一个注释 -->

< br >使用br标签:在不产生一个新段落的情况下进行换行;

< p >定义一个段落

HTML使用标签< b >与< i >对输出的文本进行格式

1
2
3
4
<b>加粗文本</b><br><br>
<i>斜体文本</i><br><br>
<code>电脑自动输出</code><br><br>
这是 <sub> 下标</sub><sup> 上标</sup>

HTML链接由< a >标签定义,链接地址在href属性中指定。

target属性:定义被链接的文档在何处显示;id属性:创建一个在HTML文档书签的标记;

1
<a href="http://www.runoob.com">这是一个链接</a>

HTML头部

< head >元素包含了所有的头部标签元素,可以插入脚本、CSS(样式文件)以及各种meta信息

< title > 定义不同文档的标题;

< base > 描述了基本的链接地址、链接目标,作为默认链接;

< link > 定义了文档与外部资源之间的关系,用于链接到样式表;

< style >元素:定义了HTML文档的样式文件引用地址,在该元素中直接添加样式来渲染HTML;

< meta >元素:描述了一些基本的元数据,通常用于指定网页的描述,关键词,文件最后的修改时间,作者和其他元数据。

< script > 用于加载脚本文件,如javascript

HTML样式 CSS

用于渲染HTML元素标签的样式,三种方式添加到HTML中:1、内联样式:在HTML元素中使用style属性;2、内部样式表:在HTML文档头部使用< style > 来包含CSS;3、外部引用:使用外部CSS文件。

最好的方式是外部引用;当特殊的样式需要应用到个别元素时,就可以使用内联样式,在相关标签中使用样式属性。

1
2
<h1 style="font-family:verdana;">一个标题</h1>
<p style="font-family:arial;color:red;font-size:20px;">一个段落。</p>

内部样式表:当单个文件需要特别样式时,在< head >部分通过< style >标签定义。

1
2
3
4
5
6
<head>
<style type="text/css">
body {background-color:yellow;}
p {color:blue;}
</style>
</head>

当样式被应用到很多页面时,外部样式表是理想选择,使用外部样式表,可以通过更改一个文件来修改一整个站点的外观。

1
2
3
<head>
<link rel="stylesheet" type="text/css" href="mystyle.css">
</head>

HTML图像

图像由< img > 标签定义,为空标签,只有属性而无闭合标签,可使用源属性src,其值是图像的URL地址。alt属性用来为图像定义一串预备的可替换文本,在浏览器无法载入图像时,替换文本告诉读者他们失去的信息。

height、width属性用于设置图像的高度与宽度;

HTML表格

表格由< table >标签来定义,每个表格有若干行(由< tr >标签定义),每行被分割为若干单元格(由< td >标签定义),td指表格数据即数据单元格的内容,border属性来定义表格的边框。

1
2
3
4
5
6
7
8
9
10
<table border="1">
<tr>
<td>row 1, cell 1</td>
<td>row 1, cell 2</td>
</tr>
<tr>
<td>row 2, cell 1</td>
<td>row 2, cell 2</td>
</tr>
</table>

HTML列表

无序列表用< ul >标签

有序列表始于< ol >标签,每个列表项始于< li >标签,且列表各项会自动使用数字来标记。

自定义列表:项目及其注释的组合;以< dl >标签开始,每个自定义列表项从< dt >开始,每个自定义列表的定义以 < dd >开始。

HTML区块

可通过 < div >和< span >将元素组合起来

大多数HTML元素被定义为块级元素、内联元素;块级元素,通常以新行开始和结束,内联元素:显示时不会以新行开始;

< div >元素是块级元素,可用于组合其他元素的容器,无特定含义,与CSS一同使用时,可用于对大的内容块设置样式属性;< div >元素的另一个常见作用是文档布局,用table显示表格化数据,用div进行表格定义布局。

< span >元素是内联元素,可用作文本的容器

HTML布局

使用div、table元素来创建多列,CSS用于元素定位或为页面创建背景以及色彩丰富的外观。

使用CSS最大的好处是,如果将带啊存放到外部样式表中,那么站点更易于维护,通过编辑单一的文件就可以改变所有页面的布局。

HTML表单

用于收集不同类型的用户输入,表单是一个包含表单元素的区域,表单元素允许用户在表单中输入内容,表单标签用< form >来设置。

输入元素input,输入类型由类型属性type定义。text:文本域;password:密码字段;radio:单选按钮;checkbox:复选框;submit:提交按钮

HTML框架、

通过使用框架可以在同一个浏览器窗口显示不止一个页面;

iframe语法:< iframe src=”URL” >< /iframe >

使用height、width来设置高宽,frameborder属性用于定义iframe表示是否显示边框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>

<iframe src="demo_iframe.htm" name="iframe_a"></iframe>
<p><a href="//www.runoob.com" target="iframe_a">RUNOOB.COM</a></p>

<p><b>注意:</b> 因为 a 标签的 target 属性是名为 iframe_a 的 iframe 框架,所以在点击链接时页面会显示在 iframe框架中。</p>

</body>
</html>

因为 a 标签的 target 属性是名为 iframe_a 的 iframe 框架,所以在点击链接时页面会显示在 iframe框架中

HTML颜色

由16进制符号来定义,RGB三向通道,FF FF FF

141个颜色名称是在HTML和CSS颜色规范定义的,17中表针颜色,124种非标准。

HTML脚本

script标签用于定义客户端脚本,比如JavaScript,script元素即可包含脚本语句,也可以通过src属性指向外部脚本文件。JavaScript最常用与图片操作、表单验证以及内容·动态更新。

noscript提供无法使用脚本时的替代内容,可包含普通HTML页面的body元素中能够找到的所有元素。

HTML字符实体

其中,某些字符是预留的,比如> 、<;因此需要在源代码中使用字符实体;常用字符实体是不间断空格&nbsp,由于浏览器总会截断HTML页面的空格,只留下一个,因此如果需要在页面中增加空格数量,需要用到&nbsp。

字符实体的名称对大小写敏感。

HTML URL

URL是一个网页地址,Web浏览器通过URL从Web服务器请求页面,当点击页面上链接时,对应标签指向万维网上地址。

一个网页地址实例: http://www.runoob.com/html/html-tutorial.html 语法规则:

scheme://host.domain:port/path/filename

说明:

    • scheme - 定义因特网服务的类型。最常见的类型是 http
    • host - 定义域主机(http 的默认主机是 www)
    • domain - 定义因特网域名,比如 runoob.com
    • :port - 定义主机上的端口号(http 的默认端口号是 80)
    • path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。
    • filename - 定义文档/资源的名称

http:超文本传输协议;https:安全超文本传输协议;ftp:文件传输协议;file:自己计算机上文件。

URL 只能使用 ASCII 字符集.

来通过因特网进行发送。由于 URL 常常会包含 ASCII 集合之外的字符,URL 必须转换为有效的 ASCII 格式。

URL 编码使用 “%” 其后跟随两位的十六进制数来替换非 ASCII 字符。

URL 不能包含空格。URL 编码通常使用 + 来替换空格。

HTML速查列表

速查列表1

速查列表1

速查列表3速查列表4速查列表5速查列表6

2、H5前端开发

为了更好地处理今天的互联网应用,HTML5添加了很多新元素及功能,比如: 图形的绘制,多媒体内容,更好的页面结构,更好的形式 处理,和几个api拖放元素,定位,包括网页 应用程序缓存,存储,网络工作者,等。

HTML5 Canvas

canvas标签定义图形,只是图形的容器,必须使用脚本来绘制图形。

一个画布在网页中是一个矩形框,通过canvas元素来绘制,指定id属性,height、width属性来定义画布大小,使用style属性来添加边框。

H5内联SVG

SVG指可伸缩矢量图形,用于定义用于网络的基于矢量的图形;使用XML格式定义图形,SVG图像在放大或改变尺寸的情况下其图形质量不会有损失,SVG是万维网联盟的标准。

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html>
<body>

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="190">
<polygon points="100,10 40,180 190,60 10,60 160,180"
style="fill:lime;stroke:purple;stroke-width:5;fill-rule:evenodd;">
</svg>

</body>
</html>

SVG是一种使用XML描述2D图形的语言,而Canvas通过JavaScript绘制2D图形。

SVG基于XML,因此SVG DOM中的每个元素都是可用的,可以为某个元素附加JavaScript事件处理器,在SVG中,每个被绘制的图形均被视为对象,若SVG对象属性发生变化,那么浏览器能自动重现图形;

Canvas是逐像素进行渲染,一旦图形被绘制完成,便不会得到浏览器的关注,如果其位置发生变化,则整个场景需要进行重新绘制。SVG与Canvas

HTML5 MathML

标签math,MathML是数学标记语言,基于XML的标准,用于互联网上书写数学符号与公式。

HTML5拖放drag和drop

拖放是一种常见的特性,即抓取对象以后拖到另一个位置,在H5中任何元素都可以拖放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
<style type="text/css">
#div1 {width:350px;height:70px;padding:10px;border:1px solid #aaaaaa;}
</style>
<script>
function allowDrop(ev)
{
ev.preventDefault();
}

function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}

function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
</script>
</head>
<body>

<p>拖动 RUNOOB.COM 图片到矩形框中:</p>

<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>
<br>
<img id="drag1" src="/images/logo.png" draggable="true" ondragstart="drag(event)" width="336" height="69">

</body>
</html>

1、设置元素可拖放:把draggable属性设置为true;

2、拖动什么:ondragstart属性调用了一个函数drag(event),并在函数中用dataTransfer.setData()方法设置被拖数据的数据类型和值;

3、放到何处:ondragover事件规定在何处放置被拖动的数据,默认中无法将数据、元素放置到其他元素中,通过调用ondragover事件的event.preventDefault()方法

4、进行放置:当放置被拖数据时,会发生drop事件,在上面的例子中ondrop属性调用了一个函数,drop(event)

1
2
3
4
5
6
function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}
  • 调用 preventDefault() 来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)
  • 通过 dataTransfer.getData(“Text”) 方法获得被拖的数据。该方法将返回在 setData() 方法中设置为相同类型的任何数据。
  • 被拖数据是被拖元素的 id (“drag1”)
  • 把被拖元素追加到放置元素(目标元素)中

H5地理定位

H5视频

H5规定了一种通过video元素来包含视频的标准方法,

1
2
3
4
5
<video width="320" height="240" controls>
<source src="movie.mp4" type="video/mp4">
<source src="movie.ogg" type="video/ogg">
您的浏览器不支持Video标签。
</video>

video与audio元素同样拥有方法、属性、事件,且均可以使用JavaScript进行控制。方法:用于播放、暂停、加载;属性:时长、音量可以被读取或设置;其中DOM事件可以通知用户。下面的例子调用了play()和pause()方法,且使用了paused、width属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html> 
<html>
<head> 
<meta charset="utf-8"> 
<title>菜鸟教程(runoob.com)</title> 
</head>
<body>

<div style="text-align:center">
<button onclick="playPause()">播放/暂停</button>
<button onclick="makeBig()">放大</button>
<button onclick="makeSmall()">缩小</button>
<button onclick="makeNormal()">普通</button>
<br>
<video id="video1" width="420">
<source src="mov_bbb.mp4" type="video/mp4">
<source src="mov_bbb.ogg" type="video/ogg">
您的浏览器不支持 HTML5 video 标签。
</video>
</div>

<script>
var myVideo=document.getElementById("video1");

function playPause()
{
if (myVideo.paused)
myVideo.play();
else
myVideo.pause();
}

function makeBig()
{
myVideo.width=560;
}

function makeSmall()
{
myVideo.width=320;
}

function makeNormal()
{
myVideo.width=420;
}
</script>

</body>
</html>

H5新的Input类型

color、data(从日期选择器中选择时间)、datetime、datetime-local、email、month、number、range(用于应该包含一定范围内数字值的输入域)、search(定义一个搜索字段)、tel、time、url(自动验证url域的值)、week、

H5新的表单元素

datalist:规定输入域的选项列表,该属性规定form、input域应该拥有自动完成功能,当用户在自动完成域中开始输入时,浏览器应该在该域中显示填写的选项,实现一个可输入下拉框的功能。

keygen:提供一种验证用户的可靠方法,规定用于表单的密钥生成器字段;当提交表单时,会生成2个键,一个为私钥、一个为公钥,私钥存储于客户端,公钥被发送至服务器,公钥可用于之后验证用户的客户端证书。

output元素:用于不同类型的输出

1
2
3
4
5
<form oninput="x.value=parseInt(a.value)+parseInt(b.value)">0
<input type="range" id="a" value="50">100 +
<input type="number" id="b" value="50">=
<output name="x" for="a b"></output>
</form>

H5表单属性

H5的form、input标签添加了几个新属性

新属性:
  • autocomplete
  • novalidate

新属性:

  • autocomplete
  • autofocus
  • form
  • formaction
  • formenctype
  • formmethod
  • formnovalidate
  • formtarget
  • height 与 width
  • list
  • min 与 max
  • multiple
  • pattern (regexp)
  • placeholder
  • required
  • step

H5语义元素

语义元素:有意义的元素:能够清楚地描述其意义给浏览器和开发者;

H5提供了新的语义元素来明确一个Web页面的不同部分。

header(头部区域,定义内容的介绍展示区域)、nav(定义导航链接)、section(定义文档节,页眉、章节)、article(定义独立的内容)、aside(主区域以外的内容,如侧边栏)、figcaption、figure、footer(底部区域)

H5的Web存储

在本地存储用户的浏览数据,数据以键值对存在,只允许该网页访问使用。

localStorage对象:用于长久保存整个网站的数据,保存数据无过期时间;

sessionStorage对象:用于临时保存同一窗口数据,在关闭窗口后会删除数据。

H5 Web SQL数据库

引入了一组使用SQL操作客户端数据库的APIs,拥有三个核心方法:

1、openDatabase:使用现有的数据库创建一个数据库对象;

2、transaction:控制一个事务,以及基于这种情况执行提交或回滚;

3、executeSql:执行实际的SQL查询。

H5 Web Workers

web worker是运行在后台的JS,不会影响页面的性能,当HTML页面执行脚本时,页面的状态不可响应直至脚本完成,而Web Worker是运行在后台的JS,独立于其他脚本,不影响页面的性能,可继续做想做的事情。

HTML5 WebSocket

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯

词嵌入

one-hot词向量构造起来很容易,但并不是一个含选择,因为其并不能准确地表达不同词之间的相似度,word2vec工具提出解决了上述问题,将每个词表示成一个定长的向量,并使这些向量能较好地表达不同词之间的相似和类比关系,包括跳字模型和连续词袋模型。

跳字模型

跳字模型假设:基于某个词来生成它在文本序列周围的词。

在该模型中,每个词被分为2个d维向量,用来计算条件概率,假设该词在词典索引为i,当它为中心词时向量表示为Vi,而它为背景词时向量表示为Ui。

设中心词Wc在词典中索引为c,背景词Wo在词典中索引为o,故给定中心词生成背景词的条件概率可以通过对向量内积做softmax运算而得到:跳字模型条件概率

跳字模型的参数是每个词所对应的中心词向量和背景词向量,训练中一般使用最大似然函数来学习模型参数

连续词袋模型

与跳字模型不同的是,连续词袋模型假设:基于某中心词在文本序列前后的背景词来生成该中心词。因为连续词袋模型的背景词有很多个,因此将这些取平均,然后使用和跳字模型一样的方法来计算条件概率。

同样,连续词袋模型的最大似然估计等价于最小化损失函数。

近似训练

跳字模型的核心在于,使用softmax运算得到给定中间词Wc生成背景词Wo的条件概率,,该条件概率对应的对数损失;

由于softmax运算考虑背景词可能是词典中任一词,以上损失包含了词典大小数目的项的累加。因此每次梯度计算可能开销过大,有下面两种方法来进行近似训练。

负采样:修改了原来的目标函数,使用相互独立事件来构造损失函数,其训练中每一步梯度计算开销与采样的噪声词的个数线性相关。

层序softmax:使用了二叉树这一个数据结构,树的每个叶结点代表词典中的每个词,并根据根节点到叶节点的路径来构造损失函数,每一步的梯度计算开销与词典大小的对数相关。

word2vec的实现

预处理数据集

PTB是常用的语料库

1、建立词语索引:将词映射到整数索引

2、二次采样::文本中一般会出现一些高频词,而在背景窗口中,与高频词一起出现会更有益。故训练词嵌入模型时可以对词进行二次采样,即每个索引词都有一定概率被丢弃。

3、提取中心词与背景词:我们将与中心词距离不超过背景窗口大小的词作为背景词,定义函数提取出所有中心词和它们的背景词。它每次在整数1与max_window_size之间随机均匀采样一个整数作为背景窗口大小。

负采样

读取数据集

使用随机小批量来读取数据集,小批量读取函数batchify,其输入data是一个长度为批量大小的列表,其中每个元素分别包含中心词center、背景词context、噪声词negativ,其返回的小批量数据符合我们需要的格式。

跳字模型

嵌入层:获取词嵌入的层称为嵌入层,在Gluon中可以通过创建nn.Embedding实例得到。其权重为一个矩阵,行数为词典大小、列数为每个词向量的维度。嵌入层输入为词的索引,返回为权重矩阵的第i行作为它的词向量。

小批量乘法:batch_dot对两个小批量中的矩阵一一做乘法。

跳字模型前向计算:输入包含中心词索引center以及连结的背景词与噪声词索引contexts_and_negatives。

定义损失函数:使用Gluon的二元交叉熵函数

子词嵌入fastText

英语单词通常由其内部结构和形成方式,而在word2vec中,我们并没有直接利用构词学中信息,而在fastText中,每个中心词被表示为子词的集合,利用

全局向量的词嵌入GloVe

文本分类情感分析:使用循环神经网络

文本分类是自然语言处理的一个常见任务,将一段不定长的文本序列变换成文本的类别。

子问题:使用文本情感分析来分析文本作者的情绪,即情感分析。

文本情感分析:使用卷积神经网络textCNN

其实,我们也可以将文本看作一维图像,从而可以使用一维卷积神经网络来捕捉临近词之间的关联,

编码器-解码器Seq2seq

前面都是表征并变换了不定长的输入序列,但在自然语言处理的很多应用中,输入、输出都可以是不定长序列,此时可用编码、解码器或Seq2seq模型。两个模型的本质都用到了两个循环神经网络,分别为编码器、解码器。+

在卷积神经网络中介绍了计算机视觉领域常用的深度学习模型,并实践了简单的图像分类。先描述目标检测的流程与方法,再使用全卷积网络对图像做语义分割,之后用样式迁移技术生成图像。

图像增广

1、扩大样本数据集;2、随机改变训练样本可降低模型对某些属性的依赖。两者均为了提高模型的泛化性。

常用方法:翻转和裁剪、变化颜色、叠加多个图像增广方法;

为了在预测时获得准确结果,图像增广通常仅用于训练集,而不在预测时使用含随机操作的图像增广。

Gluon数据集提供的transform模块中,transform_first函数将图像增广应用在每个训练样本(图像和标签)的第一个元素,即图像之上。

用增广后图像训练模型:

1、定义try_all_gpus函数,获取所有能用的GPU;

2、定义辅助函数_get_batch将小批量数据样本batch划分,并复制到ctx变量所指定的各个显存上;

3、定义evaluate_accuracy函数来评价模型的分类准确性,该函数通过辅助函数_get_batch使用ctx变量所包含的所有GPU来评价模型;

4、定义train函数使用多GPU训练并评价模型;

5、最终定义train_with_data_aug函数,来使用图像增广来训练模型;该函数获取所有GPU,并将Adam算法作为训练优化算法,之后将图像增广应用于训练数据集上,最后调用刚定义的train函数训练并评价模型。

1
2
3
4
5
6
7
8
def train_with_data_aug(train_augs, test_augs, lr = 0.001):
batch_size, ctx, net = 256, try_all_gpus(), d2l.resnet18(10)
net.initialize(ctx=ctx, init=init.Xavier())
trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr})
loss = gloss.SoftmaxCrossEntropyLoss()
train_iter = load_cifar10(True, train_augs, batch_size)
test_iter = load_cifar10(False, test_augs, batch_size)
train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs=10)

图像微调

由于收集数据所需要的成本较高,应用迁移学习:将从源数据集学到的知识迁移到目标数据集上。例:可从图形数据集训练的模型中抽取较通用的模型特征,边缘、纹理、形状、物体组成识别等等。

微调时常用的一种迁移学习技术,当目标数据集远小于源数据集时,微调有助于提升模型的泛化能力,步骤包括:

1、在源数据集上预训练一个神经网络模型,即源模型;

2、创建一个新的神经网络模型,即目标模型,其复制了源模型上除了输出层以外的所有模型设计与参数。假设该模型参数包含了源数据集上学习到的知识,且同样适用于目标数据集。

3、为目标模型添加一个输出大小为目标数据集类别个数的输出层,并初始化该层的模型参数。

4、在目标数据集上训练目标模型,将从头训练输出层,而其余层的参数将会基于源模型的参数微调得到。

热狗识别

基于一个小数据集对在ImageNet数据集上训练好的ResNet模型进行微调。

1、获取数据集;

2、定义和初始化模型:使用在ImageNet数据集上预训练的ResNet-18作为源模型,该源模型实例含有两个成员变量,即features和output。前者包括模型输出层以外的所有层,后者为模型的输出层,这样的划分方便微调除输出层以外所有层的模型参数。

3、微调模型:先定义一个使用微调的训练函数train_fine_tuning,以便多次调用。

1
2
3
4
5
6
7
8
9
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5):
train_iter = gdata.DataLoader(train_imgs.transform_first(train_augs), batch_size, shuffle=True)
test_iter = gdata.DataLoader(test_imgs.transform_first(test_augs), batch_size)
ctx = d2l.try_all_gpus()
net.collect_params().reset_ctx(ctx)
net.hybridize()
loss = gloss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': learning_rate, 'wd': 0.001})
d2l.train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs)

一般来说,微调参数会使用较小的学习率;而从头训练输出层可以使用较大学习率。

目标检测与边界框

在图像分类任务里,假设只有一个主体目标;而目标检测往往是图像中有多个感兴趣的目标。

目标检测算法通常会在输入图像中采样大量的区域,,然后判断是否包含感兴趣的目标,并调整区域边缘从而更准确地预测目标的真是边界框。

锚框:以每个像素为中心生成多个大小和宽高比不同的边界框。

交并比:(若某个锚框较好地覆盖了图像的狗,那么较好该如何量化)直观的方法是,衡量锚框与真实边界框间的相似度Jaccard系数可以衡量两个集合的相似度,Jaccard系数等于二者交集大小除以二者并集大小。

在训练集中,将每一个锚框视为一个训练样本,为了训练目标检测模型,需为每个锚框标注两个标签:1、锚框所含目标的类别;2、真实边界框相对锚框的便宜量offset。

在目标检测的训练集中,每个图像已经标注了真实边界框的位置及所含目标的类别,那么生成锚框后,如何为锚框分配与其相似的真实边界框呢?

分配真实边界框

1、锚框有Na个,真实边界框有Nb个,定义矩阵为Na X Nb,其第i列第j行的元素为锚框Ai与真实边界框Bj的交并比。则通过不停找出矩阵最大元素,且每找出一个元素则丢弃该行列的元素,直至矩阵丢弃完,只剩Na - Nb个锚框。

2、遍历剩下的锚框,只有该交并比大于预先设定的阈值时,才为锚框分配真实边界框Bj。

3、如果一个锚框A被分配了真实边界框B,将A的类别设为B的类别,并根据B和A的中心坐标的相对位置以及两个框的相对大小为锚框A标注偏移量。如果一个锚框没有被分配真实边界框,需将该锚框的类别设为背景,称为负类锚框。

4、通过contrib.nd模块中的MultiBoxTarget函数来为锚框标注偏移量和类别。该函数将背景设定为0,并从令0开始的目标类别的整数索引自加1,并通过expand_dims函数为锚框和真实边界添加样本维,并构造形状为(批量大小,包括背景的类别个数,锚框数)的任意预测结果。

非极大值抑制

当锚框数量较多时,同一目标可能输出较多相似的。用非极大值抑制来移除:对一个预测边界框B,模型会计算其各个类别的预测概率,其中最大概率对应的类别即B的预测类别,且在同一图像上将预测类别置信度从高到低排列,得到列表L。从L中选取置信度最高的预测边界框B1为基准,将与B1交并比大于某阈值的从L中移除,阈值为预定的超参数,此时L保留了置信度最高的边界框并移除了与之相似的其他预测边界框。

多尺度目标检测

如果以图像每个像素中心都生成锚框,很容易生成过多锚框而造成计算量过大,方法一:在输入图像中均匀采样一小部分像素,并以采样的像素为中心生成锚框。之后既然我们已经在不同尺度下生成了不同大小的锚框,相应的需要在不同尺度下检测不同大小的目标,基于卷积神经网络有如下的方法:

在某尺度下,假设我们根据Ci张形状为h X w的特征图生成h X w组不同中心的锚框,且每组锚框的个数为a。

假设这里的Ci张特征图为卷积神经网络根据输入图像做前向运算所得的中间输出,根据感受野的定义,特征图在相同位置的Ci个单元在输入图像的感受野相同且表征了同一感受野内的输入图像信息。因此我们将这Ci个单元变换为该位置为中心生成的a个锚框的类别和偏移量,故本质上使用感受野内的信息来预测锚框。

因此不同大小的感受野用于检测不同大小的目标,可通过设计网络来控制输出层感受野大小,从而分别用来检测不同大小的目标。

单发多框检测

由一个基础网络块和若干多尺度特征块串联而成。其中网络块用于从原始图像中抽取特征,因此一般会选择常用的深度卷积神经网络,例如:在分类层之前截断的VGG、或者用ResNet替代。

SSD单发多框检测

设计基础网络,使其输出的高宽较大,这样一来基于该特征图生成的锚框数量较多,用于检测较小目标;接下来每个多尺度特征块将上一层提供的特征图的高、宽减小,使感受野变广阔,这样越靠顶部其特征图越小,生成锚框越少,适合检测尺寸大的目标。借此,单发多框检测是一个多尺度的目标检测。

类别预测层

如果用全连接层作为输出,容易导致模型参数过多,故像NIN一样使用卷积层的通道进行输出类别的预测,来降低模型复杂度。即使用一个保持输入高、宽的卷积层,使输入、输出的空间坐标一一对应

边界预测层

设计与类预测层类似,需要为每个锚框预测4个偏移量。

连接多尺度的预测

由于每个尺度的特征图形状与锚框个数都可能不同,因此不同尺度预测输出形状可能不同。需要将他们变形成统一的格式并将多尺度的预测连结,从而让后续的计算更简单。

高、宽减半块

为了能多尺度地检测目标,需要定义高宽减半块,其串联了两个填充为1的3X3卷积层和步幅为2的2X2最大池化层,卷积层不改变特征图形状,而后面池化层将特征图的高、宽减半。

基础网络块

用于在原始图像中抽取特征,此处串联3个高、宽减半块,并将通道数翻倍,则当输入图像形状为256X256时,基础网络块的输出特征图的形状为32X32。

完整的模型

单发多框检测一共包括5个模块,每个模块即生成锚框,又来预测锚框的类别与偏移量。第一模块为基础网络块,二至四模块为高宽减半块,第五模块使用全局最大池化层将高和宽降到1。

单发多框检测训练模型

1、读取数据集并初始化;

2、定义损失函数与评价函数:

一、有关锚框类别的损失,图像分类问题一般使用的:交叉熵函数

二、有关正类锚框偏移量的损失:预测偏移量是一个回归问题,因此不用平方损失,而用L1范数损失,即预测值与真实值之间差的绝对值。

3、训练模型

在模型的前向计算过程中生成多尺度的锚框anchors,并为每个锚框预测类别cls_preds和偏移量bbox_preds,之后根据标签信息Y为生成的每个锚框标注类别和偏移量。最后,根据这两者值来计算损失函数。

4、预测目标

在预测阶段,我们读取图像并变换尺寸,转换为卷积层所需的四维格式,通过MultiBoxDetection函数根据锚框及其预测偏移量得到预测边界框,并通过非极大值抑制移除相似的预测边界框;最后,将置信度不低于0.3的边界框筛选为最终输出。

区域卷积神经网络R-CNN

R-CNN首先对图像选取若干提议区域,并标注它们的类别和边界框,之后用卷积神经网络对每个提议区域做前向运算来抽取特征。

R-CNN

1、对输入图像进行选择性搜索,来选取多个高质量的提议区域,通常在多个尺度下选取,并标注类别与真实边界框;

2、选取一个预训练的卷积神经网络,并将其在输出层之前截断,并将每个提议区域变形为网络所需要的尺寸,并通过前向计算输出抽取的提议区域特征;

3、将每个提议区域的特征连同其标注的类别作为一个文本,训练多个支持向量机对目标进行分类,其中每个支持向量机用来判断样本是否属于一个实例;

4、将每个提议区域的特征连同其标注的边界框作为一个样本,训练线性回归模型来预测真实边界框。

FAST R-CNN

R-CNN抽取的独立特征常有大量重复计算,利用FAST R-CNN进行简化,

FASTER R-CNN

将选择性搜索替换成区域提议网络,从而减少提议区域的生成数量,以达到较精确的目标检测结果。

Mask R-CNN

当训练数据还标注了每个目标在图像上的像素级位置,那么Mask R CNN模型能有效利用这些详尽的标注信息

语义分割和数据集

语义分割问题:关注如何将图像分割成属于不同语义类别的区域,且均为像素级,相比于锚框更加精确。

图像分割问题:利用像素间相关性将图像分割成若干区域,且训练时并不需要像素有关的标签信息,预测时也无法保证希望得到的语义。

实例分割问题:研究如何识别图像中各个目标实例的像素级区域,不仅要区分语义,还要区分不同目标实例,比如:区分两条同样语义的狗。

Pascal VOC2012数据集

由于语义分割的输出图像和标签在像素上一一对应,所以将图像随机裁剪成固定尺寸而不是缩放。

全卷积网络FCN

FCN实现了从图像像素到像素类别的变换;FCN通过转置卷积层,将中间层特征图的高、宽变换回输入图像的尺寸,从而令预测结果与输入图像在空间维上一一对应。

转置卷积层

构造模型

1、先使用卷积神经网络来抽取图像特征;

2、通过1X1卷积层将通道数变换成类别个数;

3、通过转置卷积层,将特征图的高、宽变换为输入图像的尺寸,使模型输出与输入图像的高、宽相同,并在空间位置一一对应,最终输出的通道中包含了该空间位置像素级别的类别预测;

样式迁移

使用卷积神经网络自动将某图像中的样式应用在另一图像上,两张输入图像:内容图像、样式图像。

具体实施

样式迁移

1、初始化合成图像,一般初始化成内容图像,该图像便是样式迁移过程中需要迭代的模型参数。

2、选择一个预训练的卷积网络来抽取图像的特征,其中模型参数在训练时无需更新,深度神经网络凭借多个层级逐级抽取图像的特征,可以选择其中某些层的输出作为内容特征;

3、正向传播计算样式迁移的损失函数,通过反向传播迭代模型参数,即不断更新合成图像。

预处理和后处理图像

预处理:在RGB三个通道分别做标准化,将结果变换成输入形式;

后处理:将输出图像中的像素值还原回标准化之前值;

抽取特征

使用基于ImageNet数据集训练的VGG-19模型来抽取图像特征;

定义损失函数

内容损失:利用平方误差函数衡量合成图像与样式图像在内容上差异;

样式损失:利用平方误差函数衡量合成图像与样式图像在样式上差异;

总变差损失:用于降噪,使合成图像中噪点(特别亮或特别暗的颗粒像素)减少。

损失函数为以上三者的加权和。

优化与深度学习

一般会预定义一个损失函数,再使用优化算法试图将其最小化,这样的损失函数通常被称为优化问题的目标函数,通常只考虑最小化目标函数。由于优化算法的目标函数通常是一个基于训练集的损失函数,故优化目的在于降低训练误差,而深度学习目的在于降低泛化误差,因此需要注意过拟合问题。

很多优化问题并不存在解析解,因此需要通过优化算法有限次迭代模型参数来尽可能降低损失函数值。

局部最小值

当一个优化问题的数值解在局部最优解附近时,由于目标函数有关解的梯度接近或变成0,因此最终迭代可能只令目标函数局部最小化而非全局最小化。

鞍点

在二维空间函数中,f(x,y) = x^2 - y^2,鞍点位置是x = 0处。且在图的鞍点位置,目标函数在x轴方向上是局部最小值,但在y轴方向上是局部最大值。

假设函数的输入为k维向量,输出为标量,则其海森矩阵有k个特征值;可通过该函数在对应点,其海森矩阵的特征值的正负,来判断该点为:(特征值全为负)局部最大值、(特征值全为正)局部最小值,还是(特征值有正有负)鞍点。

而通过随机矩阵理论可知:对一个大的高斯随机矩阵来说,任一特征值为正或负的概率均为0.5,故局部最小、最大值的可能性均为(0.5)^k,目标函数的鞍点比局部最值更常见。

梯度下降与随机梯度下降

一维梯度下降:通过用X - nf*(x)来代替x的方法,利用该式子不断迭代x,直到达到停止条件(一般为f’(x) ^2已经足够小,或者迭代次数已到达某个值)。其中正数n通常叫做学习率,为超参数,需人工设定。学习率过小:x更新缓慢,需要更多次迭代;学习率过大:可能会导致taylor展开的不等式不一定成立,迭代x不一定减小f(x)的值。

多维梯度下降:方向导数给出了x沿所有可能方向的变化率,为了最小化f,希望能找到f能被下降最快的方向,故利用梯度下降算法不断降低f的值。

随机梯度下降:n为训练数据样本数,x为模型的参数向量。则如果使用梯度下降时,会使用各个样本的平均作为,每次自变量迭代的计算开销为O(n),随n线性增长,因此若样本数大时,每次迭代的计算开销高。而随机梯度下降减少了计算开销,在每次迭代中随机均匀采样样本索引来计算梯度,从而减少每次迭代的开销。

小批量随机梯度下降

在每次迭代,梯度下降用整个训练集来计算梯度,而小批量梯度随机下降,利用随机均匀采样一个由样本索引组成的小批量B。

且由于随机采样得到梯度的方差在迭代过程中无法减小,因此实际中,小批量随机梯度下降的学习率需要在迭代过程中自我衰减。

在Gluon中可用创建Trainer实例来调用调优算法。

动量法

梯度下降又称最陡下降:自变量在当前位置下降最快的方向,在每次迭代中梯度下降根据自变量当前位置沿着梯度来更新自变量,然而,若自变量的迭代方向仅仅取决于自变量当前位置,可能会带来问题。

在二维或者多维的变量中,梯度下降往往难以同时兼顾学习率与确保f(x)下降;需要确保学习率较小,从而避免自变量在竖直方向越过函数最优解,但会因此导致向最优解移动缓慢。

动量法:设时间步t的自变量为Xt,学习率为Nt,动量法对每次迭代的步骤做出以下修改:Vt <- yV(t-1);Xt <- X(t-1) - Vt。y为动量超参数,范围在[0,1)

指数加权移动平均:

由指数加权平均理解动量法:

相对于小批量随机梯度下降,动量法需要对每一个自变量维护一个同它一样形状的速度变量,且在超参数中多了动量超参数。

在Gluon中,需要在Trainer实例中通过momentum来指定动量超参数,即可使用动量法。

AdaGrad算法

动量法依赖指数加权移动平均,使得自变量的更新方向更加一致,从而降低自变量在梯度较大的维度发散的可能。

而AdaGrad算法根据自变量在每个维度的梯度大小,来调整各个维度的学习率,从而避免统一的学习率难以适应所有维度的问题。

AdaGrad算法会使用一个小批量随机梯度Gt按元素平方的累加变量St:

Gluon中使用名称为”adagrad”的Trainer实例来调用该算法训练模型。

RMSProp算法

AdaGrad算法在迭代后期由于学习率过小,可能比较难找一个有用的解:因此用RMSProp算法改良后。

不同于AdaGrad算法里状态变量St是截至时间步t所有小批量随机梯度Gt按元素平方和。RMSProp算法将这些梯度按元素平方做指数加权移动平均,即

Gluon中使用名称为”rmsprop”的Trainer实例来调用该算法训练模型,且超参数由gammal指定。

还有AdaDelta算法、Adam算法

深度学习计算性能

命令式和符号式混合编程

之前一般使用Sequential类来串联多个层,先为了使用混合式编程,使用HybridSequential类来替换Sequential类。

异步运算

MXNet使用异步运算来提升性能,通过前端线程与后端线程的交互进行异步运算:前端线程无需等待当前指令从后端线程返回结果就继续执行后面的指令。

但同样异步运算会占据额外的内存:由于深度学习模型往往比较大,且内存资源通常有限,因此在训练模型时通常使用同步函数,而不用异步运算。

自动并行计算

MXNet后端会自动构建计算图,依据该图,系统会自动知道所有计算的依赖关系。

包括CPU与GPU的并行计算、多GPU计算、数据并行。

一、基础知识

在命令行中进行编译运行

1
2
3
4
5
6
7
8
~/project ls -l #执行命令ls,用于列出当前所在计算机存储位置中的文件和目录,并为其配置参数-l
-rw-r--r-- 1 user user 112 Aug 25 20:48 main.c
~/project gcc -o program main.c
#gcc为编译器名称,用该命令告诉gcc,将main.c的代码文件编译成名为program的可执行文件
~/project ls -l
-rw-r--r-- 1 user user 112 Aug 25 20:48 main.c
-rwxrwxr-x 1 user user 8512 Aug 25 21:35 program #目录中新增了program的可执行文件
./program #执行程序

变量相关

变量名:由大、小写字母,下划线,数字组成;数字不能开头;不能是有特定含义的保留字。

作用域:

变量声明语句之后,包裹了它声明语句的最内一层{}中,且一个变量在其作用域内仅能声明一次,但能够赋值多次,只要是在其作用域内的赋值,均起作用。

这些在结构化语句的内部的变量的作用域为结构化语句内部。注意,对于switch中在case内部定义的变量的作用域就是在当前case。我们可以简单理解为在大括号内部定义的变量,其作用域就是在当前大括号中,在当前大括号外部无效。对于循环嵌套和分支嵌套程序来说都是一样的。关于这一点不再赘述。

注意,当一个嵌套结构中出现两个不同作用域的变量时,变量的名称可以相同,在使用时以其小作用域为准。

与局部变量相对的就是全局变量,我们把定义在函数外部的变量称为全局变量,这些变量的作用域为整个程序,也就是所有的函数和结构化语句都能使用它们。

甚至多个源文件一起编译时,全局变量在其他文件中也能够生效需要用extern关键字在函数外部声明一个文件外部变量。

递归问题

在头递归的实现中,我们在进行下一层的调用前,没有进行计算,只有在下一层的返回之后,我们才完成了这一层的运算。

在尾递归的实现中,我们在进行下一层的调用前,会先进行计算,而在最终一般条件满足时,会将计算的结果逐层直接返回。

声明与实现分离

如果采用直接定义的方式来创造函数,则需要时刻关注它们之间依赖关系,并正确排序,不现实。需要进行声明与实现的分离,从而简化这一过程。

只需要在需要用到该函数前进行声明即可,定义可放在执行程序的后面位置,函数声明时可以不写函数参数名,只写参数类型。

变量地址做函数参数

swap函数中,由于该函数定义的形式参数a,b作用域有限,因此swap函数内的交换并不会影响main函数中x,y大的值。此时,需修改成传入参数x、y的地址,直接对其地址进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
void swap(int *a, int *b);
int main(){
int x,y;
swap(&x, &y);
}

void swap(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}

函数地址做函数参数

C语言中函数与变量类似,也有其自己的内存地址,但函数不能像变量一样可以进行值传递,在想要将函数作为另一个函数的参数进行传递时,需要传递它的地址。

1
2
3
int g(float (*f)(int), int a){
return f(a);
}

上面这种情况,函数g需要有一个形式参数来接收函数地址,其第一个参数需要一个返回值类型为float且有一个int类型参数的函数;第二个参数就是普通的int类型值

其实直接写f(a)和float (*f)(int)来调用其地址本质上是一样的。因此声明时需要如上所示去取函数的地址,而调用时,直接函数名与变量一起传入也是可以的。g(f(x), a)。

有关代码风格的空格

哪些使用空格的地方:

1、+、-、>、==、|、&&等双目运算符前后;

2、if、switch、for、while等关键字,函数名和之后的左小括号之间;

3、不在行尾的逗号、分号之后,例如for循环中的分号之后;

4、必须加空格的情况:如return后面不加空格就会报语法错误的情况。

数组

相当于定义了一系列地址相邻的元素,与取变量地址的方式一致,可以通过&radius[1]的方式取得数组radius在索引位置1元素的地址。在C语言中,对一个元素的地址加上位移值n得到的就是这个元素往后数n后所在元素的地址。

且一般来说,在地址上进行运算的方式访问数组的效率比利用索引更快,例如:你只希望访问数组中每一个元素一次时,可用while循环内使用地址上运算的方式,使用数组中每一个元素的值,而无需关心数组的索引是谁。

1
2
3
int *p_radius;
p_radius = &radius[0];
//这里*(p_radius + 1)或者(&radius[0] + 1)都会得到radius[1]元素的地址。

字符串的本质是数组

字符串实际上是一个元素为字符的数组,例如“Hello”由五个字母字符与一个空字符\0组成;任何字符串的内部表示都会以空字符‘\0’作为结尾,故可以以此方式找到字符串结尾。

同样,在C中提供字符数组初始化的简化方式:

1
char string[] = "Hello";

字符串更严谨应该被称为 字符串字面量,其表现为一对双引号包裹的0个或者多个字符;字面量并非仅包含字符串常量

1
2
3
4
int a;
a = 1234;
//语句中的1234其实就是一个整数型字面量,其实是将一个整数型字面量的值放入了变量中作为值。在字面量后往往需要增加一个后缀标记类型。L:长类型;U:无符号类型;F:浮点类型。
//除了十进制,也可用其他进制表示字面量。

除了用字符数组存储字符串,也可声明一个用于存储字符地址的变量操作字符串

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
//string的地址是内存栈区的地址;string2则是直接关联到"Hello"字符串字面量在内存中字面量池的地址。
int main() {
char string[] = "Hello";
printf("%s\n", string);
char *string2 = "Hello";
printf("%s\n", string2);
printf("%p\n", &string); //0x7fff09dd0480
printf("%p\n", string2);//0x4allc4
printf("%p\n", &"Hello");//0x4allc4
return 0;
}

按位运算

结构体

使用struct定义完结构体后,每当我们需要使用结构体时都需要写一次struct关键字,而其实C语言中提供了一种为某一已知类型添加别名的方式:typedef;typedef 原类型 类型别名

1
2
3
4
5
6
7
8
typedef struct point Point;
//完成设置别名后再进行变量声明时,可以不写struct point point1;
Point point1;
//也可以在定义结构体时同时在前面加上typedef,这样可以把两步合二为一
typedef struct point{
float x;
float y;
} Point;

函数的返回值也可以通过结构体的方式来进行返回,同样传入参数也可以以结构体的形式传入, 但若以结构体变量值的形式进行传递参数,这种传值的效率相对来说是低的(特别是结构体内成员特别多时),当vector_add函数不会改变传入参数的值时,没有必要采用会使用额外内存并需要赋值传入值到额外内存的“传值”作参数的方式,可将传入的参数改成指针的形式。

同时这样修改后调用函数参数时,也要加上取地址符号&。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <math.h>

typedef struct point {
float x;
float y;
} Vector;

Vector vector_add(Vector *v1, Vector *v2) {
Vector v_result;
v_result.x = v1->x + v2->x;
v_result.y = v1->y + v2->y;
return v_result;
}

int main() {
Vector v1 = { 2.4f, 2.5f };
Vector v2 = { 3.7f, 4.4f };
Vector v_result;
v_result = vector_add(&v1, &v2);
printf("(%f, %f)\n", v_result.x, v_result.y);
return 0;
}

以后再用到(*结构体指针名).结构体成员元素名形式的代码时,都可以将其写为:结构体指针名->结构体成员元素名。

用结构体构建一个简单链表的方法如下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>
typedef struct node{
int number;
struct node*next;
}Node;

Node *create_node(int new_number){
Node *temp_node;
temp_node = (Node *) malloc(sizeof(Node));
temp_node->number = new_number;
temp_node->next = NULL;
return temp_node;
}

int main() {
Node *head;
head = create_node(1);
head->next = create_node(2);
head->next->next = create_node(3);
printf("%d\n", head->next->number);
return 0;
}

约瑟夫环问题

N个同学围成圆圈,每个人被顺序地编了一个序号,从编号为K的人开始报1,之后按顺序增长,直至报数字M的人出列,出列人的下一个人从1继续开始报数,重复该过程直至所有人均出列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int data;
struct node *next;
} Node;

Node *circle_create(int n);
void count_off(Node *head, int n, int k, int m);

int main() {
int n, k, m;
scanf("%d%d%d", &n, &k, &m);
Node *head = circle_create(n);
count_off(head, n, k, m);
return 0;
}

Node *circle_create(int n) {
Node *temp, *new_node, *head;
int i;

// 创建第一个链表节点并加数据
temp = (Node *) malloc(sizeof(Node));
head = temp;
head->data = 1;

// 创建第 2 到第 n 个链表节点并加数据
for(i = 2; i <= n; i++) {
new_node = (Node *) malloc(sizeof(Node));
new_node->data = i;
temp->next = new_node;
temp = new_node;
}

// 最后一个节点指向头部构成循环链表
temp->next = head;

return head;
}

void count_off(Node *head, int n, int k, int m) {
int i, j;
Node *newNode, *beginNode;
for (i = 0; i < n - 1; i++){
beginNode = head->next;
}
newNode = head->next;
for (i = 0; i < k - 1; i++){
beginNode = head;
head = newNode;
newNode = head->next;
}
for (i = 0; i < n; i++){
for (j = 1; j < m ; j++){
beginNode = head;
head = newNode;
newNode = head->next;
}
if (m == 2 && i == n - 1) head = newNode;
printf("%d", head->data);
if (i != n - 1)
printf(" ");
head = newNode;
newNode = head->next;
beginNode->next = head;
}
return;
}

共用体

结构体的特性解决了一系列不同类型变量可以怎么放在一起组织的问题,而共用体则使多种不会同时出现的变量共用一块内存成为了可能,关键字union,共用体所占用的内存空间是被公用的,可通过两种或多种不同类型描述成员进行访问,且无论通过哪种方式进行访问,访问的是同一块内存空间。共用体类型变量的成员在内存中地址相同。

枚举enumeration

枚举由一系列的整数成员,表示这一数据类型的变量可以取的所有可能值,但是这些值都不直接以字面量形式存在,每个值都被单独给予一个名字,同样也可以给多个枚举成员进行显性的编号。

声明一个该枚举类型的变量时,只能取定义过的枚举类型中的成员名作为值,枚举类型的成员不能有结构体和共用体变量。

二、简单算法

牛顿迭代法

多数方程不存在求根方式,因此用牛顿法寻找方程的近似跟,时间复杂度为log n。

牛顿迭代法

步骤:1、确定迭代变量;2、建立迭代关系式;3、对迭代过程进行控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <math.h>
#define EPSILON 1e-6

double f(double x) {
return 2 * pow(x, 3) - 4 * pow(x, 2) + 3 * x - 6;
}

double f_prime(double x) {
return 6 * pow(x, 2) - 8 * x + 3;
}

double h(double x){
return pow(x,3) - 4 * pow(x,2) + 3 * x - 6;
}

double h_prime(double x){
return 3 * pow(x,2) - 8 * x + 3;
}

double newton(double (*fp)(double), double(*fp_prime)(double)) {
double x = 1.5;
while (fabs(fp(x)) > EPSILON){
x = x - fp(x) / fp_prime(x);
}
return x;
}

int main() {
printf("%g\n", newton(f, f_prime));
printf("%g\n", newton(h, h_prime));
return 0;
}

二分法

二分法同样是一个求方程近似跟的方法,在使用二分法近似求解时,先设定一个迭代区间,且区间两边自变量x对应的F(X)是异号的,之后计算两端中点位置x对应的f(x),再更新迭代区间,并确保迭代区间两端x对应的函数值还是异号,重复过程直至中点x对应的f(x)小于某个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <math.h>
#define EPSILON 1e-7

double bisection(int p, int q, double (*func)(int, int, double));
double f(int p, int q, double x);
int main() {
int p;
int q;
scanf("%d%d", &p, &q);
printf("%.4f\n", bisection(p, q, f));
return 0;
}

double bisection(int p, int q, double (*func)(int, int, double)) {
int forward, backward;
if(func(p, q, 20) > 0){
forward = 20;
backward = -20;
}
else{
forward = -20;
backward = 20;
}
int x = 0;
while(fabs(f(p, q, x)) > EPSILON){
if(f(p, q, x) > 0){
forward = x;
}
else backward = x;
x = (backward + forward)/2;
}
}

double f(int p, int q, double x) {
return p * x + q;
}

质数筛法

与之前的对每一个数依次判断是否为质数的方式不同,筛法的思想是“标注出所有非质数,输出所有没被标记的数字”,声明了一个mark数组,用于标记所有质数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <math.h>
int main() {
printf("2\n");
int digit;
int divisor;
for (digit = 3; digit <= 15; digit += 2) {
for (divisor = 3; divisor < sqrt(digit); divisor += 2) {
if (digit % divisor == 0){
break;
}
}
if (divisor == digit){
printf("%d\n", digit);
}
}
return 0;
}

质数筛法的逻辑:对于n以内的筛选来说,如果n为合数,c为n的最小因数,1< C*C < n;故只要找到了c就可以确定n是合数,并将n进行标记,通过这样的一个个筛选,将容易得到的合数均筛选出去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>

int main() {
int n = 15;
int mark[16] = {
1, 1, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
};
int c;
int j;

for (c = 2; c * c <= n; c++) {
if(mark[c] != 1){
for(j = 2; j <= n / c;j++){
mark[c * j] = 1;
}
}
}
for (c = 2; c <= n; c++){
if(mark[c] != 1){
printf("%d\n", c);
}
}

return 0;
}

折半查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
int BinSearch(int arr[], int len, int key)
{
int low = 0;
int high = len - 1;
int mid;
while(low <= high){
mid = (low + high) / 2;
if(key == arr[mid])
return mid + 1;
else if(key > arr[mid])
low = mid + 1;
else
high = mid - 1;
}
return 0;
}

int main() {
int n;
int k;
int numbers[1000001];
int m;
int i;
int j;

// 反复读入数字和查找数字的数量
while (scanf("%d%d", &n, &k) != EOF) {

// 读入给定的数字
for (i = 0; i < n; i++) {
scanf("%d", &numbers[i]);
}

for (j = 0; j < k; j++) {
// 读入待查找的数字,
scanf("%d", &m);
printf("%d",BinSearch(numbers, n, m));
// 请在下面完成查找读入数字的功能
if(j < k-1)
printf(" ");
}

}
return 0;
}

递归问题

有时候递归会使时间复杂度过高,是因为使用了多次的重复计算,可以用数组来存储之前计算的值,从而避免简单的运算重复进行。经典的爬楼梯问题代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main() {
int n, a, i;
scanf("%d", &n);
int arr[n];
arr[0] = 0;arr[1] = 0;arr[2] = 1;arr[3] = 1;
for(i = 4; i <= n; i++){
arr[i] = arr[i-2] + arr[i-3];
}
printf("%d", arr[n]);

return 0;
}

冒泡排序

基本思想:将数组中每个相邻元素进行两两比较,按照较小元素在前的原则决定是否进行交换,这样每一轮执行之后,最小元素就被换至了最后一位。完成第一轮后,我们从头进行第二轮的比较,直至倒数第二位(因为最后一位是已经被排序好的),依次进行直至所有元素被排列成预期的顺序为止。

1
2
3
4
5
6
//当有5个数待排序时,可写出如下的程序。
for (j = 0; j < 5; j++){
for (i = 0; i < 4 - j; i++){
swap(a[i], a[i+1]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>

int main() {
int n = 10;
int m;
int numbers[10];
int i, j;

// 读入给定的数字
for (i = 0; i < n; i++) {
scanf("%d", &numbers[i]);
}
for (i = 0; i < n; i++)
for (j = 0; j < n - 1 - i; j++){
if(numbers[j] < numbers[j+1]){
m = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = m;
}
}

for(i = 0; i < n; i++){
printf("%d", numbers[i]);
if(i != n-1){
printf(" ");
}
}
return 0;
}

选择排序

核心思想:根据从小到大的排序需求,它每一次从到排序的数据元素中选择出最小的元素,移动至序列的起始位置,然后在剩余的待排序元素中进行排序。

用两层循环来实现:1、寻找最小的元素需要一层循环;2、逐个被选出也需要一层循环。

螺旋输出矩阵

对任意的给定m行、n列的矩阵,按顺时针螺旋的顺序输出矩阵中所有的元素.

找规律,由外向内一层层进行for循环打印,最外层循环控制有多少层,每层分为(上方、右侧、下方、左侧)四个递增的循环,直至最后一层打印完;如果输出N为奇数,将会有N/2 + 1层

优化后的螺旋矩阵

当然它的规律很简单,直接的方法就是先申请一个矩阵,然后按螺旋方向填入相应的元素,填充完毕后再打印出来。它的时间按复杂为O(n2),已经是最优的(为什么?)。空间复杂度也为O(n2)。似乎已经很好了。 但是还不够好。

按照矩阵规律填充元素时,我们是随机访问矩阵元素的(如果可以按顺序访问,根本不用先存起来再打印)。随机访问内存,效率当然不高。所以即使时间复杂度已为最优,但那只是理论上的最优,在实践中表现并不一定就好。

假如能根据行列号直接计算出对应的矩阵元素就好了。当n给定后,这个矩阵就已经唯一确定了,那么每一个元素也是确定的。也就是说,每一个位置放什么元素仅仅取决于n。因此我们可以找到一个函数element(i, j),将行号i和列号j映射成对应这个行列号的元素。当然这个函数肯定不是一个简单的函数,不是一眼就可以看出来的,但也并不是不可能。

现在我们就来考查一下这个矩阵有什么特点。注意观察一下螺旋矩阵的最外层,它的左上角的元素是最小的,然后沿顺时针方向递增,就如同一个环一样(比如n为4时,1, 2, …, 12就是最外面一层环)。再注意一下里面一层,也是一样,顺时针方向递增的一个环(比如n为4时,13, 14, 15, 16就是里面一层环)。以此类推,环里面还有一层环(n为4时有2层环,n为5时有3层环,最里面一层只有一个元素25),实际上是一个圆环套圆环结构。每一圆环最关键的元素就是左上角的那一个元素。只要知道了这个元素,再加上这个正方形环的边长就可以计算出剩下的元素。设左上角元素为a,边长为l(ell),也就是边上有几个元素,并假设左上角的行号和列号均为0,其它元素的行号和列号都以它作参考,计算方法如下所示:

1、若i == 0,element(i, j) = a + j;

2、否则若j == 0,element(i, j) = a + 4(l-4) - (i-1) - 1;

3、否则若i == l-1,element(i, j) = a + 4(l-4) - (l-2) - 1 - j;

4、否则element(i, j) = a + l - 1 + i;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

#include<iostream>
using namespace std;

int a[10][10];

void Fun(int n)
{
int m=1;
int i,j;
for(i =0;i<n/2;i++){
for(j=0;j<n-i;j++){
if(a[i][j] ==0)
a[i][j] = m++;
}
for(j=i+1;j<n-i;j++){
if(a[j][n-1-i] ==0)
a[j][n-1-i] = m++;
}
for(j=n-i-1;j>i;j--){
if(a[n-i-1][j] ==0)
a[n-i-1][j] = m++;
}
for(j=n-i-1;j>i;j--){
if(a[j][i] ==0)
a[j][i] = m++;
}
}
if(n%2==1)
a[n/2][n/2]=m;
}

int main(void)
{
int n,i;
cout<<"请输入螺旋矩阵维数: "<< endl;
cin>>n;
cout<<"显示螺旋矩阵数值: "<< endl;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
a[i][j]=0;
}
}
Fun(n);
for(i=0;i<n;i++){
for( int j=0;j<n;j++){
cout<<a[i][j]<< "\t";
}
cout<<endl;
}
}

螺旋队列

问题描述: 设1的坐标是(0,0),x方向向右为正,y方向向下为正,例如,7的坐标为(-1,-1),2的坐标为(1,0)。编程实现输入任意一点坐标(x,y),输出所对应的数字.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

#include "stdafx.h"
#include <iostream>
#define max(a,b) ((a)<(b)?(b):(a))
#define abs(a) ((a)>0?(a):-(a))

using namespace std;

int foo(int x,int y)
{
int t = max(abs(x),abs(y));
int u = t+t;
int v = u-1;
v= v*v+u;
if(x == -t)
v+=u+t-y;
else if(y==-t)
v+=3*u+x-t;
else if(y ==t)
v+= t-x;
else
v+=y-t;
return v;
}


int _tmain(int argc, _TCHAR* argv[])
{
int x ,y;
int N;
cout<<"请输入螺旋队列数字: "<<endl;
cin>>N;
cout<<"显示螺旋队列数值: "<<endl;
for(y=-N;y<=N;y++)
{
for(x=-N;x<=N;x++)
cout<<"\t"<<foo(x,y);
cout<<endl;
}
while(scanf("%d%d",&x,&y)==2)
//printf("%d\n",foo(x,y));
cout<<"\t"<<foo(x,y);
return 0;
}

双螺旋矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
int main() {
int matrix[100][100];
int m;
int i;
int j;
int n;
scanf("%d%d", &m, &n);

for (i = 0; i < m; i++){
for (j = 0; j < n; j++){
scanf("%d", &matrix[i][j]);
}
}

int rowBegin = 0;
int rowEnd = m - 1;
int colBegin = 0;
int colEnd = n - 1;
while (rowBegin <= rowEnd && colBegin <= colEnd){
//先向右走
for (int i = colBegin; i <= colEnd; i++){
printf("%d", matrix[rowBegin][i]);
if (rowBegin == rowEnd && i == colEnd);
else printf(" ");
}
rowBegin++;
//向下走
for (int i = rowBegin; i <= rowEnd; i++){
printf("%d", matrix[i][colEnd]);
if (colBegin == colEnd && i == rowEnd);
else printf(" ");
}
colEnd--;
//向左走前先判定条件
if (rowBegin <= rowEnd){
for (int i = colEnd; i >= colBegin; i--){
printf("%d", matrix[rowEnd][i]);
if (rowBegin == rowEnd && i == colBegin);
else printf(" ");
}

rowEnd--;
}
if (colBegin <= colEnd){
for (int i = rowEnd; i >= rowBegin; i--){
printf("%d", matrix[i][colBegin]);
if (colBegin == colEnd && i == rowBegin);
else printf(" ");
}

colBegin++;
}
}
return 0;
}

三、深入探究语言逻辑

动态分配内存

1、栈区:C语言程序在编译时会被分配到内存上一篇有限的连续区域,这部分内存会被用于存储局部变量的值,这部分内存区域被称为栈区;

2、堆区:这部分内存是我们通过程序手动地向系统申请的,栈区内存带下编译时就已经被限制,如果使用超过限制的内存就会出现“溢出”的情况,而堆区的内存可以被一直申请使用,直至操作系统的有效内存无法再被申请位置;堆区被申请后,在使用的过程中若不释放就可能会出现内存泄漏;需要使用free(arr);

3、全局区(静态区):程序中的全局变量和静态变量都被存储在这块内存区域中;

如果需要使用堆上内存,需要将malloc.h,stdlib.h引入到程序中来。

1
2
int *p;
p = (int *) malloc(sizeof(int));

声明一个整数型的指针并向系统申请堆区上sizeof(int)的一块内存空间,并将指针p赋值为这片空间所在的起始地址;

malloc函数的返回值类型为void* ,这是一种特殊的指针类型,任何一个其他指针变量都可以直接被赋值给void*类型的指针,但如果反过来将无类型指针付给一个其他类型的指针变量,则必须要在前面加上被赋值指针变量的类型,如:(int *),进行强制类型转换

1、语言模型

与多层感知机和能有效处理空间信息的卷积神经网络不同,循环神经网络是为了更好地处理时序信息而设计的。引入状态变量来存储过去的信息,并用其与当前的输入共同决定当前的输出。

语言模型:可将自然语言文本看作一段离散的时间序列,假设一段长度为T的文本中的词依次为W1、W2….Wt,那么在离散的时间序列中,Wt可看作在时间步t的输出或标签。给定一个长度为T的词的序列,语言模型将计算该序列概率:P(W1,W2,….,Wt)。为计算该语言模型,需要先计算词的概率,以及一个词在给定前几个词的情况下的条件概率。

N元语法:计算和存储多个词的概率复杂度会呈指数级增加,故通过马尔可夫假设简化语言模型:一个词的出现只与前面N个词相关,即N阶马尔可夫链。

故基于n-1阶马尔可夫链,可将语言模型改写为:
$$
P(W1,W2,…,Wt) = ∏P(Wt|Wt-(n-1),…,Wt-1)
$$
以上也叫做n元语法,当n较小时,n元语法往往不准确,当n较大时,n元语法需要计算并存储大量的词频和多词相邻频率。

2、循环神经网络

循环神经网络:并非刚性地记忆所有固定长度的序列,而是通过隐藏状态来存储之前时间步的信息。利用之前的多层感知机,通过添加隐藏状态将其变成循环神经网络。

不含隐藏状态的多层感知机:

样本数为n、输出个数为d的小批量数据样本X。设隐藏层的激活函数为f,则其输出H为:
$$
H = f(X Wxh + bh)
$$
其中隐藏层权重参数W为Rn*d,隐藏层偏差参数b为R1xh,h为隐藏单元个数。上式两者根据广播机制来相加,设输出层的输出个数为q,则输出层的输出为:
$$
O = HWhq +bq
$$
若为分类问题,可用softmax(O)来计算输出类别的概率分布。

含隐藏状态的多层感知机

与上一个相区别的是,保存上一时间步的隐藏变量Ht-1,并引入一个新的权重参数Whh为Rhxh,该参数用于描述在当前时间步如何使用上一时间步的隐藏变量。
$$
Ht = f(Xt Wxh + Ht-1Whh +bh)
$$
与多层感知机相比,添加了隐藏变量来捕捉截至当前时间步的序列的历史信息,就像神经网络当前时间步的状态或记忆一样,因此也称为隐藏状态,由于在当前时间步使用了上一时间步的隐藏状态,因此计算是循环的。通过对上一次时间步的利用,其模型参数的数量不随时间步的增加而增长。将输入与前一时间步隐藏状态连结后,输入至一个激活函数为f的全连接层,该全连接层的输出就是当前时间步的隐藏状态,且模型参数为Wxh与Whh的连结,偏差为bh。

隐藏状态循环神经网络

3、基于字符级神经网络的语言模型

字符级循环神经网络

演示如何1基于当前与过去字符来预测下一个字符,训练时对每个时间步的输出层输出使用softmax运算,然后使用交叉熵损失函数来计算其与标签的误差。

处理数据集

建立字符索引:将每个字符映射成一个从0开始的连续整数,又称索引以方便后续处理。为得索引,我们将数据集中所有不同字符取出来,并逐一映射到索引来构造字典。

时序数据采样:每次随机读取小批量样本和标签,样本包含连续的字符。例:时间步数为5,样本序列为5个字符:‘’想,要,有,直,升”,则其标签序列为这些字符在训练集中的下一个字符:“要,有,直,升,机”。

随机采样:每个样本为原始序列上任意截取的一段序列,相邻的两个随机小批量不一定相邻,故每次随机采样前都需要重新初始化隐藏状态。

相邻采样:令相邻的两个随机小批量在原始序列上的位置也相毗邻,此时可以用一个小批量最终时间步的隐藏状态来初始化下一个小批量的隐藏状态:如此循环造成的影响:1、训练模型时,只需在每个迭代周期开始时初始化隐藏状态;2、当多个小批量通过传递隐藏状态串联起来时,梯度计算将依赖串联起的序列,迭代次数增加,梯度开销会越来越大。

one-hot向量

使用one-hot向量将词表示成向量输入到神经网络:每个字符已经同一个从0到N-1的连续整数值索引一一对应。如果一个字符的索引是i,则其向量为全0的长为N的向量,仅将位置为i的元素设为1。

1
2
def to_one_hot(X,size):
return [nd.one_hot(x,size) for x in X.T]

构建模型

先初始化模型参数,将隐藏单元个数num_hiddens作为超参数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
num_inputs, num_hiddens, num_outputs = vocab_size, 256, vocab_size

def get_params():
def _one(shape):
return nd.random.normal(scale=o.o1, shape=shape, ctx=ctx)
#先定义隐藏层参数
W_xh = _one((num_inputs, num_hiddens))
W_hh = _one((num_hiddens, num_hiddens))
b_h = nd.zeros(num_hiddens, ctx=ctx)
#输出层参数
W_hq = _one((num_hiddens, num_outputs))
b_q = nd.zeros(num_outputs, ctx=ctx)
#附上梯度
params = [W_xh, W_hh, b_h, W_hq, b_q]
for param in params:
param.attach_grad()
return params

定义init_rnn_state函数来返回初始化的隐藏状态,返回由一个形状为(批量大小,隐藏单元个数)的值为0的NDArray组成的元组。

1
2
def init_rnn_state(batch_size, num_hiddens, ctx):
return (nd.zeros(shape=(batch_size, num_hiddens), ctx=ctx))

定义rnn函数来在一个时间步中计算隐藏状态与输出,激活函数使用tanh

1
2
3
4
5
6
7
8
9
10
def rnn(inputs, state, params):
#inputs和outputs皆为num_steps个形状为(batch_size, vocab_size)的矩阵
W_xh, W_hh, b_h, W_hq, b_q = params
H, = state
outputs = []
for X in inputs:
H = nd.tanh(nd.dot(X, W_xh) + nd.dot(H, W_hh) + b_h)
Y = nd.dot(H, W_hq) + b_q
outputs.append(Y)
return outputs, (H,)

定义预测函数

基于前缀prefix(含有数个字符的字符串)来预测接下来的num_chars个字符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def predict_rnn(prefix, num_chars, rnn, params, init_rnn_state, 
num_hiddens, vocab_size, ctx, idx_to_char, char_to_idx):
state = init_rnn_state(1, num_hiddens, ctx)
output = [char_to_idx[prefix[0]]]
for t in range(num_chars + len(prefix) - 1):
#将上一时间步的输出作为当前时间步的输入
X = to_onehot(nd.array([output[-1]], ctx=ctx), vocab_size)
#计算输出和更新隐藏状态
(Y, state) = rnn(X, state, params)
#下一个时间步的输入是prefix里的字符或者当前的最佳预测字符
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(int(Y[0].argmax(axis=1).asscalar()))
return ''.join([idx_to_char[i] for i in output])

裁剪梯度

利用裁剪梯度以应对梯度爆炸,假设将所有模型参数梯度的元素拼接成一个向量g,并设裁剪的阈值为s;裁剪后的梯度的L2范数不超过s
$$
裁剪后的梯度为 min(s/||g|| , 1)g
$$

1
2
3
4
5
6
7
8
def grad_clipping(params, theta, ctx):
norm = nd.array([0], ctx)
for param in params:
norm += (param.grad **2).sum()
norm = norm.sqrt().asscalar()
if norm > theta:
for param in params:
param.grad[:] *= theta / norm

模型训练函数

困惑度用于评价语言模型的好坏,是对交叉熵损失函数做指数运算后得到的值。

与之前章节的训练函数相比,该模型训练有几点不同:1、用困惑度评价模型;2、在迭代模型参数前裁剪梯度;3、对时序数据采用不同的采样方法将导致隐藏状态初始化的不同。

使用Gluon进行模型的简洁实现

Gluon的rnn模块提供了循环神经网络的实现,

时间反向传播

不裁剪梯度时,模型将无法正常训练。我们将循环神经网络按时间步展开,从而得到模型变量与参数之间的依赖关系,并根据链式法则应用反向传播计算并存储梯度。

反向传播

每次迭代中,我们在依次计算完以上各个梯度后,会将它们存储起来,从而避免重复计算。同时,反向传播中的梯度计算可能会依赖变量的当前值,他们正是通过正向传播计算出来的。

4、门控循环单元

裁剪梯度可以应对梯度爆炸,但是没办法解决梯度衰减的问题。因此,循环神经网络在实际中很难捕捉时间序列中时间步距离较大的依赖关系。GRU门控循环神经网络通过可以学习的门来控制信息的流动。

重置门和更新门

两者输入均为当前时间步输入Xt与上一时间步隐藏状态Ht-1,输出由激活函数为sigmoid函数的全连接层计算得到

重置门与更新门

候选隐藏状态

GRU将计算候选隐藏状态来辅助稍后的隐藏状态计算,先将当前时间步重置门的输出与上一时间步重置门隐藏状态做按元素乘法。重置门中元素值接近0,则意味重置对应隐藏状态元素为0,则丢弃上一时间步的隐藏状态;若近似1,则保留上一时间步隐藏状态。

之后与当前时间步的输出连结,再通过含t激活函数tanh的全连接层计算候选隐藏状态,其所有元素值域为[-1,1]

候选隐藏状态

隐藏状态

最后,时间步的隐藏状态Ht的计算使用了当前时间步的更新门Zt来对上一时间步的隐藏状态Ht-1和当前时间步的候选隐藏状态Ht*来做组合:

隐藏状态

在Gluon中可直接调用rnn模块中的GRU类来实现GRU门控循环。

LSTM长短期记忆

LSTM引入了三个门:输入门、遗忘门、输出门

在Gluon中可调用rnn模块中的LSTM类来实现长短期记忆。

5、深度循环神经网络

深度循环神经网络:目前为止介绍的循环神经网络只有一个单项的隐藏层,深度学习中通常会用到含多个隐藏层的循环神经网络,在该网络中,隐藏状态的信息不断传递至当前层的下一时间步和当前时间步的下一层。

双向循环神经网络:之前的介绍的神经网络模型都是假设当前时间步由前面的较早时间步的序列来决定,因此信息均通过隐藏状态向后传递。故双向神经网络通过增加从后往前传递信息的隐藏层来更灵活地处理这类信息。

1、二维卷积层

二维卷积层:有高和宽两个空间维度用于处理图像数据,通常使用更直观的互相关运算来代替卷积运算。将输出与卷积核做互相关,并加上一个标量偏差来得到输出。

在训练模型时,通常先对卷积核初始化,然后不断迭代卷积核与偏差。使用corr2d函数来实现:

1
2
3
4
5
6
7
def corr2d(X,K):
h,w = K.shape
Y = nd.zeros((X.shape[0] - h + 1,X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i,j] = (X[ i:i+h , j: j+w]*K).sum()
return Y

在构造函数中声明了weight和bias,并在forward中利用corr2d函数来实现卷积核。

1
2
3
4
5
6
7
def __init__(self,kernel_size,**kwargs):
super(Conv2D,self).__init__(**kwargs)
self.weight = self.params.get('weight',shape=kernel_size)
self.bias = self.params.get('bias',shape=(1,))

def forward(self,x):
return corr2d(x, self.weight.data()) + self.bias.data()

一般通过数据学习得到核数组:首先构造一个卷积层,将其卷积核初始化为随机数组,并在每一次迭代中,使用平方误差来比较输出Y和卷积层的输出,并计算梯度来更新权重。

由于核数组都是学习出来的,所以卷积层使用互相关运算还是卷积运算都不影响模型预测时的输出。

特征图:输出可看作输入在空间维度上的表征。

影响X的前向计算的所有可能输入区域,叫做X的感受野。

2、填充与步幅

填充:在输入高、宽的两侧填充元素;用于增加输出的高、宽。

步幅:卷积窗口从输入窗口左上方开始,依次滑动,每次滑行的行数和列数称为步幅,用于减少输出的高、宽。

3、多输入、多输出通道

1、多输入时,构造输入通道数与输入数据的通道数相同的卷积核,从而能够互相关运算,对每个通道做互相关,然后通过add_n函数来进行累加

1
2
3
def corr2d_multi_in(X,K):
#首先沿X和K的第0维(通道维)遍历,然后用*将结果列表变为add_n函数的位置参数(positional argument)来相加
return nd.add_n(*[d2l.corr2d(x,k) for x , k in zip(X,K)]13

2、卷积核输入、输出通道数为Ci、Co,高和宽分别为Kh和Kw。若希望有多输出时,为每个通道分别创建核数组Ci x Kh x Kw,并将其在输出通道维上连结,卷积核形状为C0 X Ci x Kh x Kw,互相关时每个输出通道结果由卷积核在该通道核数组与整个输入数组计算得来。

1
2
3
def corr2d_multi_in_out(X,K):
#对K的第0维遍历,每次同输入X做互相关运算,所有结果用stack函数合并在一起
return nd.stack(*[corr2d_multi_in(X,K) for k in k])

4、1 x 1 卷积层

1 X 1 卷积主要发生在通道维上,输入与输出具有相同的高宽,输出中每个元素来自输入中在高、宽上相同位置的元素在不同通道之间的按权重累加。将通道维作为特征维,将高、宽上的元素当成数据样本,则其作用与全连接层等价。

1 X 1卷积层通常用来调整网络层之间的通道数,并控制模型复杂度。

5、池化层

目的:为了缓解卷积层对位置的过度敏感性。

池化层:每次对输入数据的一个固定形状窗口中的元素计算输出,直接计算池化窗口内的元素最大值或平均值,也叫最大池化或者平均池化。p X q 池化层

同样池化层也能设置填充和步幅。

1
2
pool2d = nn.MxaPool2D(1, kernel_size=(5,3),padding=(2,1),strides=(3,4))
#使用高、宽分别为5,3的卷积核;在高宽上的填充数分别为2,1;高宽上步幅分别为3,4

在处理多通道输入数据时,池化层会对每个输入通道分别池化,而不是像卷积层那样将各通道输入按通道相加。因此,池化层的输出通道数与输入通道数相等。

6、卷积神经网络

单隐藏层的多层感知机分类图像:将图像像素逐行展开,得到长为28*28=784的向量,并输入进全连接层中。局限性:1、同一列像素相隔远;2、对于大尺寸图像,全连接层可能导致模型过大。

卷积层如何解决:1、保留输入形状,使像素在高、宽两个方向的相关性均能被有效识别;2、通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

LeNet模型

分为卷积层块与全连接层块。

卷积层基本单位:卷积层+最大池化层。卷积层用于识别图像中空间模式,最大池化层用于降低卷积层对位置的敏感性。

卷积层输出形状为(批量大小,通道,高,宽);当其输出传入全连接层块时,全连接层会将小批量中的每个样本变平。形状将改变为二维,第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道*高 *宽。

AlexNet模型

1、包含8层变换,其中有5层卷积和2层全连接隐藏层,全连接输出个数为4096;

2、将激活函数由sigmoid改成了更简单的Relu,在不同参数初始化方法下使模型更容易训练,且在正区间的梯度恒为1;

3、利用丢弃法来控制全连接层的模型复杂度;

4、引入大量的图像增广,从而进一步扩充数据集来缓解过拟合;

5、利用了更多的卷积层和更大的参数空间来拟合大规模数据集ImageNet,是浅层网络与深度神经网络的分界线。

VGG模型

VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路;其组成规律是:连续使用数个相同的填充为1、窗口形状为3X3的卷积层后接上一个步幅为2、窗口形状为2X2的最大池化层。卷积层保持输入的高、宽不变,而池化层则对其减半。使用vgg_block函数来实现基础的VGG卷积层模块,可指定卷积层数量num_convs和输出通道数num_channels。

1
2
3
4
5
6
def vgg_block(num_convs, num_channels):
blk = nn.Sequential()
for _ in range(num_convs):
blk.add(nn.Conv2D(num_channels, kernel_size=3,padding=1,activation='relu'))
blk.add(nn.MaxPool2D(pool_size=2, strides=2))
return blk

VGG由卷积模块后接全连接层模块构成,卷积层串联数个vgg_block,其超参数由变量conv_arch定义,指定了VGG块中卷积层个数与输出通道数。下面构造VGG,5个卷积块,前2块用单卷积层,后3块用双卷积层。第一块输出通道为64,之后每次输出通道数翻倍,直至512。总共8个卷积,3个全连接,因此称为VGG-11。

1
2
3
4
5
6
7
8
9
10
11
12
conv_arch = ((1,64),(1,128),(2,256),(2,512),(2,512))

def vgg(conv_arch):
net = nn.Sequential()
#卷积层部分
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
#全连接层部分
net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(10))
return net

NIN模型

上述模型共通处均是:先以卷积层构成的模块充分抽取空间特征,再以全连接层构成的模块来输出分类结果。

NIN则提出了另外一个思路:串联多个由卷积层和全连接层构成的小网络来构建深层网络。

卷积层通常输入、输出:四维数组(样本、通道、高、宽);全连接层输入输出:二维数组(样本、特征)。故利用1 X 1卷积层来代替全连接层,从而使空间信息自然传递至后面层。

NIN块:由一个卷积层和两个充当全连接层的1 X 1卷积层串联而来,可自由设置第一个卷积层超参数。

NIN

NIN设计:除了使用NIN块以外,NIN去掉了AlexNet最后的3个全连接层,并使用输出通道数等于标签类别数的NIN块,然后使用全局平均池化层对每个通道中所有元素求平均,并直接进行分类。NIN这个设计的好处:显著减少模型参数尺寸,从而缓解过拟合。

GoogLeNet模型

GoolLeNet中的基础卷积块为Inception块,有4条并行的线路,前3条线路使用窗口分别为1X1,3X3,5X5的卷积层来抽取不同的空间尺寸下的信息,且中间2条线路会对输入先做1X1卷积来减少输入通道数,以降低模型复杂度;第四条线路则用3X3池化层后接1X1卷积层来改变通道数;且均使用了合适的填充来使输入、输出高宽一致。

GooLeNet

GooLeNet跟VGG一样,在主体卷积部分使用5个模块,每个模块之前使用步幅为2的3 X 3最大池化层来减小输出高宽。

模块一:使用一个64通道的7X7卷积层;

模块二:使用2个卷积层,64通道的1X1卷积层,然后是将通道增大3倍的3X3卷积层,对应Inception的线路二;

模块三:串联2个完整的Inception块;

模块四:串联5个Inception块;

模块五:2个Inception块,其后紧跟输出层,故同NIN一样使用全局平均池化层来将每个通道的高、宽变成1

7、批量归一化

数据标准化处理:任一特征在数据集所有样本上的均值为0、标准差为1,可以使各个特征的分布相近。对于浅层模型,数据标准化预处理已经足够了,但对于深层网络,模型参数更新仍容易造成剧烈变化。

批量归一化层(batch normalization):为应对深层模型的挑战,在训练时,利用小批量上的均值和标准差,不断调整神经网络中间输出,从而使整个神经网络在各层的中间输出数值更稳定。

全连接层的批量归一化

对于全连接层:将批量归一化层放在全连接层的仿射交换与激活函数之间。

对卷积层做批量归一化

对于卷积层:批量归一化发生在卷积计算之后、应用激活函数之前。

8、残差网络ResNet

实践中,添加过多层后训练误差往往不降反升,即使利用批量归一化使训练深层模型更加容易,该问题仍存在。

残差块:当理想映射f(x)极接近恒等映射时,残差映射也易于捕捉恒等映射的细微波动。

ResNet块

ResNet沿用了VGG全3X3卷积层的设计,残差块中首先由2个相同输出通道数的3X3卷积层,每个卷积层之后接一个批量归一化层和RELU激活函数,然后将输入跳过这2个卷积运算后再加在最后的RELU激活函数前。这样设计要求2个卷积层的输入、输出形状一样,从而可以相加。

ResNet的前两层跟GoogLeNet一样,在输出通道为64、步幅为2的7X7卷积层后接步幅为2的3X3最大池化层。

1
2
3
4
net = nn.Sequential()
net.add(nn.Conv2D(64,kernel_size=7,strides=2,padding=3),
nn.BatchNoem(),nn.Activation('relu'),
nn.MaxPool2D(pool_size=3,strides=2,padding=1))

不同在于其每个卷积层后增加的批量归一层,GoogLeNet在后面会接4个Inception组成的模块,而ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。我们用resnet_block函数来实现:

1
2
3
4
5
6
7
8
def resnet_block(num_channels, num_residuals, first_block=False):
blk = nn.Sequential()
for i in range(num_residuals):
if i == 0 and not first_block:
blk.add(Residual(num_channels, use_1x1conv=True, strides=2))
else:
blk.add(Residual(num_channels))
return blk

接着使用ResNet加入所有残差块,这里每个模块使用2个残差块

1
2
3
4
5
6
7
net.add(resnet_block(64, 2, first_block=True),
resnet_block(128,2),
resnet_block(256,2),
resnet_block(512,2))

net.add(nn.GlobalAvgPool2D(), nn.Dense(10))
#加入全局平均池化层后接上全连接层输出

9、稠密连接网络DenseNet

DenseNet

DenseNet主要构建模块时稠密块和过渡层,前者定义了输入、输出是如何连结的,后者则用来控制通道数,使之不过大。

由于每个稠密块都会带来通道数的增加,使用过多则会带来过于复杂的模型,过度层用来控制模型复杂度。它通过1X1卷积层来减少通道数,并使用步幅为2的的平均池化层减半高、宽,从而进一步降低模型复杂度。