链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。
使用链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表的基本单位是节点(Node),每个节点有数据域和指针域,数据域存储数据,指针域存储对下一个节点的引用。
链表根据特点又分为单向链表、双向链表、循环链表。
单链表是最简单的链表,只有一个数据域和一个指针域。
建立链表需要有一个Node节点作为基础单位。
class Node{//结点
private Object data;//每个节点的数据
private Node next;//指向下一个节点
public Node(Object data){
this.data = data;
}
}
加入一些基本的属性,方法就可以构成一个单链表。
public class SingleLinkList {
private class Node{//结点
private Object data;//每个节点的数据
private Node next;//每个节点指向下一个节点的连接
public Node(Object data){
this.data = data;
}
}
private int size;//链表长度
private Node head;//头结点
public SingleLinkList() {//初始化
size=0;
head=null;
}
public int getSize() {//实际长度
return size;
}
public boolean isEmpty(){//判空
return this.getSize()==0;
}
private boolean isError(int index){//索引异常处理
if(isEmpty()){
System.out.println("空表");
return true;
}
if(index<=0|index>=getSize()){
System.out.println("索引出错");
return true;
}
return false;
}
加入元素可以从头部加入,也可以从尾部加入。
public boolean addHead(Object obj) {//头插法增加元素
Node node=new Node(obj);
if(this.getSize()==0) {
head=node;
}else {
node.next=head;
head=node;
}
size++;
System.out.println("从头部添加成功");
return true;
}
public boolean add(Object obj){//尾插法
Node node=new Node(obj);
if(this.getSize()==0) {
head=node;
}else {
Node p=head;
while(p.next!=null){
p=p.next;
}
p.next=node;
}
size++;
System.out.println("从尾部添加成功");
return true;
}
通过索引插入元素。
public void insert(int index,Object e){//插入元素
if(this.isError(index)){
return ;
}
Node p=head;
for(int i=1;i<index-1;i++){
p=p.next;
}
Node f=new Node(e);
Node s=p.next;
f.next=s;
p.next=f;
size++;
}
get方法通过索引得到链表中的元素, contain方法查询元素是否在表中,若在,返回索引,不在,返回-1。
public int contain(Object e){//查询元素是否在表中,若在,返回索引,不在,返回-1
if(isEmpty()){
System.out.println("空表");
return -1;
}
Node p=head;
for(int i=1;i<=getSize();i++){
if(e==p.data){
return i;
}
p=p.next;
}
return -1;
}
public Object get(int index){//按照索引得到元素
if(this.isError(index)){
return null;
}
Node p=head;
for(int i=1;i<index;i++){
p=p.next;
}
return p.data;
}
}
通过索引删除元素。
public void remove(int index){//删除元素del
if(this.isError(index)){
return ;
}
if(index==1){
removeHead();
return ;
}
Node p=head;
for(int i=1;i<index-1;i++){
p=p.next;
}
p.next=(p.next).next;
size--;
}
public void removeHead(){
head=head.next;
size--;
}
修改元素,或者说更新元素。
public void replace(int index ,Object e){//修改元素update,set
if(this.isError(index)){
return ;
}
Node p=head;
for(int i=1;i<index;i++){
p=p.next;
}
p.data=e;
}
遍历元素。
public void display(){//遍历
if(this.isEmpty()){
System.out.println("空表");
return ;
}
System.out.println("开始遍历数据:");
Node p=head;
while(p!=null){
System.out.print(" "+p.data);
p=p.next;
}
System.out.println("\n遍历结束");
}
以上链表提供了基本的增删改查功能,接下来测试一下。
因为本链表的数据类型被定义为Object
类型,所以可以装载各种数据,包括对象。
先测试基本的增加元素和遍历功能。
public static void main(String[] args) {
SingleLinkList s=new SingleLinkList();
s.addHead("ok");
s.addHead("world");
s.addHead("Hello");
s.addHead(1);
s.add("yes");
s.add("no");
People p=new People("Tom",16);//一个对象
s.add(p);
s.display();
}
如下,所有数据正常。
从头部添加成功
从头部添加成功
从头部添加成功
从头部添加成功
从尾部添加成功
从尾部添加成功
从尾部添加成功
开始遍历数据:
1 Hello world ok yes no { name='Tom', age='16'}
遍历结束
在以上数据下继续测试:
int index=2;
Object o=s.get(index);
System.out.println("第"+index+"号元素是"+o);
Object e="yes";
Object i=s.contain(e);
System.out.println(e+"在容器中是第"+i+"号元素");
Object e1="full";
s.insert(index,e1);
System.out.printf("在%d处插入数据%s\n",index,e1);
s.display();
s.remove(index);
System.out.println("移除数据"+index);
s.display();
s.replace(index, "go");
System.out.println("修改数据"+index);
s.display();
结果如下:
第2号元素是Hello
yes在容器中是第5号元素
在2处插入数据full
开始遍历数据:
1 full Hello world ok yes no { name='Tom', age='16'}
遍历结束
移除数据2
开始遍历数据:
1 Hello world ok yes no { name='Tom', age='16'}
遍历结束
修改数据2
开始遍历数据:
1 go world ok yes no { name='Tom', age='16'}
遍历结束
理解了单链表基本也就理解了链表,双向链表相比单向链表就多了一个指针域,用于指向前一个节点。
class Node{//结点
private Object data;//每个节点的数据
private Node next;//指向后一个节点
private Node prev;//指向前一个节点
public Node(Object data){
this.data = data;
}
}
链表的尾节点如果可以指向头节点,那么这个链表就是循环链表。
双向循环链表由于可以从任意节点索引到整个链表的数据,兼具其他链表的优点,经常被计算机语言所采用。
如java中内置的集合类LinkedList.
关于循环链表和双向链表可以仿照单向链表自己实现一下,在具体细节的处理上有些许差别。
数据结构与算法学习目录