写给读者的话:
本人是一个刚刚毕业的程序员,大学期间数据结构学的比较扎实,来工作后发现虽然概念都知道,但是应用不是很熟练,所以打算重新撸几遍数据结构,正好在写java,这里就用java描述数据结构了;然后有几个要点:
1)实践永远是检验真理的唯一标准,要想知道自己学的好不好,锻炼提升自己,就需要多多练习了,本人在工作中,都会去思考代码的优化问题,看能够有更好的办法解决;
2)温故而知新,很多东西看一遍是不可能理解透彻的。
3)共享精神,要乐于去分享知识,不要怕被别人超越(有句话说得好,一直被模仿,从未被超越,精髓不是都能学来的而是自己总结体会出来的)。
4)今日更新一下一个优美的品质。我和我同事同样都是看书,我的同事显得仔细的多,很有质疑精神,但是过多的仔细就显得固执了。希望我在以后的学习中能尽量细致一点吧。
书中讲述了大量的数学公式,指数,对数,二项式,推理方法以及java的泛型和函数对象,这里不做整理了。有兴趣的可以去看看书,不是说这一块不重要,而是很基础(装13了,哈哈)!
看来到哪里都离不开数学,真是学会数理化走遍天下都不怕。这一部分主要讲述了算法的时间复杂度,看的我头要炸了。
本章主要讲述三种基本的数据结构,每一个有意义的程序都应该显式地至少使用一种这样的数据。
定义:是带有一组操作的一些对象的集合。
printList();
makeEmpty();
find();返回某一项首次出现的位置
insert();链表中插入元素
remove();链表中删除元素
findKth();返回索引
next操作;
previous操作;
这里讲一下java里的链表类:LinkedList
简介
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。
继承关系
java.lang.Object
↳ java.util.AbstractCollection
↳ java.util.AbstractList
↳ java.util.AbstractSequentialList
↳ java.util.LinkedList
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable {}
LinkedList的本质是双向链表。
1)LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
2)LinkedList包含两个重要成员:header和size。
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
size是双向链表中节点的个数。
LinkedList类中的APIhttp://www.yiibai.com/java/java_linkedlist_class.html
LinkedList的遍历方式(比较实用)
1)迭代器遍历,即通过Iterator去遍历
for(Iterator iter = list.iterator(); iter.hasNext();)
iter.next();
2)普普通通的遍历
for (int i=0; i
3)另类for循环
for (Integer integ:list)
;
4)pollFirst遍历
while(list.pollFirst() != null)
;
5)pollLast遍历
while(list.pollLast() != null)
;
6)removeFirst()遍历
try {
while(list.removeFirst() != null)
;
} catch (NoSuchElementException e) {
}
7)removeLast()遍历
try {
while(list.removeLast() != null)
;
} catch (NoSuchElementException e) {
}
结果示意:
在类库中,java语言包含了一些普通数据类型的实现。该语言的一部分通常叫做collections API。表ADT是Collections API中实现的数据结构之一;
集合( collection)是将其他对象组织到一起的一个对象。集合也叫作容器(container),它提供了一种方法来存储、访问和操作其元素。集合帮助 Java 程序员很容易地管理对象。Java程序员应该熟悉集合框架中一些重要的类型,它们在java.util包中。
这里挪用一下网上的继承关系图,从表中可以看出,set,list,quene这几种数据结构是继承了collection接口的。
:
下面介绍一下collection的API函数:
可以看到collection接口扩展了Iterabale接口。实现Iterable接口的类可以拥有增强的for循环。实现Iterable接口的类必须实现一个Iterator的方法,改方法返回一个Iterator类型对象,该Iterator类定义在java.util包中,如下图。这里强调一个小技巧,用Iterator迭代器进行remove操作的效率要比用collection的remove操作高,但是在迭代器操作的过程中,如果collection对象发生了结构变化,就会呆滞迭代器报错,这里需要注意。
ArrayList是list的可变数组实现,LinkedList是list的双向链表实现。数组的优点在于遍历速度快,删除添加开销较大;链表的优点在于添加和删除速度快,遍历开销大;但是有了迭代器,对于LinkedList是非常有利的,加快了遍历速度,但是对于ArrayList没有很大的提升,因为添加和删除操作数组总是要整体移动的,除非是在末端操作。
这里写一个小插曲,其实看一个类或者接口怎么用,最好的办法是去阅读JDK的文档,上边说的很清楚,现在也有汉化版的,不过英文版的可能描述更清晰一些。http://tool.oschina.net/apidocs/apidoc?api=jdk-zh
本宝宝用的markDown软件画图操作不是很友好,在word里画了一下单向链表的移动流程。不论是Iterator还是ListIterator都是在对象间移动;next()函数取值是取的当前最后一次访问的值,next()函数调用一次既会向后移动一次。remove操作是删除最近访问的元素;如果要逆向遍历列表要首先正向遍历一遍;
下面展示一下ListIterator接口的API函数:nextIndex()和previousIndex()函数返回前后位置的索引,如果前后节点为空也会返回索引
程序实例:
public class LinkIterator {
public static void main(String[] args) {
List Horde = new LinkedList<>();
Horde.add("Great ");
Horde.add("Wall ");
Horde.add("is ");
Horde.add("beautiful!");
Iterator it = Horde.iterator();
while (it.hasNext()){
String temp =it.next().toString();
if (temp.contains("ea")){
it.remove();
}
}
System.out.println(StringUtils.join(Horde,""));
//建议两段程序分别运行
ListIterator Lit = Horde.listIterator();
while (Lit.hasNext()){
String temp =Lit.next().toString();
System.out.println(Lit.hasNext());
System.out.println(Lit.hasPrevious());
System.out.println(Lit.nextIndex());
System.out.println(Lit.previousIndex());
if (temp.contains("ll")){
Lit.remove();
}
}
System.out.println(StringUtils.join(Horde,""));
}
}
Iterator运行结果:
ListIterator运行结果:
这一部分较为复杂,不是很明白,作者自已写了一个ArrayList类的实现,这里涉及内部类的一些问题。
作者自已写了一个LinkedList类的实现
定义:限制插入和删除只在一个位置上进行的表,叫做栈的顶端,是一种先进后出队列。对空栈的pop(出栈)操作是一种错误。ArrayList和LinkedList都支持栈的操作
应用:字符的匹配,后缀表达式
定义:插入在一端,删除在另一端的表。
本章结束了。这里还建议看一下map,map.entry和Queue类的介绍,参照jdk1.6中文版
本章将讨论树这种非常有效的数据结构,其大部分的运行时间为O(logN),这里涉及的树叫做二叉树;是两种类集合库TreeSet和TreeMap实现的基础。
这里不再介绍树的基础预备知识,有不清楚的同学可以看看这个https://www.cnblogs.com/polly333/p/4740355.html
应用—表达式树
定义:使二叉树成为二叉查找树的性质是,对于树中的每一个节点X,它的左子树中所有项的值都小于X中的项,而它右子树中所有项的值大于X中的项。二叉查找树没有直接的类予以支持, 需要自己编写。http://blog.csdn.net/a19881029/article/details/24379339
这里介绍一下TreeSet类的方法摘要:
在介绍一下TreeMap类的方法摘要:
AVL树的特性:一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正负的区别,但是这个并不影响我们对平衡二叉树的讨论)。
看了看大神的博客,才发现自己写的文章很菜啊,可能是学习的收获没有那么深刻吧,这里附上链接,向前辈致敬!http://blog.csdn.net/javazejian/article/details/53892797
插入等操作对AVL树的影响,通过单旋转和双旋转解决。附上链接:
http://blog.csdn.net/pacosonswjtu/article/details/50522677
http://blog.csdn.net/liyong199012/article/details/29219261
伸展树是基于二叉查找树的,它不保证树一直是平衡的,但是各种操作的平均复杂读是 O(logN) 。
伸展树的设计是具体考虑到了局部性原理 (刚被访问的内容下次可能还被访问,查找次数多的内容可能下次还被访问),为了使整个的查询时间更小,查询频率高的那些结点应当处于
靠近树根的位置。这样,一个比较好的解决方案就是:每次查找就结点之后对树进行重新构造。把查找的结点搬移到树根的位置,以这种方式自调整形式的二叉查找树就是伸展树。
http://blog.csdn.net/u012124438/article/details/78067998
http://blog.51cto.com/kiritor/1226766
散列表(hash table)ADT,只支持二叉查找树所允许的一部分操作。散列表的实现常常叫做散列(hashing)
本人是垃圾,看的懂书中关于散列的种种叙述却无法写出来,只能整理一点实用的标准类库。
数据结构(Java语言)——HashTable(分离链接法)简单实现http://blog.csdn.net/zhang_zp2014/article/details/47980563
标准库包含set和map的哈希实现,即hashSet和hashMap类,必须提供equals()和hashCode()方法,通常是使用分离链接散列法实现的。
示例程序:
public class HashTable {
private static final int DEFAULT_TABLE_SIZE = 10;//默认容量
private List[] theLists;//散列表的数组
private int currentSize;//当前数据个数
public HashTable() {
this(DEFAULT_TABLE_SIZE);
}
public HashTable(int size) {
theLists = new LinkedList[nextPrime(size)];
for (int i = 0; i < theLists.length; i++) {
theLists[i] = new LinkedList();
}
}
/**
* 使哈希表变空
*/
public void makeEmpty() {
for (List list : theLists) {
list.clear();
}
currentSize = 0;
}
/**
* 哈希表是否包含某元素
* @param x 查询元素
* @return 查询结果
*/
public boolean contains(AnyType x) {
List whichList = theLists[myhash(x)];
return whichList.contains(x);
}
/**
* 向哈希表中插入某元素,若存在则不操作
* @param x 插入元素
*/
public void insert(AnyType x) {
List whichList = theLists[myhash(x)];
if (!whichList.contains(x)) {
whichList.add(x);
if (++currentSize > theLists.length) {
rehash();
}
} else {
}
}
/**
* 向哈希表中删除某元素,若不存在则不操作
* @param x 删除元素
*/
public void remove(AnyType x) {
List whichList = theLists[myhash(x)];
if (whichList.contains(x)) {
whichList.remove(x);
currentSize--;
} else {
}
}
/**
* 哈希算法,有多种实现方法
* @param x 元素
* @return 哈希值
*/
private int myhash(AnyType x) {
int hashVal = x.hashCode();
hashVal %= theLists.length;
if (hashVal < 0) {
hashVal += theLists.length;
}
return hashVal;
}
/**
* 再散列函数,插入空间不够时执行
*/
private void rehash() {
List[] oldLists = theLists;
// 分配一个两倍大小的空表
theLists = new List[nextPrime(2 * theLists.length)];
for(int j=0;j();
}
currentSize = 0;
for (int i = 0; i < oldLists.length; i++) {
for (AnyType item : oldLists[i]) {
insert(item);
}
}
}
/**
* 检查某整数是否为素数
* @param num 检查整数
* @return 检查结果
*/
private static boolean isPrime(int num) {
if (num == 2 || num == 3) {
return true;
}
if (num == 1 || num % 2 == 0) {
return false;
}
for (int i = 3; i * i <= num; i += 2) {
if (num % i == 0) {
return false;
}
}
return true;
}
/**
* 返回不小于某个整数的素数
* @param num 整数
* @return 下一个素数(可以相等)
*/
private static int nextPrime(int num) {
if (num == 0 || num == 1 || num == 2) {
return 2;
}
if (num % 2 == 0) {
num++;
}
while (!isPrime(num)) {
num += 2;
}
return num;
}
/**
* 输出散列表
*/
public void printTable() {
for(int i=0;i hashTable = new HashTable();
for (int i = 0; i < 30; i++) {
hashTable.insert(random.nextInt(30));
}
hashTable.printTable();
}
}
结果展示:
-----
0 23
-----
1 24
-----
2
-----
26
-----
4
-----
-----
6 29
-----
-----
-----
9
-----
10
-----
11
-----
-----
13
-----
14
-----
-----
16
-----
17
-----
-----
19
-----
20
-----
-----
22
满足结构性和堆序性。
- 结构性
这里插入一个完全二叉树的概念:
完全二叉树是指这样的二叉树:除最后一层外,每一层上的结点数均达到最大值;在最后一层上只缺少右边的若干结点。
堆是一种完全二叉树或者近似完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化。
一个堆结构将由一个(comparable对象的)数组和一个代表当前堆的大小的整数组成。由于是完全二叉树所以这里用数组实现堆的结构。
堆排序
插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。
对于已基本排序的输入项,插入排序是不错的算法。该算法的的效率为O(N^2)
示例如下:
程序实例:
希尔排序的效率主要取决于增量序列的设置。
实例程序:
归并排序以O(N log N)最坏情形时间运行,而所使用的比较次数几乎是最优的。它是一个递归算法一个好的实例。
归并排序程序实例:
本证主要讲述了几种常见的排序,非常重要,需要反复的研读。这里附上大牛的作品http://blog.csdn.net/happy_wu/article/details/51841244