线性表的链式存储结构的特点是用一组任意的存储单元存储线性表中的数据元素,这组存储单元可以是连续的,也可以是不连续的。这也就意味着这些数据可以存在内存未被占用的任意位置。在链式存储结构中由于存在以上特性,所以它除了要存数据元素信息外,还要存储它的后继元素的存储地址。
因此,为了表示每个数据元素a[i]与其直接后继数据元素a[i+1]之间的逻辑,对数据元素a[i]来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储元素信息的域称为数据域,把存储直接后继的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素a[i]的存储映像,称为结点(Node)。而n个结点(a[i]的存储映像)链结成一个链表,即为线性表(a[1],a[2],...,a[n])的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。
下面来看看链式存储结构的插入和删除操作的图解:
在图(a)在要注意的是操作顺序,一定要是先将插入结点c的后继先指向结点b,然后再将结点a的后继指向结点c,否则,先将结点a指向后,c再指向b时,现在的a->next就已经不再是曾经的a->next了(以前的a->next指向b,现在的a->next指向c),从而造成错误。
由上面的单链表的一些性质很容易知道单链表的优缺点:
优点:
1)链式存储时,相邻数据元素可随意存放,不会造成空间的“碎片”。
2)插入和删除的操作速度快,时间复杂度低。
3)不需要想顺序存储结构那样担心存储空间的容量
缺点:
1)查找元素的时间复杂度相比顺序存储结构而言要高。
2)需要单独使用空间来表示元素间的逻辑关系。
下面我们来看看单链表的实现方法:
其中含有根据元素获取索引、根据索引查找元素、插入、删除、清空等操作。
单链表LinkList
package LinkList;
/**
* Created by jiangxs on 17-5-21.
*/
public class LinkList<T> {
//定义一个内部类Node代表链表的节点
private class Node{
private T data;//保存数据
private Node next;//指向下一个节点的引用
//无参构造器
public Node(){}
//初始化全部属性的构造器
public Node(T data,Node next){
this.data = data;
this.next = next;
}
}
private Node header;//保存头结点
private Node tail;//保存尾节点
private int size;//保存已含有的结点数
//创建空链表
public LinkList(){}
//返回链表长度
public int getSize(){
return size;
}
//获取指定位置的结点
public Node getNodeByIndex(int index){
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("获取位置超过了链表长度范围");
Node current = header;//从链表表头开始遍历
for (int i = 0;i<size && current != null;i++,current = current.next)
if (i == index)
return current;
return null;
}
//获取指定索引处的元素
public T getElement(int index){
return this.getNodeByIndex(index).data;
}
//按值查找所在位置
public int getIndex(T element){
Node current = header;
for (int i = 0;i < size && current != null;i++,current = current.next)
if (current.data.equals(element))
return i;
return -1;
}
//在尾部插入元素
public void add(T element){
//如果链表为空
if (header == null){
header = new Node(element, null);
tail = header;//空链表中头尾结点指向同一个
}
else {
Node newNode = new Node(element,null);//创建新结点
tail.next = newNode;//尾结点的next指向新结点
tail = newNode;//将新结点作为尾结点
}
size++;
}
//在头部插入
public void addHeader(T element){
//创建新结点,并让新结点指向header
Node newNode = new Node(element,null);
//然后让新结点作为header
header = newNode;
//如果链表为空
if (tail == null)
tail = header;
size++;
}
/**
* 在指定位置插入元素
* @param element 要插入的元素
* @param index 要插入的位置
*/
public void insert(T element,int index){
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("插入位置超出链表范围");
//如果链表为空
if (header == null)
add(element);
else {
if (index == 0)//如果插入位置为0
addHeader(element);
else {
//获取插入位置的前一个结点
Node prev = getNodeByIndex(index-1);
//让prev指向新的结点,新结点指向原prev结点的下一个结点
prev.next = new Node(element,prev.next);
size++;
}
}
}
/**
* 删除索引处的元素
* @param index
* 输入要删除的位置
*/
public T delete(int index){
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("删除位置超出链表范围");
Node del = null;
//如果删除的是头结点
if (index == 0){
del = header;
header = header.next;
del.next = null;
}
else {
Node prev = getNodeByIndex(index-1);
del = prev.next;
prev.next = del.next;
del.next = null;
}
size--;
return del.data;
}
//删除最后一个元素
public T remove(){
return delete(size-1);
}
//判断链表是否为空
public boolean isEmpty(){
return size == 0;
}
//清空线性表
public void clear(){
//将头结点和尾结点设为空
header = null;
tail = null;
size = 0;
}
public String toString(){
if (isEmpty())
return "[]";
else {
StringBuilder sb = new StringBuilder("[");
for (Node current = header;current != null;current = current.next)
sb.append(current.data+"->").toString();
int len = sb.length();
return sb.delete(len-2,len).append("]").toString();
}
}
}
测试代码:
package LinkList;
/**
* Created by jiangxs on 17-5-21.
*/
public class LinkListDemo {
public static void main(String[] args) {
LinkList<String> ll = new LinkList<String>();
ll.add("haha");
ll.add("hehe");
ll.add("xixi");
System.out.println("添加元素后的顺序线性表为: "+ll);
ll.insert("heihei",2);
System.out.println("在线性表的位置2插入元素: "+ll);
ll.delete(2);
System.out.println("删除线性表中位置2的元素: "+ll);
ll.remove();
System.out.println("删除线性表中的一个元素: "+ll);
System.out.println("获得线性表位置1处的元素"+ll.getElement(1));
System.out.println("获取元素hehe所在位置: "+ll.getIndex("hehe"));
//清空线性表
ll.clear();
System.out.println("清空线性表");
System.out.println("清空后线性表是否为空: "+ll.isEmpty());
}
}
测试结果:
添加元素后的顺序线性表为: [haha->hehe->xixi]
在线性表的位置2插入元素: [haha->hehe->heihei->xixi]
删除线性表中位置2的元素: [haha->hehe->xixi]
删除线性表中的一个元素: [haha->hehe]
获得线性表位置1处的元素hehe
获取元素hehe所在位置: 1
清空线性表
清空后线性表是否为空: true
Process finished with exit code 0
参考:《大话数据结构》