Java——数据结构

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_45613931/article/details/101905361


本人是个新手,写下博客用于自我复习、自我总结。
如有错误之处,请各位大佬指出。
参考教材:零基础学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类提供实现可增长数组的功能,随着更多元素加入其中,数组变得更大。在删除一些元素之后,数组变小。

猜你喜欢

转载自blog.csdn.net/qq_45613931/article/details/101905361