咱们都知道,线性表的存储结构分为两种,顺序存储结构和链式存储结构,线性表的分类能够参考下图来学习记忆。今天咱们主要来学习一下链式存储结构。java
"链式存储结构,地址能够连续也能够不连续的存储单元存储数据元素"——来自定义。node
其实,你能够想象这样一个场景,你想找一我的(他的名字叫小谭),因而你首先去问 A , A 说他不知道,可是他说 B 可能知道,并告诉了你 B 在哪里,因而你找到 B ,B 说他不知道,可是他说 C 可能知道,并告诉了你 C 的地址,因而你去找到 C ,C 真的知道小谭在何处。程序员
上面场景其实能够帮助咱们去理解链表,其实每个链表都包含多个节点,节点又包含两个部分,一个是数据域(储存节点含有的信息),一个是指针域(储存下一个节点或者上一个节点的地址),而这个指针域就至关于你去问B,B知道C的地址,这个指针域就是存放的 C 的地址。面试
链表下面其实又细分了3种:单链表、双向链表和循环链表。今天咱们先讲单链表。架构
什么是单链表呢?单链表就是每个节点只有一个指针域的链表。以下图所示,就是一个带头节点的单链表。下面咱们须要知道什么是头指针,头节点和首元节点。学习
头指针:指向链表节点的第一个节点的指针网站
头节点:指在链表的首元节点以前附设的一个节点spa
首元节点:指在链表中存储第一个实际数据元素的节点(好比上图的 a1 节点)3d
单链表的建立有两种方式,分别是头插法和尾插法。指针
头插法,顾名思义就是把新元素插入到头部的位置,每次新加的元素都做为链表的第一个节点。那么头插入法在Java中怎么实现呢。首先咱们须要定义一个节点,以下
public class ListNode {
public int val; //数据域
public ListNode next;//指针域
}复制代码
而后咱们就建立一个头指针(不带头节点)
//元素个数
int n = 5;
//建立一个头指针
ListNode headNode = new ListNode();
//头插入法
headNode= createHead(headNode, n);复制代码
而后建立一个私有方法去实现头插法,这里咱们插入5个新元素,头插入的核心是要先断开首元节点和头指针的链接,也就是须要先将原来首元节点的地址存放到新节点的指针域里,也就是 newNode.next = headNode.next,而后再让头指针指向新的节点 headNode.next = newNode,这两步是头插入的核心,必定要理解。
/**
* 头插法
* 新的节点放在头节点的后面,以前的就放在新节点的后面
* @param headNode 头指针
* @return
*/
private static ListNode createHead(ListNode headNode, int n) {
//插入5个新节点
for (int i = 1; i <= n; i++) {
ListNode newNode = new ListNode();
newNode.val = i;
//将以前的全部节点指向新的节点(也就是新节点指向以前的全部节点)
newNode.next = headNode.next;
//将头指针指向新的节点
headNode.next = newNode;
}
return headNode;
}复制代码
最后我把链表打印输出一下(其实也是单链表的遍历),判断条件就是只有当指针域为空的时候才是最后一个节点。
private static void printLinkedList(ListNode headNode) {
int countNode = 0;
while (headNode.next != null){
countNode++;
System.out.println(headNode.next.val);
headNode = headNode.next;
}
System.out.println("该单链表的节点总数:" +countNode);
}复制代码
最后的输出结果显然是逆序,由于没一个新的元素都是从头部插入的,天然第一个就是最后一个,最后一个就是第一个:
尾插法,顾名思义就是把新元素插入到尾部的位置(也就是最后一个位置),每次新加的元素都做为链表的第最后节点。那么尾插法在 Java 中怎么实现呢,这里仍是采用不带头节点的实现方式,头节点和头指针和头插入的实现方式同样,这里我就直接将如何实现:
/**
* 尾插法
* 找到链表的末尾结点,把新添加的数据做为末尾结点的后续结点
* @param headNode
*/
private static ListNode createByTail(ListNode headNode, int n) {
//让尾指针也指向头指针
ListNode tailNode = headNode;
for (int i = 1; i <= n; i++) {
ListNode newNode = new ListNode();
newNode.val = i;
newNode.next = null;
//插入到链表尾部
tailNode.next = newNode;
//指向新的尾节点,tailer永远存储最后一个节点的地址
tailNode = newNode;
}
return headNode;
}复制代码
和头插入不一样的是,咱们须要声明一个尾指针来辅助咱们实现,最开始,尾指针指向头指针,每插入一个元素,尾指针就后移一下,这里咱们来说一下原理:每次往末尾新加一个节点,咱们就须要把原来的链接断开,那怎么断开呢,咱们首先须要让尾指针指向新的节点,也就是 tailNode.next = newNode; 而后再让尾指针后移一个位置,让尾指针指向最后一个节点。也就是尾指针始终指向最后一个节点,最后将头指针返回,输出最后结果:
既然单链表建立好了,怎么在链表里面删除元素呢,单链表的删除,我分为了两种状况删除,分别是删除第i个节点和删除指定元素的节点。
咱们能够先来理一下思路:在单链表里,节点与节点之间都是经过指针域连接起来的,因此若是咱们想实现删除的操做,其实是须要咱们去改变相应指针域对应得地址的。当想去删除第i个元素的时候,好比要删除上图的第3个元素(也就是3),实际上咱们要作的就是要让2号元素指向4号元素(其实就是须要修改2号元素的指针域,让2号元素的指针域存储4号元素)。那么怎么作才能实现这一步呢?很显然,要实现这个步骤,咱们必需要找到4号元素和2号元素,可是再仔细想一下,其实咱们只须要找到2号元素就能够了,由于4号元素的地址存储再2号的下一个元素的指针域里面。
因此综上所述分析咱们能够得出删除的两个核心步骤:
1.删除第i个节点,须要先找到第 i-1 个个节点,也就是第i个节点的前一个节点;
2.而后让第 i-1 个节点指向第 i-1 个节点的下下个节点
下面的代码具体实现了怎么删除第i个元素。
/**
* 删除第i个节点
* 1,2 4,4,5
* 删除以后应该是1,2,4,5
* @param headNode
* @param index
* @return
*/
public static ListNode deleteNodeByIndex(ListNode headNode, int index) {
int count = 1;
//将引用给它
ListNode preNode = headNode;
//看计数器是否是到了i-1,若是到了i-1,就找到了第i-1个节点
while (preNode.next != null && count <= index -1){
//寻找要删除的当前节点的前一个节点
count++;
preNode = preNode.next;
}
if (preNode != null){
preNode.next = preNode.next.next;
}
return headNode;
}复制代码
删除指定元素节点的实现方法有两种,第一种就是先找到指定元素对应的链表的位置( index ),而后再按照删除第 i 个节点的思路删除便可。实现方法以下图所示:
/**
* 删除链表指定数值的节点
* @param headNode
* @param val
* @return
*/
private static ListNode deleteNodeByNum(ListNode headNode, int val) {
ListNode deleteOne = headNode;
int countByDeleteOne = 1;
while (deleteOne.next != null){
if (deleteOne.next.val == val){
deleteOne = deleteOne.next;
break;
}
countByDeleteOne ++;
deleteOne = deleteOne.next;
}
return deleteNodeByIndex(headNode, countByDeleteOne);
}复制代码
第二种方法的实现就很精妙(前提是此节点不是尾节点)
public void deleteNode(ListNode node) {
//删除node即经过将后面的值赋给node,而后更改node的指针指向下下一个结点便可
node.val = node.next.val;
node.next = node.next.next;
}复制代码
单链表的查询实现很简单,就是遍历当前单链表,而后用一个计数器累加到当前下标,那么当前的这个节点就是要查询的那个节点,而后再返回便可,固然须要判断传过来的这个下标是否合法。固然若是须要修改,就须要把当前找到的节点的数据域从新赋上须要修改的值便可,这里就不上代码了。具体实现以下:
private static ListNode searchLinkedList(ListNode headNode, int index) {
//若是下标是不合法的下标就表示找不到
if (index < 1 || index > getLinkedListLength(headNode)){
return null;
}
for (int i = 0; i < index; i++) {
headNode = headNode.next;
}
return headNode;
}复制代码
获取单链表的长度(注意我这里定义的 headNode 是头指针不是头节点)
/**
* 求单链表长度
* @param headNode
* @return
*/
private static int getLinkedListLength(ListNode headNode) {
int countNode = 0;
while (headNode.next != null){
countNode++;
headNode = headNode.next;
}
return countNode;
}复制代码
单链表的相关操做就讲解完了,其实经过上面对单链表的相关操做,咱们不难发现,单链表的删除和插入其实很方便,只须要改变指针的指向就能够完成,可是查找元素的时候就比较麻烦,由于在查找的时候,须要把整个链表从头至尾遍历一次。
最后,最近不少小伙伴找我要Linux学习路线图,因而我根据本身的经验,利用业余时间熬夜肝了一个月,整理了一份电子书。不管你是面试仍是自我提高,相信都会对你有帮助!目录以下:
免费送给你们,只求你们金指给我点个赞!
也但愿有小伙伴能加入我,把这份电子书作得更完美!
推荐阅读: