赞
踩
最近在复习补充数据结构及算法知识,因为不是计算机专业,底子薄,没办法。虽然写了几年代码,但遇到问题时,总是感觉抓不住核心,无法高效的解决问题。尤其是看了侯捷老师的课程后,更加感慨。老师曾说:勿以流沙建高楼!!!顿时奉为至理名言。
好了,进入主题,复习算法章节时,看到二分法,据我浅薄的理解,无非就几步而已。按照大小给数据排序,然后按照树结构对数据进行组织,当然最好采用平衡树结构,这样会极大的缩小搜索次数。我突然冒出个想法,是否有比二分法更快的搜索算法呢?我们都知道,二分法已经达到Log2 n的时间复杂度了,已经非常快了。那么真的会有比二分法更快的方法吗,我非常好奇?直到看到了哈希算法!!!
对于哈希算法假设大家都比较熟悉了,不熟悉也没关系,百度上文章很多,稍微看下就行,简单来说,就是把数据按照一个固定的算法进行分类,取数据的时候也按照相同的算法进行查找,相比于遍历搜索节省了大量的查找次数和查找时间。大家如果想做更详细的了解的可以直接百度,我就不做过多介绍了。
重点来了!!!
能否利用哈希算法提高二分查找的效率呢?我们知道,二分法,每查找一次,能够排除近乎一半的数据(平衡二叉树),如果结合哈希算法,每一次查找,应该可以排除更多的数据。假如我有9个数,1 2 3 4 5 6 7 8 9 ,要查找2。根据二分法,先找到中间节点5,查找目标2小于5,所以在左子节点继续找,这时找到节点3,目标2仍然小于3,继续查找左子节点2,找到了!用了3次查找。
如果利用hash算法,这9个数按照顺序依次存放在一个数组中。那个固定的分类和查找算法就是,数组首地址 + sizeof(int) * (2 - 1)。这里的2代表你要查找的数字。
hash算法的弊端在于,如果数据过于庞大,hash算法需要的固定的分类算法也会相对比较复杂,且hash冲突也会频繁发生,当有多个值通过分类算法得到的结果相同,就会产生冲突。怎么去解决冲突,不在此章节展开讲,我所常见的就是在hash表每个位置后维护一张链表,有冲突的key值会依次放入链表中以待后续查询。
这样的hash结构,我称之为单次哈希。随便取的名字,大家不必介怀,最重要的是引出下面的多次hash结构。。。
俗话说,数不如表,表不如图,下面我给大家画个图
此图中的数据,我们可以暂时理解成一个节点数组,以及存放一个值的空间,和上面的例子一样。我将其作为N叉树中的一个节点Node,N = 10,分叉数量和数组中元素个数相同。 树结构如下图
下面还有,我就不画了,本质就是一个N叉树,每个树节点里维护一个Node数组以及一个值对象而已。结构很简单,奈何画图水平有限,见谅。。。
那么如何存放数据呢?初始树结构里只有一个根节点,节点里面数组为null,只有一个值为-1(-1为标识非法数值类型,默认无效值)。
先放一个1进去,首先计算1 % 10的值,等于1。这时找到第一个节点,也就是根节点,为根节点中的数组开辟空间,也就是什么时候用什么时候开辟,默认节点数组都是null,这样更节省内存。
然后在开辟空间的数组中把1放到数组下标为1的位置。以此类推,2会放到下标为2的位置。等等
好,这时候我们已经放了一个元素进去。如下图
那么继续,再放一个进去,例如2,计算2 % 10,等于2, 此时将2放到根节点中数组下标为2的位置,如下图
继续,放数字12进去,个位数字是2,十位数字是1,那么该怎么放呢?
首先判断个位,个位数字是2,就应该放到数组下标为2的位置,但是这个位置已经有值了,2.
好,在这个有值的节点中,开辟这个节点中数组的空间,将这个数组中下标为1(12的十位数字)的节点处放入12这个值。如下图
后面依次类推。所有的数据均按照此方法进行放置。
大致原则,将数字按照位数分组,个位数字是几,就找到根节点的数组中的第几个位置。继续计算十位数字是几,寻找到子节点中数组的第几个位置的孙节点,继续计算,一直到遍历完所有的位数,或者在找到一个空位置处存放这个数。
看到这里如果你晕了,没关系。只要你懂了如何在树结构中查找数据,你就能理解为什么要按照上面的方式存储数据了。
查找数据:
例如,在这个树结构中查找 1653564
首先看个位数字4,拿到根节点,找到节点中的数组下标为4的节点,比对这个节点中的值是否等于1653564,假如不等,继续计算十位数字6,在上面找到的下标为4的节点中,拿到这个节点的数组数据,找到下标为6的节点,对比其值是否等于1653564,如果不等,继续找百位数字5,继续拿子节点做对比。直到找到节点值等于1653564的节点。
也就是说,若查询1653564这个值,最多仅需要比对7次即可。也就是这个数有多少位,我们最多也就比对多少次。
上面的示例中,我是以十进制的方式演示的,实际中为了更快的进行取模运算和计算各分位数值。
我采取的16 * 16进制进行数据存储,也就是一个数组中存放256个节点。这样能更好的减少查询次数。当然也可以存放16个节点。
那么这种存储数据的方式的优缺点是什么呢?我总结一下:
1、当数据量过于庞大时,比如达到亿级,十亿,百亿级时(假设内存中能够存储这么多数据)。此方法查询数据的次数明显少于二分法。根据我自己的测试结果显示,50万条数据,二分法查找需要22次左右,HashTree方法只需要3次左右(HashTree是我自己对这种存储结构的暂时命名,且3次是我优化了这个结构的查询次数,后面会讲到优化的方法),由于电脑支持不了千万级及以上的数据,所以只测试到了50w级别。
2、单次查询,二分法只做了比较大小,然后根据子节点地址取值操作,而hashTree方法先做取余运算,然后根据余数寻址取值。按照道理来讲,二分法查询一次的速度更快。但通过我的测试,对二分法和hashtree方法都做了一亿次的重复查询并取值操作,hashtree方法居然更快,这里我也没搞懂,稍后我会贴代码,大家也可以看看。
1、hashtree结构占用空间较大,最不利情况,hashtree应该比二叉树结构多占N倍的空间,N为每个节点中数组的个数。
2、hashtree结构空间利用率不高,数据越紧密,hashtree的空间利用率越高,最不利就是N倍。最不利的数据就像存储1 11 111 1111 11111...这种情况,当然这种情况很少。
3、目前来说,办公型普通配置电脑,达不到上亿级的内存数据存储,即使hashtree查询次数明显少于二分法,也仅仅是3次查询和22次查询的区别。一闪而逝,都用不了1ms。查询效率相当。
4、hash树目前仅支持对整形数据的操作,如果分正负值的话,也可以分两棵树去存储。如果存储小数值,可将小数乘以10的N次方后转为整数,进行存储。二分法则比较简单,只要能够比较大小,任何数据都可以作为实际值存储在节点中。
由于目前计算机硬件的限制,hashtree并无明显优势,但胜在潜力。在日新月异的技术更新的年代,可能在某些领域会逐渐凸显优势。尤其是现在各行各业都在进行数字化转型,数据量也是呈指数级增长。在这些海量的数据中,能够速度更快的查询数据也是衡量技术指标的一个必不可少的条件。我相信hashtree也能在未来的某一天内发挥它的作用。
最后,本文仅为作者抛砖引玉,欢迎大家批评指正,当然更多的还是希望读者看到后会有一些启发。有疑问也可以联系作者qq 3535413812。谢谢大家!
using HashTreeTest.HashTree;
using HashTreeTest.RedBlackTree;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace HashTreeTest
{
public static class Demo
{
public static void Main()
{
Console.WriteLine("****************测试开始****************");
int count = 10000000;
int find = 0x20000;
HashTreeAlgorithm.HashTree hashTree = new HashTreeAlgorithm.HashTree();
RB_Tree avlTree = new RB_Tree();
#region treetest
//hash树
{
Console.WriteLine($"开始构造Hash树");
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < count; i++)
{
hashTree.Add(i);
}
watch.Stop();
var detalTime = watch.Elapsed.TotalMilliseconds;
Console.WriteLine($"构造Hash树完成");
Console.WriteLine($"用时:{detalTime}ms");
Console.WriteLine($"开始查找{find}");
var watch2 = new Stopwatch();
watch2.Start();
var result = hashTree.Find(find);
watch2.Stop();
var detalTime2 = watch2.Elapsed.Ticks;
Console.WriteLine($"查找次数{hashTree.LoopCount}");
Console.WriteLine($"找到结果{result}");
ConsoleEx.WriteLine(ConsoleColor.Green, $"用时:{detalTime2}个ticks");
}
//AVL树
{
Console.WriteLine($"开始构造AVL树");
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < count; i++)
{
avlTree.Insert(i);
}
watch.Stop();
var detalTime = watch.Elapsed.TotalMilliseconds;
Console.WriteLine($"构造AVL树完成");
Console.WriteLine($"用时:{detalTime}ms");
Console.WriteLine($"开始查找{find}");
var watch2 = new Stopwatch();
watch2.Start();
var node = avlTree.Find(find);
watch2.Stop();
var detalTime2 = watch2.Elapsed.Ticks;
Console.WriteLine($"查找次数{avlTree.LoopCount}");
Console.WriteLine($"找到结果{node.data}");
ConsoleEx.WriteLine(ConsoleColor.Green, $"用时:{detalTime2}个ticks");
}
#endregion
Console.WriteLine("****************测试结束****************");
Console.ReadKey();
}
}
public static class ConsoleEx
{
public static void WriteLine(ConsoleColor textColor, string text)
{
var foreColor = Console.ForegroundColor;
Console.ForegroundColor = textColor;
Console.WriteLine(text);
Console.ForegroundColor = foreColor;
}
public static void Write(ConsoleColor textColor, string text)
{
var foreColor = Console.ForegroundColor;
Console.ForegroundColor = textColor;
Console.Write(text);
Console.ForegroundColor = foreColor;
}
}
namespace HashTreeAlgorithm
{
/// <summary>
/// 一个储存单元是8位,一个byte,内部数组的容量
/// </summary>
public class HashTree
{
public int LoopCount = 0;
public Node Root { get; set; } = new Node(-1);
public void Add(int data/*不验证data的有效性,只要你add了,就说明这个数是一个可以插进来的数*/)
{
var lastNum = data & 0xff;
if (Root.Nodes == null) { Root.Nodes = new Node[Util.Capacity]; }
var newNode = new Node(data);
if (Root.Nodes[lastNum] == null)
{
Root.Nodes[lastNum] = newNode;
newNode.Parent = Root;
}
else
{
Root.Nodes[lastNum].AddNode(newNode);
}
return;
}
public int Find(int data)
{
var temp = data;
var current = Root;
while (current.Value != data)
{
LoopCount++;
if (temp == 0x0) { return -1; }
var lastDigit = temp & 0xff;
current = current.Nodes[lastDigit];
if (current == null) { return -1; }
temp >>= Util.ShiftBit;
}
return current.Value;
}
public int Find2(int data, int count)
{
var temp = data;
var current = Root;
for (int i = 0; i < 100000000; i++)
{
LoopCount++;
var lastDigit = 0x15236548 & 0xff;
var n = current.Nodes[lastDigit];
temp >>= Util.ShiftBit;
}
return current.Value;
}
public void Clear() { Root = new Node(-1); }
}
public class Node
{
public int Value = -1;
public Node Parent = null;
public Node[] Nodes = null;
public Node(int value) { Value = value; }
public int GetGradeLevel()
{
var grade = 0;//从0级开始
var currentNode = this;
while (currentNode.Parent != null)
{
currentNode = currentNode.Parent;
grade++;
}
return grade;
}
public void AddNode(Node insertNode)
{
if (insertNode.Value == this.Value) { return; }
var grade = this.GetGradeLevel();
if (insertNode.Value == grade)
{
if (this.Value == -1)
{
this.Value = insertNode.Value;
}
return;
}
var index = insertNode.Value.GetDigit(grade);//要插入到哪个位置的索引
//1、如果当前节点Value为空,则找对应Index下的子节点,递归判断
if (Value == -1)
{
var childNodes = this.Nodes.ToList().FindAll(x => x != null);
var childValues = childNodes.Select(x => //找childNodes中的两个子节点的有效value值
{
if (x.Value != -1) { return x.Value; }
Node child = x;
while (child.Value == -1)
{
var childs = child.Nodes.ToList().FindAll(y => y != null);
var validChild = childs.Find(z => z.Value != -1);
if (validChild != null) { return validChild.Value; }
child = childs.FirstOrDefault();
}
return -1;
});
if (childValues == null || childValues.Count() < 2) { throw new Exception("节点数据错误:未找到两个以上节点有效Value值"); }
//如果新节点 是 当前节点下两个及以上子sun节点 的 祖父节点,则新节点 替换 当前节点
if (Util.IsParent(childValues.First(), insertNode.Value) && Util.IsParent(childValues.Last(), insertNode.Value))
{
this.Value = insertNode.Value;
}
else if (this.Nodes[index] == null) //如果所需插入位置无节点,直接插入
{
insertNode.Parent = this;
this.Nodes[index] = insertNode;
}
else//如果所需插入位置有节点,递归加入
{
Nodes[index].AddNode(insertNode);
}
return;
}
//2、如果当前节点Value不为空,则根据父子关系或者兄弟关系重新进行组织
else if (Util.IsSon(Value, insertNode.Value))//插入值为子孙节点
{
CreateSonNode(insertNode, index);
}
else if (Util.IsParent(Value, insertNode.Value)) //插入值为祖父节点
{
CreateFatherNode(insertNode);
}
else //出五服了
{
CreateBrotherNode(insertNode);
}
return;
}
private void CreateBrotherNode(Node insertNode)
{
var currentGrade = this.GetGradeLevel();
var currentIndex = this.Value.GetDigit(currentGrade - 1);//所在父节点的子节点数组索引
var newNode = new Node(-1);
this.Parent.Nodes[currentIndex] = newNode;
newNode.Parent = this.Parent;
this.Parent = newNode;
insertNode.Parent = newNode;
newNode.Nodes = new Node[Util.Capacity];
var index1 = insertNode.Value.GetDigit(currentGrade);
newNode.Nodes[index1] = insertNode;
var index2 = this.Value.GetDigit(currentGrade);
newNode.Nodes[index2] = this;
}
private void CreateFatherNode(Node insertNode)
{
var currentGrade = this.GetGradeLevel();
var currentIndex = this.Value.GetDigit(currentGrade - 1);//所在父节点的子节点数组索引
var parent = insertNode;
parent.Parent = this.Parent;
this.Parent = parent;
if (parent.Nodes == null) { parent.Nodes = new Node[Util.Capacity]; }
parent.Nodes[currentIndex] = this;
var index = this.Value.GetDigit(currentGrade);//所在父节点的子节点数组索引
parent.Parent.Nodes[index] = this;
if (parent.Value > this.Value || parent.Value > insertNode.Value)
{
throw new Exception("父节点数据错误:父节点Value大于子节点Value");
}
}
private void CreateSonNode(Node insertNode, int index)
{
var son = insertNode;
if (this.Nodes == null) { this.Nodes = new Node[Util.Capacity]; }
if (this.Nodes[index] == null)
{
son.Parent = this;
this.Nodes[index] = son;
}
else
{
this.Nodes[index].AddNode(insertNode);
}
if (this.Value > insertNode.Value)
{
throw new Exception("父节点数据错误:父节点Value大于子节点Value");
}
}
}
}
namespace HashTree
{
public static class Util
{
public const int Capacity = 0x100; //16进制数字
public const int ShiftBit = 0x8; //右移时的位数
public const int FilterBit = 0xff;
/// <summary>
/// 返回指定位数的数字
/// </summary>
/// <param name="number">0x1582</param>
/// <param name="digitIndex">0返回0x82,1返回0x15</param>
/// <returns></returns>
public static int GetDigit(this int number, int digitIndex)
{
if (number < 0) { return -1; }
number >>= digitIndex * ShiftBit;
var result = number & FilterBit;
return result;
}
public static bool IsSon(int parent, int son)
{
return (parent & son) == parent;
}
public static bool IsParent(int son, int parent)
{
return (parent & son) == parent;
}
}
}
namespace RedBlackTree
{
public class TreeNode
{
public int data;
public TreeNode left;
public TreeNode right;
public TreeNode parent;
public bool color;
public TreeNode(int data)
{
this.data = data;
this.color = false;
}
}
public class RB_Tree
{
public const bool RED = false;
public const bool BLACK = true;
public TreeNode root = null;
public void Insert(int item)
{
if (root == null)
{
//注意,根节点为黑色
root = new TreeNode(item);
Black(root);
}
else
{
//这一步只完成插入操作,与二叉查找的插入是一样的
var newnode = Inserts(item);
//重点在于这一步,看节点是否需要调整
InsertFixUp(newnode);
}
}
public TreeNode Inserts(int item)
{
TreeNode node = root;
TreeNode newnode = new TreeNode(item);
while (true)
{
if (item > node.data)
{
if (node.right == null)
{
//注意父子关系要确定
newnode.parent = node;
node.right = newnode;
break;
}
node = node.right;
}
else if (item < node.data)
{
if (node.left == null)
{
newnode.parent = node;
node.left = newnode;
break;
}
node = node.left;
}
}
return newnode;
}
private void InsertFixUp(TreeNode node)
{
TreeNode parent = node.parent;
if (parent == null)
{
Black(node);
return;
}
if (IsBlack(parent)) return;
TreeNode uncle = Sibling(parent);
TreeNode grand = parent.parent;
if (IsRed(uncle))//叔叔节点是黑色
{
Black(parent);
Black(uncle);
InsertFixUp(Red(grand));
return;//这里直接返回,因为下面处理的是祖父节点的调整,与自己这个节点已经无关了
}
if (IsLeftChild(parent))
{
if (IsLeftChild(node))
{
Black(parent);
Red(grand);
RightRotate(grand);
}
else
{
Black(node);
Red(grand);
LeftRotate(parent);
RightRotate(grand);
}
}
else
{
if (IsLeftChild(node))
{
Black(node);
Red(grand);
RightRotate(parent);
LeftRotate(grand);
}
else
{
Black(parent);
Red(grand);
LeftRotate(grand);
}
}
}
public void Remove(int data)
{
TreeNode node = Find(data);
Remove(node);
}
private void Remove(TreeNode node)
{
if (node == null)
return;
if (node.left != null && node.right != null)
{
TreeNode s = FindNext(node);
node.data = s.data;
node = s;
}
TreeNode replacement = node.left != null ? node.left : node.right;
if (replacement != null)
{
replacement.parent = node.parent;
if (node.parent == null)
root = replacement;
else if (node == node.parent.left)
node.parent.left = replacement;
else
node.parent.right = replacement;
RemoveFixUp(node, replacement);
}
else if (node.parent == null)//是叶子结点,并且是根节点
{
root = null;
//RemoveFixUp(node, null);
}
else//是叶子结点,但不是根节点
{
if (node == node.parent.right)
node.parent.right = null;
else
node.parent.left = null;
RemoveFixUp(node, null);
}
}
private void RemoveFixUp(TreeNode node, TreeNode replacement)
{
//1、如果删除的是红色节点
if (IsRed(node)) return;
//2、如果删除的是黑色节点,用于取代node的节点是红色
if (IsRed(replacement))
{
Black(replacement);
return;
}
//3、如果删除的是黑色节点,孩子都是黑色(或者null)
TreeNode parent = node.parent;
//3.1、如果删除的是根节点,直接返回
if (parent == null) return;
//获取删除节点的兄弟节点
bool left = parent.left == null || IsLeftChild(node);//判断删除的是父节点的左子节点还是右子节点
TreeNode sibling = left ? parent.right : parent.left;
if (left)//被删除的节点在右边,兄弟节点在左边
{
if (IsRed(sibling))//兄弟节点是红色情况
{
Black(sibling);
Red(parent);
LeftRotate(parent);
sibling = parent.right;//重新指定兄弟节点
}
//能来到这里。兄弟节点必是黑色了,处理黑色兄弟的情况
if (IsBlack(sibling.left) && IsBlack(sibling.right))
{
bool parentBlack = IsBlack(parent);
Red(sibling);
Black(parent);
if (parentBlack)
{
RemoveFixUp(parent, null);
}
}
else//能来到这里,说明兄弟中至少有一个红色子节点
{
if (IsBlack(sibling.right))
{
RightRotate(sibling);
sibling = parent.right;//兄弟节点变了,这里要重新转换
}
Color(sibling, ColorOf(parent));
Black(sibling.right);
Black(parent);
LeftRotate(parent);
}
}
else
{
if (IsRed(sibling))//兄弟节点是红色情况
{
Black(sibling);
Red(parent);
RightRotate(parent);
sibling = parent.left;//重新指定兄弟节点
}
//能来到这里。兄弟节点必是黑色了,处理黑色兄弟的情况
if (IsBlack(sibling.left) && IsBlack(sibling.right))
{
bool parentBlack = IsBlack(parent);
Red(sibling);
Black(parent);
if (parentBlack)
{
RemoveFixUp(parent, null);
}
}
else//能来到这里,说明兄弟中至少有一个红色子节点
{
if (IsBlack(sibling.left))
{
LeftRotate(sibling);
sibling = parent.left;//兄弟节点变了,这里要重新转换
}
Color(sibling, ColorOf(parent));
Black(sibling.left);
Black(parent);
RightRotate(parent);
}
}
}
public TreeNode FindNext(TreeNode root)
{
if (root == null)
return null;
if (root.right == null)
return null;
TreeNode current = root.right;
while (current != null)
{
if (current.left != null)
{
current = current.left;
}
else
break;
}
return current;
}
public int LoopCount = 0;
public TreeNode Find(int data)
{
TreeNode current = root;
while (current != null)
{
LoopCount++;
//Find1(50);
if (data < current.data)
current = current.left;
else if (data > current.data)
current = current.right;
else
return current;
}
return null;
}
private void LeftRotate(TreeNode node)
{
TreeNode parent = node.parent;
TreeNode rightChild = node.right;
TreeNode grandLeftChild = rightChild.left;
node.parent = rightChild;
rightChild.left = node;
rightChild.parent = parent;
if (parent != null)
{
if (parent.left == node)
{
parent.left = rightChild;
}
else
{
parent.right = rightChild;
}
}
else//如果父节点是空,说明是根节点,让root指向旋转后的父节点
{
root = rightChild;
}
node.right = grandLeftChild;
if (grandLeftChild != null)
{
grandLeftChild.parent = node;
}
}
private void RightRotate(TreeNode node)
{
TreeNode parent = node.parent;
TreeNode leftChild = node.left;
TreeNode grandRightChild = leftChild.right;
node.parent = leftChild;
leftChild.right = node;
leftChild.parent = parent;
if (parent != null)
{
if (parent.left == node)
{
parent.left = leftChild;
}
else
{
parent.right = leftChild;
}
}
else//如果父节点是空,说明是根节点,让root指向旋转后的父节点
{
root = leftChild;
}
node.left = grandRightChild;
if (grandRightChild != null)
{
grandRightChild.parent = node;
}
}
//染色
private TreeNode Color(TreeNode node, bool color)
{
if (node == null)
return node;
node.color = color;
return node;
}
//获取颜色
private bool ColorOf(TreeNode node)
{
if (node == null)
return BLACK;
return node.color == RED ? RED : BLACK;
}
//染成红色
private TreeNode Red(TreeNode node)
{
return Color(node, RED);
}
//染成黑色
private TreeNode Black(TreeNode node)
{
return Color(node, BLACK);
}
//判断是否为红色
private bool IsRed(TreeNode node)
{
return ColorOf(node) == RED;
}
//判断是否为黑色
private bool IsBlack(TreeNode node)
{
return ColorOf(node) == BLACK;
}
//判断是不是父亲的左孩子
private bool IsLeftChild(TreeNode node)
{
return node.parent != null && node.parent.left == node;
}
//判断是不是父亲的右孩子
private bool IsRightChild(TreeNode node)
{
return node.parent != null && node.parent.right == node;
}
//获取兄弟节点
private TreeNode Sibling(TreeNode node)
{
if (IsLeftChild(node))
{
return node.parent.right;
}
if (IsRightChild(node))
{
return node.parent.left;
}
return null;
}
public void PreOrder(TreeNode node)
{
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode current = node;
while (current != null || stack.Count > 0)
{
while (current != null)
{
string color = current.color == RED ? "R" : "B";
Console.Write(color + current.data + " ");
stack.Push(current);
current = current.left;
}
if (stack.Count > 0)
{
current = stack.Pop().right;
}
}
}
}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。