前记
最近又开始重新看数据结构和算法,算是还账吧。看到链表这觉得问题很多,网上的参考有些博主说法也不尽相同,于是刷了几道题找找思路和感觉,用Java总结了一下,毕竟网上大多都是C的(指针这问题感觉好多人也没完全搞懂,包括我)。
链表
首先,链表的存在就是为了解决顺序表不能解决的问题,即顺序表在插入一个节点的时候,在插入点后方的节点都要往后移动一位,因此时间复杂度在最好的情况下就是在最后的节点后面插入,时间复杂度为O(1),最坏情况就是在表头插入,时间复杂度为O(N),因此平均情况就是O(N/2)即O(N)。而链表因为有指针的缘故,在插入节点的时候,只需要调整插入点指针的指向。这里时间复杂度得看插入操作的数量级,比如我们在第n个位置插入操作,时间复杂度为O(n),然后再在n+1的位置插入,这时时间复杂度为O(1),因为n的位置已经知道了。而顺序表,我在第n个位置插入,后面的节点依次要往后移位,我在第n+1个位置插入,后面的节点还是需要移位。也就是说,和顺序表相比,链表更适合于频繁插入的情景。而顺序表的优势则在于访问节点时更快,时间复杂度为O(1),因为顺序表有索引。
而链表在访问某个节点的时候,我需要一个接一个的查找,而不能像顺序表那样有类似索引那样的东西。
链表节点
每个链表都是由若干节点构成:
那一个多个节点构成的单链表则长这个样子:
分为以下几种情况:
在这里要注意的是:每个链表必须有头指针(如上图的指针变量H),但是不一定有头节点,头结点的使用是为了处理一些极端情况,使得首元节点能够像其他节点一样用相同的处理方式。无论链表空还是非空,都必须有头指针。
若链表有头节点,那么头指针指向头节点,头节点指向链表的首元节点,即第一个含有数据域的节点,头节点的数据域一般为空,但有些情况下,头结点的数据域可以用来存放链表的长度等信息。若链表不含有头结点,那么头指针将指向首元结点。
单链表的相关操作
首先我们用一个java类来定义一个链表的节点:
public class Node{
int val = 0;
Node next = null;
public Node(int x){
val = x;
}
}
1.初始化链表
没什么好说的,就好比你要定义一个整型变量 i,需要初始化:int n = 0;链表也一样。
这里我们可以定义一个链表类LinkList,初始化空链表其实就是空链表类的构造方法
public class LinkList {
Node head;//头指针
Node current; //当前节点
int size; // 节点个数
//初始化空链表
public LinkList(){
//初始化一个头结点,让头指针指向头结点。并且让当前节点独享等于头结点
head = current = new Node(0);
size = 0;
head.next = head;
}
}
2.其他操作
/**
*清空链表
*/
public void clearList(){
//将头结点的next结点地址(即单链表的第一个结点的地址)置空,
//则单链表会因缺少引用而被jvm回收,实现清空
head.next = null;
}
/**
* 判断链表是否为空
* @return
*/
public void isEmpty(){
//这个和清空链表道理类似,如果头结点的next节点为空,那么就是一个空链表
return head.next == null;
}
/**
* 返回链表元素个数(链表长度)
* @return int
*/
public int listLength(){
//求链表长度必须要遍历所有元素
//我们可以用当前节点current遍历链表
int i;
//遍历终止条件就是当当前节点的下一节点为null时
for(i = 0;current.next != null ;i++){
current = current.next;
}
return i;
}
/**
* 返回单链表中指定位置元素的值
* @param pos 位置
* @return int
*/
public int getElem(int pos){
//这个和求链表长度类似,但是循环终止条件应为当到达pos位置时
//但是要注意不合法的输入,即pos < 0 或pos > size
if(pos < 0 || pos > size){
System.out.println("非法的输入")
}
//index == 0 则说明是头结点,则返回null
if (index == 0) {
return null;
}
for(int i = 0; i < pos; i++){
current = current.next;
}
return current.data;
}
/**
* 返回单链表中第一个与指定值相同的元素的位置
* @param val 指定值
* @return 位置
*/
public int getPos(int val){
for(int i = 0;current.next != null ;i++){
if(current.data == val){
return i;
}
current = current.next;
}
return 0;//未找到返回0
}
/**
* 向指定位置插入节点
* @param pos 位置
* @param val 元素值
*/
public void insertNode(int pos,int data){
//新插入的节点应指向原先位置的节点,原先位置的前一个节点要指向新插入的节点。最后,别忘了链表长度+1
//图在最下面
Node newNode = new Node(data);
for(int i = 0; i < pos;i++){
current = current.next;
}
newNode.next = current.next;
current.next = newNode;
size ++;
}
/**
* 删除操作 删除第pos个节点
* @param pos
* @return
*/
public void deleteNode(int pos){
for(int i = 0;i < pos;i++){
current = current.next;
}
//
current.next = current.next.next;
size -- ;
}
链表插入图示:
链表删除操作:
未完待续…
有不正确的地方欢迎批评指正~