链表可能是继数组之后第二种使用最为广泛的数据结构。
链表有单链表、双端链表、有序链表、双向链表。本文讲的就是链表中的基本即单链表,来了解链表的基本操作和概念。
通过下图简单了解链表的概念:
其特点举个简单例子,如图把33这个元素插入在42之前,数组实现的话要把42开始之后为元素全部后移一位,然后把33插到指定位置,然而链表则可以随便的把他分配在某一个位置,然后我们通过修改指针关系,把散落在各个位置的单元串联起来,把原来指向42的指针指向33,并把33的下一个指针指向42仅仅这2步动作就完成了把33这个新单元加入到我们的链表中,并且后面这些元素7 98等根本没有感受到这个插入动作,对他们没有任何影响。
这样内存的分配比较灵活,可以随意的分配。
删除的时候同理。要把33这个单元删除,只要把指向它的那个指针再指向42就可以了,其他的什么都不需要动,注意此时的33单元没有任何指针指向它,现在它变成了垃圾对象,将来会被系统回收。然而单链表的随机访问很慢,要从head找到第一个元素然后找找找。。
还有两个概念即链结点和链表类;
上图中的每一项都是链结点,一个链结点是某个类的对象,这个类可以叫做Link。因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点。每个Link对象中都包含一个对下一个链结点引用的字段(通常叫做next),但是链表本身的对象中有一个字段指向对第一个链结点的引用。
链表:LinkList类中只包含一个数据项:即对链表中第一个链结点的引用,叫做head。他是唯一的链表需要维护的永久信息,用以定位所有其他的链结点。从head出发,沿着链表通过每个链结点的next字段,就可以找到其他的链结点。
实现的代码如下:
public class YezhuLinkedList {
public Link head = null;
public boolean isEmpty() {
return head == null;
}
/**
* insertFirst() 作用是在表头插入一个新链结点。实际上,这是最容
* 易插入的一个链结点。具体操作时,新创建的链结点的next指向原来
* head指向的值,然后head指向新插入的链结点。
*/
public void insertFirst(int a) {
Link link = new Link(a);
link.next = head; //新插入的元素 指向原来head指向的链接点
head = link;//head指向新插入的元素
}
/**
* 展示全部的结点数据
*/
public void showAll() {
//从head开始遍历 到最后一个元素的时候 指向的肯定是空
Link current = head;
while (current != null) {
System.out.println(current.data);
current = current.next;
}
}
/**
* 在尾部插入元素
* 从头开始一直遍历 遍历到尾部 让之前Link的next指向新的结点
*/
public void insertLast(int data) {
Link link = new Link(data);
if (head == null) {
link.next = head;
head = link;
} else {
Link current = head;
while (current.next != null) {
current = current.next;
}
current.next = link;
}
}
/**
* 删除头元素
*/
public void deleteFirst() {
Link current = head;
head = current.next;
}
/**
* 长度
*
* @return
*/
public int length() {
int length = 0;
Link link = head;
while (link != null) {
length++;
link = link.next;
}
return length;
}
/**
* 根据元素的关键字 找到其结点所在的位置
*
* @param data
* @return
*/
public int getPosByValue(int data) {
Link current = head;
int position = 0;
if (!contains(data)) {
return -1;
} else {
while (current.data != data) {
position++;
if (current.next == null) {
return position;
} else {
current = current.next;
}
}
return position;
}
}
/**
* 整个链表中是否包含关键字为data的链结点
*
* @param data
* @return
*/
public boolean contains(int data) {
Link current = head;
int position = 0;
while (current.data != data && current.next != null) {
//判断条件加上current.next != null 是为了队尾的时候的空指针
position++;
current = current.next;
}
//遍历到链表的尾部 要是position到了尾部 说明没有包含此元素
if (position == length() - 1) {
return false;
} else {
return true;
}
}
}
测试代码:
/**
* 测试
*/
public class Test {
public static void main(String[] args) {
YezhuLinkedList yezhuLinkedList = new YezhuLinkedList();
yezhuLinkedList.insertFirst(1);
yezhuLinkedList.insertFirst(5);
yezhuLinkedList.insertLast(8);
yezhuLinkedList.insertFirst(3);
yezhuLinkedList.showAll();
//测试是否包含关键字是data的链结点,有则输出位置
int data = 10;
if (yezhuLinkedList.contains(data)) {
System.out.println("包含此元素,在链表中的位置是:" + yezhuLinkedList.getPosByValue(data));
} else {
System.out.println("不包含此元素");
}
// yezhuLinkedList.deleteFirst();
System.out.println("链表长度是" + yezhuLinkedList.length());
}
}
输出结果:
其过程中我们可以看到一个这样的结构,能更清楚的了解链表的属性结构:
显然就是从head链结点一个指向一个的展开。
如上代码如有问题请多多指出。
关于链结点补充:
1.链结点中的数据域可能包含很多的数据项,比如一个个人记录可能有姓名、地址、社保号、头衔等许多字段。通常用一个包含这些数据的类的对象来代替这些数据项。
2.可以让每个链表的链结点是一个小型的数组,比如我们一个数组要表达十万个这样的数据,我们可以把每一百或一千个这样的数组做成一个小单元,把这样的小单元用链表串
联起来,那么它既有数组的结构又有链表的优点,也就是数据结构灵活的综合的应用。
也就是说真实的链表在实际使用过程中有很多的变种,一定要灵活的选择合适的机构。