Java——数据结构
本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
参考教材:零基础学Java
一、链表(LinkedList)
链表中的元素存储在一条链的节点上。删除一个元素时,只需将被删除元素的前一个元素的指针,指向被删除元素的后一个元素,使得被删除的元素脱离链表,就相当于删除了元素。链表不需要像数组那样移动元素,节约了系统资源。
双向链表中,每个元素都拥有两个指针属性,一个是previous指针,一个是next指针。每一个元素的next指针都会指向下一个元素本身,而下一个元素的previous指针都会指向上一个元素本身。如果要删除元素,只需要让被删元素前面元素的next指针,指向被删元素后面的元素本身,再将被删元素后面元素的previous指针,指向被删除元素的前面元素本身即可。
(1)实现对链表的添加
当向链表添加元素时,需要分析多种情况:
①插入到空表:如果链表是一个空表,只需使head指向被插入的节点即可
②插入到头指针后面:若链表中的节点没有顺序,可将新节点插入到head之后,再让插入节点的next指针指向原head指针指向的节点即可
③插入到链表的最后:若链表中的节点没有顺序,可将新节点插入到链表的最后。 这时,需将最后一个节点的next指针指向新节点,再将新节点的next指针设置为NULL即可
④插入到链表的中间:对于有序的链表,需要遍历链表,并对每个节点的关键字进行比较,找到合适的位置,再将新节点插入。这种方式,需要修改插入位置前节点的next指针,使其指向新插入的节点,然后再将新插入节点的next指针设置为指向下一个节点。
例:
import java.util.*;
public class DataStructureJava {
public void add(){
LinkedList List=new LinkedList();
List.add("cxk");
List.add("zst");
List.add("ctr");
Iterator it=List.iterator();
System.out.println("现在添加了如下同学的姓名:");
//通过迭代器对象it来遍历list对象中的元素
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("删除某些同学的姓名后,还剩下谁?");
it.remove();
//创建另一个迭代器对象it1来重新遍历list对象中的元素
Iterator it1=List.iterator();
for(int i=0;i<List.size();i++){
System.out.println(it1.next());
}
}
public static void main(String args[]){
DataStructureJava f=new DataStructureJava();
f.add();
}
}
迭代器中有一个方法:boolean hasNext(),主要用来判断这个链表是否到了结尾,如果到了结尾则返回false,否则返回true。
另一个方法:object next(),主要将链表的指针,指向下一个元素与再下一个元素的中间。也就是说指向某个元素的前面,而不是指向某个元素。系统指针指向集合中第一个元素的前面。当指针指向链表的尾部时,再调用hasNext()方法就会报错。
上面的例子就是因为第一个迭代器在遍历后,已经到了链表的末尾,所以必须再建立一个新的迭代器,重新让指针停留在链表第一个元素的前面。
(2)实现对链表的删除
例:
import java.util.*;
class exampleStudent{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zst");
st1.set(20);
st2.set(19);
try{
LinkedList list1=new LinkedList();
list1.add(st1);
list1.add(st2);
Iterator it=list1.iterator();
System.out.println("以下就是所有的同学的信息:");
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("其中有几个同学已经转学了!");
System.out.println("那么就从数据库中删除他:");
list1.remove();
Iterator it1=list1.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
}catch(Exception e){
}
}
}
(3)常用方法:
void add(int i,object element)将一个元素插入指定的位置
void addall(int i,collection element)将某个数据结构中的所有元素插入到指定的位置中
Object remove(int index)删除并返回指定位置上的元素
Object set(int i,object element)用新元素替换指定位置上的元素并且返回新元素
int indexof(object element)如果没有和element相匹配的元素,则返回 -1;否则返回这个与element相匹配的元素的index值
void add(object element)将一个元素插入到当前位置的前面
void set(object element)用新元素替换最后一次访问的元素
void addFirst(object element)在列表头部插入一个元素
void addLast(object element)在列表尾部插入一个元素
Object getFirst(object element)返回列表头部的元素
Object getLast(object element)返回列表尾部的元素
二、数组列表类(ArrayList)
数组列表类就是数组的一个扩展类,它继承了数组的特点,并且具有自己的特性。数组的容量一旦被初始化就不可以再更改。这对于正在运行的程序来说,是一种缺陷。在程序设计过程中,经常会遇到一些不确定的因素,导致无法确定一个数组的容量。为了解决这个问题,出现了数组列表。数组列表就是一个可以动态变化容量的数组,其根据正在运行的程序的变化,随时改变数组的容量,以满足程序的需要。数组列表中存放的是Object类型,因此在数组列表中存放的对象类型,以最原型的父类代替。如果要提取其中的元素,都要进行类型转换,将元素还原为它本身的类型。由于ArrayList是个可以自动伸缩的数组,所以可以无限制地向数组中添加新元素。另外,因为添加的是Object类的子类类型,所以系统会自动地完成子类向父类的转换。如果能预计一个数组列表中需要存储多少元素,那么可以在填充之前,调用ensureCapacity()方法,使得系统分配一个包含固定含量的内部数组。
数组列表(ArrayList)的构造函数和常用方法:
ArrayList():默认构造函数,系统默认的初始化容量是10
ArrayList(Collection):构建一个数组列表的对象,这个数组列表中包含集合的所有元素
ArrayList(int initCapacity):构建一个指定初始化容量的数组列表对象
void add(int index,Object element):在指定位置插入指定的元素
boolean add(object o):在数组列表的末尾追加一个新元素
boolean contains(object element):测试指定的数组列表中是否包含有指定的元素element,如果包含则返回true,否则false
int size():返回数组列表中包含元素的数量
void set(int index,object obj):设置数组列表指定位置元素的值,它会覆盖原来的内容
Object get(int index):得到指定位置存储的元素值
Object remove(int index):删除指定位置的元素
例:
import java.util.*;
class exampleStudent{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
ArrayList al=new ArrayList();
exampleStudent st=new exampleStudent("cas");
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zst");
st.set(18);
st1.set(20);
st2.set(19);
al.add(st1);
al.add(st2);
System.out.println("这里有"+al.size()+"个学生");
for(int i=0;i<al.size();i++){
System.out.println((exampleStudent)al.get(i));
}
System.out.println("对不起,系统出错了!有个同学信息需要改正");
al.set(1,st);
System.out.println("经过我们的审核后,学生信息如下:");
for(int i=0;i<al.size();i++){
System.out.println((exampleStudent)al.get(i));
}
al.remove(0);
System.out.println("有位同学退学了,剩下学生信息:");
for(int i=0;i<al.size();i++){
System.out.println((exampleStudent)al.get(i));
}
}
}
三、散列表(Hashtable)
在链表和数组列表中,要想查找某个特定的元素,就必须从头开始遍历。如果一个链表或者一个数组列表拥有的元素数量很多,那么就需要耗费大量的系统资源,去遍历整个数据结构。此时,就引进了另一个数据结构:散列表。散列表通过“键-值”对应的形式存储元素。与链表和数组列表不同,它是一个无序的数据结构。无序的数据结构虽然无法控制元素出现的顺序,但它可以快速查找特定的元素。
散列表通过一定的函数关系计算出对应的函数值,以这个值作为存储在散列表中的地址。从这个地址就可以直接获取这个元素,所以散列表对元素的查找非常高效。当散列表中的元素存放满时,就必须再散列,即产生一个新的散列表。所有的元素放到新的散列表中,原先的散列表将被删除。在Java中,通过负载因子来决定何时对散列表进行散列,例如,负载因子是0.75,即散列表中已经有75%的位置被放满,那么将进行再散列。负载因子越接近1,内存使用率越高,元素的寻找时间越长。负载因子越接近0,元素寻找时间越短,内存浪费越多。散列表的缺省负载因子是0.75。
散列表的构造函数:
Hashtable():构建一个空的散列表,初始容量为11.负载因子为0.75
Hashtable(int initalCapacity,float loadFactor):指定初始化容量和负载因子,构造一个散列表
Hashtable(Map t):根据映像所包含的元素,构建一个散列表
散列表的主要方法:
object put(object key,object value):向一个散列表中添加元素。在散列表中根据所添加元素的键值来定位元素,这个键值是唯一的。针对这个方法,要记住这个键值一定是对象型数据
boolean containskey(object key)和boolean containvalue(object value):测试散列表中是否包含指定的键值和值
Object remove(object key):根据指定的键值从散列表中删除对应键的元素
Collection values():返回散列表中所包含元素的集合
例:
import java.util.*;
class exampleStudent{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
Hashtable ht=new Hashtable();
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zst");
st1.set(20);
st2.set(19);
ht.put("a",st1);
ht.put("b",st2);
System.out.println("这里有"+ht.size()+"个学生");
System.out.println(ht.values());
System.out.println("我需要查找一个学生的信息");
if(ht.containsKey("a")){
System.out.println("找到了这个人的信息,如下:");
System.out.println((exampleStudent)ht.get("a"));
}else{
System.out.println("没有找到这个人的信息");
}
ht.remove("b");
System.out.println("有同学退学了,经过我们的审核,学生信息如下:");
System.out.println(ht.values());
}
}
通过以上的程序段,可以发现,散列表不可以通过迭代器来进行访问,因为它是无序的。另外,访问它的时候,最关键的就是键值。散列表主要是通过键值进行一些散列值的计算,计算的结果作为这个对象的地址。所以对于拥有大量数据的数据库来说,使用散列表存储数据,比起使用链表和数组列表会更方便。在散列表中,不允许有两个相同的元素,如有相同的元素,程序会将其作为一个元素来处理。
四、散列集(HashSet)
散列集和散列表这两种数据结构,功能基本相同,不过它们实现的接口不一样。散列表实现的是Map接口,而散列集实现了Set接口。另外散列集是线性同步,而散列集是非线性同步。
散列集实现了Set接口,也就实现了Collection接口,所以它是一个集合。
散列集的构造函数:
HashSet():创建一个空的散列集对象
HashSet(collection c):创建一个包含有collection集合中所有元素的散列集对象
HashSet(int initialCapacity):创建一个初始容量为initialCapacity的散列集对象
HashSet(int initialCapacity,float loadFactor):指定初始化容量和负载因子,构造一个散列表。它的初始容量是16,负载因子是0.75
散列集的常用方法:
boolean add(o):添加一个元素到散列集中。如果这个元素在散列集中不存在,就直接添加它,否则返回false
boolean remove(o):删除一个元素,如果这个元素存在,就删除它,否则返回false
boolean isempty():判断集合是否为空
散列集可以采用迭代器进行遍历,且散列集中不能拥有相同的元素。散列集通过内部散列码计算元素存储地址,这一点与散列表一样,只不过散列集没有键值。
例:
import java.util.*;
class exampleStudent{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
HashSet hs=new HashSet();
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zst");
st1.set(20);
st2.set(19);
hs.add(st1);
hs.add(st2);
System.out.println("这里有"+hs.size()+"个学生");
Iterator it=hs.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
hs.remove(st1);
System.out.println("有同学退学了,经过我们的审核,学生信息如下:");
Iterator it1=hs.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
System.out.println("所有同学都退学了!");
hs.remove(st2);
if(hs.isEmpty()){
System.out.println("所有同学的信息都删除了!");
}else{
System.out.println("系统出错了。");
}
}
}
使用散列集进行数据处理,比使用链表进行数据处理花费的时间更短。这样可以节约系统资源。而且使用散列集进行数据处理,系统花费的时间短,比使用数组列表进行数据处理更快,这样就可以节约系统资源,所以在处理大量数据时,通常使用散列集。
五、树集(TreeSet)
树集是一种有序的数据结构,可以使用各种次序往树集中添加元素。当遍历时,元素出现次序是有序的,不过这种次序由系统自动完成。系统在每添加一个元素时,按照字典顺序比较相应的字符串,按相应顺序来完成排序。
使用树集这种数据结构的对象,必须要实现Comparable接口,只有实现了这个接口的对象,才能调用Comparable接口中的CompareTo()方法来排序。
树集的构造函数:
TreeSet():创建一个空树集对象
TreeSet(collection c):创建一个树集对象,并且指定比较器给它的元素排序
TreeSet(sortedSet element):创建一个树集,并把有序集element的所有元素插入树集中。它使用与有序集element相同的元素比较器
树集的方法函数:
boolean add(object o):添加一个对象元素到树集中
boolean remove(object o):从树集中删除一个对象元素
例:
import java.util.*;
class exampleStudent implements Comparable{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public int compareTo(Object o){
exampleStudent t=(exampleStudent) o;
return(t.age-age);
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
TreeSet ts=new TreeSet();
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zst");
st1.set(20);
st2.set(19);
ts.add(st1);
ts.add(st2);
System.out.println("这里有"+ts.size()+"个学生");
Iterator it=ts.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
ts.remove(st1);
System.out.println("有同学退学了,经过我们的审核,学生信息如下:");
Iterator it1=ts.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
System.out.println("所有同学都退学了!");
ts.remove(st2);
if(ts.isEmpty()){
System.out.println("所有同学的信息都删除了!");
}else{
System.out.println("系统出错了。");
}
}
}
六、映像
集合是一种可以快速找到已经存在的元素的数据结构。但如果数据库中拥有大量的数据,一般不用集合,因为它会耗费系统大量的资源和时间,去遍历整个数据结构。其实散列表就是一种映像。映像是一种采用“键——值”的对应方式存储的数据结构形式。在映像中,除了散列表,还有树映像和散列映像。由于映像不能使用迭代器,所以映像拥有get方法函数。无论是树映像还是散列映像或散列表,它们的使用方法都差不多。
例:
import java.util.*;
class exampleStudent{
String name;
int age;
exampleStudent(String name){
this.name=name;
}
public void set(int age){
this.age=age;
}
public String getname(){
return name;
}
public int getage(){
return age;
}
public String toString(){
String information="姓名:"+name+"年龄:"+age;
return information;
}
}
public class DataStructureJava {
public static void main(String args[]){
TreeMap tm=new TreeMap();
exampleStudent st1=new exampleStudent("cxk");
exampleStudent st2=new exampleStudent("zsx");
st1.set(20);
st2.set(19);
tm.put("a",st1);
tm.put("b",st2);
System.out.println("这里有"+tm.size()+"个学生");
System.out.println(tm.values());
System.out.println("寻找学生:");
if(tm.containsKey("a")){
System.out.println("这个学生存在,他的信息如下:");
System.out.println((exampleStudent)tm.get("a"));
}else{
System.out.println("没有这个人");
}
System.out.println("有同学退学了,经过我们的审核,学生信息如下:");
tm.remove("a");
System.out.println(tm.values());
System.out.println("所有同学都退学了!");
tm.remove("b");
if(tm.isEmpty()){
System.out.println("所有同学的信息都删除了!");
}else{
System.out.println("系统出错了。");
}
}
}
树映像的排序是按照关键字来排序的,与值无关,这一点与树集不同。
七、常见疑难解答
(1)哪些是线程安全的数据结构?
Vector:比ArrayList多了个同步化机制(线程安全)。
Stack:堆栈类,先进后出。
Hashtable:比HashMap多了个线程安全。
Enumeration:枚举,相当于迭代器。
除了这些之外,都是非线程安全的类和接口。线程安全类的方法是同步的,每次只能一个访问,效率低。对于非线程安全的类和接口,在多线程中需要程序员自己处理线程安全问题。
(2)Vector是什么样的数据结构?
目前使用它的频率不高,一般用数组列表代替它,因为它们的使用方法几乎一样,唯独不同的就在线程安全方面。数组列表是非线程安全类,在实现线程编程时,要自己处理安全问题,而Vector则是线程安全类,自动会处理安全问题。Vector类提供实现可增长数组的功能,随着更多元素加入其中,数组变得更大。在删除一些元素之后,数组变小。