在程序设计中,我们常常使用数组描述线性表,但是很多时候我们并不知道线性表的长度,除此之外数组在不断删除之后,数组的某些元素是无用的而占据着空间,因此我们尝试动态申请空间,当需要时申请空间,当不需要时将其删除,这就是链表。在链表描述的线性表中,元素在内存中的存储位置是随机的。每个元素都有一个明确的指针指向线性表的下一个位置,下面通过java来讲讲链表的构建和操作。
链表分类有单向链表,双向链表,循环链表,下面我们就使用单向链表来说明如何建立一个链表。链表是由节点组成的,每一个节点至少含有一个指针指向下一个节点的位置,另外节点还可以选择的含有域,这些域的数量根据需要来确定,所谓的域就相当于存在数组的元素,域的属性可以基本数据类型也可以是一个对象。下面是一个节点的定义,重载多个构造有利于链表的操作。
package Structure;
public class Node { //定义节点
Object element; //节点元素
Node next; //节点指针
//无参构造函数
public Node(){
element=null;
next=null;
}
//单值构造函数,用于末尾插入
public Node(Object theElement){
element=theElement;
next=null;
}
//双值构造函数,用于中间插入。
public Node(Object theElement,Node node){
element=theElement;
next=node;
}
}
链表的操作可以根据需要添加想要的函数,下面就从构造函数,插入,删除,打印和链表大小添加链表操作。链表都必需含有一个根节点,通过这个根节点可以访问链表的每一个节点,当删除这个根节点,在java中将回收整个链表的空间,因此声明一个根节点私有成员root。
构造函数可以建立一个空的链表,根据需要可以添加其它的构造函数,这里添加一个复制构造函数,用于从一个已有链表构造一个完全相同的链表。
建立链表完成后,就要向里面添加节点了,这里声明了从链表头,链表尾,链表中间插入三个插入函数,其中单向链表在操作过程中需要保持链表的末尾是指向null,这样有利于判断链表是否结束。从链表头插入可以直接新建一个节点,然后节点指向根节点,然后再将根节点指向新建的节点。从链表尾插入,需要新建一个节点,然后这个节点指向null,表示这是链表的结尾,然后再从根节点找到当前链表的末尾节点,让这个节点指向新建的节点。从中间插入,需要知道插入的位置,从根节点找到插入的位置后,新建一个节点,然后将这个节点插入到中间,插入时前一个节点指向新建节点,新建节点指向下一节点。
删除操作这里只声明一个从中间删除函数,但是这个函数足以完成对链表的删除,删除根节点只需要将根节点指向第二个节点,删除中间的节点需要将删除前一个节点指向删除节点的下一个节点。当删除节点是最后一个节点时将倒数第二个节点指向null。
打印链表可以根据需要改写toString函数,这里输出整个链表的所有节点内容。
链表的大小可以从根节点出发迭代到链表的结尾即可得到链表的大小。
下面是链表的java代码:
package Structure;
public class LinkList {
private Node root;//根节点
//构造函数
public LinkList(){
root =null;
}
//复制构造函数
public LinkList(LinkList list){
Node node =list.getRoot();
if(node==null)
root=null;
else{
root = new Node(node.element);
Node p=root;
while(node.next!=null){
node=node.next;
p.next=new Node(node.element);
p=p.next;
}
}
}
//末尾插入函数
public void insertOnEnd(Object theElement){
if(root==null)
root=new Node(theElement);
else{
Node node=root;
while(node.next!=null){
node=node.next;
}
node.next=new Node(theElement);
}
}
//开始插入函数
public void insertOnStart(Object theElement){
Node node =new Node(theElement,root);
root =node;
}
//中间插入函数
public boolean insertOnMiddle(int index,Object theElement){
if(index<0)
return false;
else{
if(root==null)
if(index==0){
root=new Node(theElement);
return true;
}else
return false;
else{
Node node=root;
Node p=null;
while(index!=0&&node.next!=null){
p=node;
node=node.next;
index--;
}
if(index==0){
p.next=new Node(theElement,node);
return true;
}else
return false;
}
}
}
//取索引为index的元素
public Object getIndex(int index){
if(index<0||root==null)
return null;
else{
Node node=root;
while(node.next!=null&&index!=0){
node=node.next;
index--;
}
if(index==0)
return node.element;
else
return null;
}
}
//删除函数
public boolean deleteOnMiddle(int index){
if(index<0||root==null)
return false;
else{
if(index==0){
root=root.next;
return true;
}else{
Node node=root;
Node p=null;
while(index!=0&&node.next!=null){
p=node;
node=node.next;
index--;
}
if(index==0){
p.next=node.next;
node=null;
return true;
}else
return false;
}
}
}
//链表的长度
public int size(){
if(root==null)
return 0;
else{
Node node=root;
int size=1;
while(node.next!=null){
node=node.next;
size++;
}
return size;
}
}
//打印链表内容
public String toString(){
String content="链表的内容为:";
if (root==null)
;
else{
content+=root.element+" ";
Node node=root;
while(node.next!=null){
node=node.next;
content+=node.element+" ";
}
}
return content;
}
//返回根节点
public Node getRoot(){
return root;
}
}
下面是测试代码:
package Test;
import Structure.LinkList;
public class LinkListTest {
public static void main(String args[]){
LinkList list=new LinkList();
list.insertOnEnd(1);
list.insertOnEnd(2);
list.insertOnEnd(4);
System.out.println(list);
list.insertOnMiddle(1, 3);
list.insertOnStart(0);
System.out.println(list);
list.deleteOnMiddle(4);
System.out.println(list);
System.out.println("链表长度为:"+list.size());
System.out.println("索引为2的元素为:"+list.getIndex(2));
LinkList list1=new LinkList(list);
System.out.println("使用复制构造函数的"+list1);
}
}
输出:
链表的内容为:1 2 4
链表的内容为:0 1 3 2 4
链表的内容为:0 1 3 2
链表长度为:4
索引为2的元素为:3
使用复制构造函数的链表的内容为:0 1 3 2
链表在实际编程中很实用,其中它在库函数中有一个现成的类LinkedList,它的方法很齐全。理解链表很重要,它不仅在编程中有许多运用,是和数组是常常使用的存储线性表;它也能帮助理解更多的数据结构,比如树。