在程序中,存放指定的数据最常用的数据结构有两种:数组和链表。
数组和链表的区别:
1、数组是将元素在内存中连续存放。
链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。
2、数组必须事先定义固定的长度,不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
链表动态地进行存储分配,可以适应数据动态地增减的情况。
3、(静态)数组从栈中分配空间, 对于程序员方便快速,但是自由度小。
链表从堆中分配空间, 自由度大但是申请管理比较麻烦。
数组和链表在存储数据方面到底孰优孰劣呢?根据数组和链表的特性,分两类情况讨论。
一、当进行数据查询时,数组可以直接通过下标迅速访问数组中的元素。而链表则需要从第一个元素开始一直找到需要的元素位置,显然,数组的查询效率会比链表的高。
二、当进行增加或删除元素时,在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样,如果想删除一个元素,需要移动大量元素去填掉被移动的元素。而链表只需改动元素中的指针即可实现增加或删除元素。
那么,我们开始思考:有什么方式既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势?HASH呼之欲出。
所谓的hash,简单的说就是散列,即将输入的数据通过hash函数得到一个key值,输入的数据存储到数组中下标为key值的数组单元中去。
我们发现,不相同的数据通过hash函数得到相同的key值。这时候,就产生了hash冲突。解决hash冲突的方式有两种。一种是挂链式,也叫拉链法。挂链式的思想在产生冲突的hash地址指向一个链表,将具有相同的key值的数据存放到链表中。另一种是建立一个公共溢出区。将所有产生冲突的数据都存放到公共溢出区,也可以使问题解决。
如何实现hash的动态增加空间的效果?这和装在因子密切相关。装填因子 = 填入表中的元素个数 / 散列表的长度。当装填因子达到一定值a时,我们就让数组增加一定的内存空间,同时rehash。
下面用两个示例来加深理解。
示例一:用链表实现队列
节点类
1. package
2.
3. public class
4.
5. //构造器:传入Object对象
6.
7. public
8. data=obj;
9. }
10. public Object data; //Object对象
11. public LinkNode next;//下一个节点
12.
13. //重写toString方法
14.
15. public
16. //System.out.println(data);
17. return
18. }
19.
20. //返回Object对象
21. public
22. return
23. }
24. //修改Onject对象
25. public
26. data=o;
27. return
28. }
29. }
队列类
1. package
2.
3. public class
4.
5. public LinkNode front=null;//第一个节点
6. public LinkNode last=null;//最后一个节点
7.
8. public static void
9. new
10. new LinkNode("郑睿1");
11. new LinkNode("郑睿2");
12. new LinkNode("郑睿3");
13. new LinkNode("郑睿4");
14. lq.InsertLinkNode(lq1);
15. lq.InsertLinkNode(lq2);
16. lq.InsertLinkNode(lq3);
17. lq.InsertLinkNode(lq4);
18. int
19. "链表的长度为"+count);
20. for(int i=0;i<count;i++){
21. LinkNode ln = lq.getLinkNode(i);
22. "链表的第"+i+"个元素的的值为"+ln.getData().toString());
23. }
24. 2);
25. count=lq.getLength();
26. "链表现在的长度是"+lq.getLength());
27. for(int i=0;i<count;i++){
28. LinkNode ln = lq.getLinkNode(i);
29. "链表的第"+i+"个元素的的值为"+ln.getData().toString());
30. }
31. 1).Update("更新后的对象郑睿");
32. for(int i=0;i<count;i++){
33. LinkNode ln = lq.getLinkNode(i);
34. "链表的第"+i+"个元素的的值为"+ln.getData().toString());
35. }
36. for(int i=0;i<200;i++){
37. new
38. lq.InsertLinkNode(ln);
39. }
40. "数组长度为"+lq.getLength());
41. }
42.
43.
44. /**
45. * 插入节点
46. * @param obj:插入节点的对象
47. */
48. public void
49.
50. //当链表为空,新建一个节点并设置为第一个节点
51. if(front==null){
52. new
53. last=front;
54. }
55. //当链表不为空,新建一个节点并插入到最后一个节点的后面
56. else{
57. new
58. last.next=next;
59. last=next;
60. }
61. }
62. /**
63. *在指定索引下插入节点
64. * @param index
65. */
66. public void insertIndexObj(int
67. //判断输入的索引是否越界,如果越界,则抛出异常
68. int
69. if(index>total||index<0)
70. throw new java.lang.RuntimeException("输入的索引越界了!");
71. LinkNode lNode=getLinkNode(index);
72. new
73. lNode.insert(linkNode);
74.
75. }
76. /**
77. * 根据索引删除链表
78. * @param index:索引
79. */
80. public void deleteLinkNode(int
81.
82. //判断输入的索引是否越界,如果越界,则抛出异常
83. int
84. if(index>total||index<0)
85. throw new java.lang.RuntimeException("输入的索引越界了!");
86. if(front!=null){
87. LinkNode n=front;
88. LinkNode m=front;
89. int count=0;
90. while(n!=null){
91. if(count==index){
92. if(n.equals(front)){
93. front=front.next;
94. }
95. else{
96. m.next=n.next;
97. }
98. }
99. m=n;
100. n=n.next;
101. count++;
102. }
103. }
104. }
105. /**
106. * 根据索引取出节点
107. * @param lNode:节点
108. * @return:根据索引返回的节点
109. */
110. public LinkNode getLinkNode(int
111. if(front==null)
112. return null;
113. LinkNode l=front;
114. int count=0;
115. while(l!=null){
116. if(count==index)
117. return
118. count++;
119. l=l.next;
120. }
121. return null;
122. }
123.
124. /**
125. * 得到链表的长度
126. * @return:链表的长度
127. */
128. public int
129. if(front==null)
130. return 0;
131. LinkNode l=front;
132. int count=0;
133. while(l!=null){
134. count++;
135. l=l.next;
136. }
137. return
138. }
139. /**
140. * 修改对象节点
141. * @param index:对象节点索引
142. * @param obj:修改对象内容
143. */
144. public void UpdateLinkNode(int
145. LinkNode lNode=getLinkNode(index);
146. lNode.Update(obj);
147.
148. }
149. }
示例二:保存QQ号码及QQ用户
QQ用户类
1. package
2.
3. public class
4.
5. public String userName;//用户姓名
6. public String passWord;//用户密码
7. public String sex;//用户性别
8. public int age;//用户年龄
9.
10. public
11. return
12. }
13. public void
14. this.userName = userName;
15. }
16. public
17. return
18. }
19. public void
20. this.passWord = passWord;
21. }
22. public
23. return
24. }
25. public void
26. this.sex = sex;
27. }
28. public int
29. return
30. }
31. public void setAge(int
32. this.age = age;
33. }
34.
35. }
队列类
1. package
2.
3. public class
4.
5. public LinkNode front=null;//第一个节点
6. public LinkNode last=null;//最后一个节点
7.
8.
9. /**
10. * 根据索引删除链表
11. * @param index:索引
12. */
13. public void deleteLinkNode(int
14. if(index<0||index>)
15. if(front!=null){
16. LinkNode n=front;
17. LinkNode m=front;
18. int count=0;
19. while(n!=null){
20. if(count==index){
21. if(n.equals(front)){
22. front=front.next;
23. }
24. else{
25. m.next=n.next;
26. }
27. }
28. m=n;
29. n=n.next;
30. count++;
31. }
32. }
33. }
34. /**
35. * 根据索引取出节点
36. * @param lNode:节点
37. * @return:根据索引返回的节点
38. */
39. public LinkNode getLinkNode(int
40. if(front==null)
41. return null;
42. LinkNode l=front;
43. int count=0;
44. while(l!=null){
45. if(count==index)
46. return
47. count++;
48. l=l.next;
49. }
50. return null;
51. }
52.
53. /**
54. * 得到链表的长度
55. * @return:链表的长度
56. */
57. public int
58. if(front==null)
59. return 0;
60. LinkNode l=front;
61. int count=0;
62. while(l!=null){
63. count++;
64. l=l.next;
65. }
66. return
67. }
68. /**
69. * 修改对象节点
70. * @param index:对象节点索引
71. * @param obj:修改对象内容
72. */
73. public void UpdateLinkNode(int
74. LinkNode lNode=getLinkNode(index);
75. lNode.Update(obj);
76.
77. }
78. }
QQ节点类
1. package
2.
3. public class
4.
5.
6. //构造器:传入QQ号,QQ用户对象
7. public QQNode(int
8. this.qq=qq;
9. this.user=user;
10. }
11.
12. public int qq;//QQ号
13. public QQUser user;//QQ用户
14. public QQNode next;//下一个QQ节点对象
15. public LinkQueue lq;//队列
16.
17. public
18. return
19. }
20. public void
21. this.lq = lq;
22. }
23. public int
24. return
25. }
26. public void setQq(int
27. this.qq = qq;
28. }
29. public
30. return
31. }
32. public void
33. this.user = user;
34. }
35. public
36. return
37. }
38. public void
39. this.next = next;
40. }
41.
- }
Hash方法类
1. package
2.
3. public class
4.
5. private QQNode[] table=new QQNode[100];
6. private float load=0.75F;//装载因子
7. private int count=0;
8. private int gain=100;
9.
10. public static void
11. new
12. new
13. "用户一");
14. "1");
15. 20);
16. "女");
17. 1, user1);
18. new
19. "用户二");
20. "12");
21. 20);
22. "男");
23. 2, user2);
24. new
25. "用户三");
26. "123");
27. 20);
28. "男");
29. 3, user3);
30. new
31. "用户四");
32. "1234");
33. 20);
34. "女");
35. 101, user4);
36. qqHash.returnQQNode();
37.
38. 1);
39. 2);
40. 3);
41. 101);
42. QQNode[] table=qqHash.returnQQNode();
43. // System.out.println("表的长度为 "+table.length);
44. qqHash.returnTabLen();
45. for(int i=0;i<table.length;i++){
46. if(table[i]!=null){
47. "实际存在的Table["+i+"]的值"+table[i].getQq());
48. LinkQueue lq=table[i].getLq();
49. if(lq.getLength()>0){
50. "存在挂链");
51. for(int j=0;j<lq.getLength();j++)
52. "挂链第"+i+"个值为"+((QQNode)lq.getLinkNode(i).getData()).getUser().getUserName());
53. }
54. }
55.
56. }
57.
58. }
59. /**
60. * 存放QQ及用户
61. * @param qq:QQ号
62. * @param user:QQ用户
63. */
64. public void put(int
65. //判断己放对象的个数和table的长度比是否达到装载因子,
66. //如果超过,则reHash一次,增长,
67. //然后再放!
68. float rate=(float)count/table.length;
69. if(rate>=load){
70. new
71. for(int i=0;i<table.length;i++){
72. QQNode q=table[i];
73. int
74. QQUser u=q.getUser();
75. int
76. q.setQq(qqnum);
77. q.setUser(user);
78. table1[qqhash]=q;
79. }
80. table=table1;
81. }
82. "table长度:"+table.length);
83. //判断是否存在hash冲突
84. boolean
85. "是否存在冲突"+judge);
86. int
87. "hash值"+index);
88. if(judge){//不存在hash冲突,直接将qq和用户存放在通过hash函数获得的地址中
89. new
90. q.setQq(qq);
91. q.setUser(user);
92. table[index]=q;
93. count++;
94. }
95. else{//存在hash冲突
96. new
97. q.setQq(qq);
98. q.setUser(user);
99. " "+q.getQq()+" "+q.getUser());
100. LinkQueue lq=q.getLq();
101. lq.InsertLinkNode(q);
102. for(int i=0;i<lq.getLength();i++)
103. "======"+((QQNode)lq.getLinkNode(i).getData()).getQq());
104. if(lq.getLength()==0){
105. table[index].setNext(q);
106. }
107. }
108.
109. }
110. /**
111. * 根据QQ号取得QQ用户信息
112. * @param qq:QQ号
113. * @return:QQ用户
114. */
115. public QQUser get(int
116. int
117. QQNode q=table[index];
118. "节点"+q.getQq());
119. //看是否有下了个节点,如有,则是冲突的,就要一个一个比较
120. if(q.next==null)
121. return
122. LinkQueue lq=q.getLq();
123. for(int i=0;i<lq.getLength();i++){
124. QQNode aa=(QQNode)lq.getLinkNode(i).data;
125. int
126. if(qqq==qq)
127. "查找到了!");
128. return
129. }
130. return null;
131. }
132. //计算QQ号的has值,自定义has函数
133. private int hashQQ(int
134. return
135. }
136. //判断是否存在hash冲突
137. private boolean exist(int
138. int
139. if(table[qqhash]!=null)
140. return false;
141. return true;
142. }
143. //返回表
144. private
145. "已存在数据个数为"+count);
146. return this.table;
147. }
148. //返回表中实际存在的数据的个数
149. private int
150. return this.count;
151. }
152. }
--------------------总结
-----------------------------------------------------------
一、当进行数据查询时,数组可以直接通过下标迅速访问数组中的元素。而链表则需要从第一个元素开始一直找到需要的元素位置,显然,数组的查询效率会比链表的高。
二、当进行增加或删除元素时,在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样,如果想删除一个元素,需要移动大量元素去填掉被移动的元素。而链表只需改动元素中的指针即可实现增加或删除元素。
那么,我们开始思考:有什么方式既能够具备数组的快速查询的优点又能融合链表方便快捷的增加删除元素的优势?HASH呼之欲出。
所谓的hash,简单的说就是散列,即将输入的数据通过hash函数得到一个key值,输入的数据存储到数组中下标为key值的数组单元中去。
我们发现,不相同的数据通过hash函数得到相同的key值。这时候,就产生了hash冲突。解决hash冲突的方式有两种。一种是挂链式,也叫拉链法。