文章目录
- 前言
- 一、Java的链表是怎么构造的?
- 单链表的构造
- 简介:
- 构造链表:
- 链表增加元素,首部,中间和尾部分别会有什么问题,需要怎么处理?
- 链表的新增
- 在头部添加元素:
- 在中间添加元素:
- 在尾部添加元素:
- 链表的新增元素:
- 链表删除元素,首部,中间和尾部分别会有什么问题,需要怎么处理?
- 链表的删除
- 在头部删除元素
- 在中间删除元素
- 在尾部删除元素
- 链表的删除元素:
- 双链表是如何构造的,如何实现元素的插入和删除?
- 总结
前言
提示:努力把算法搞通透:
一、Java的链表是怎么构造的?
单链表的构造
简介:
我的回答:
链表的主要结构:数据和下一个节点的指针
public class Node {
public int val;
public Node next;
public Node(int data){
this.val = data;
this.next = null;
}
}
🐟聪明的回答:
链表是一种常见的数据结构,用于存储和组织数据。它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。
链表中的节点可以分为两类:头节点和普通节点。头节点是链表的起始节点,它通常不包含实际的数据,而只是用来指示链表的开头。普通节点用于存储实际的数据。
链表中的节点是通过指针连接在一起的。每个节点的指针指向下一个节点,从而组成了节点之间的链式关系。这种链式结构使得我们可以在链表中高效地插入、删除和查找节点。
相对于数组等连续存储的数据结构,链表的优点是在插入和删除节点时具有更好的灵活性。由于链表中的节点不需要连续的内存空间,我们可以很容易地在任意位置插入或删除节点,而不需要移动其他节点。
然而,链表的缺点是对于随机访问节点的效率较低。由于链表中的节点不是连续存储的,我们需要从头节点开始沿着链表遍历,直到找到目标节点。因此,链表在需要频繁随机访问节点的情况下可能不是最佳选择。
常见的链表类型包括单链表、双向链表和循环链表。在单链表中,每个节点只包含指向下一个节点的指针。双向链表中,每个节点不仅包含指向下一个节点的指针,还包含指向前一个节点的指针。循环链表是一种特殊的链表,其中最后一个节点的指针指向第一个节点,形成一个循环。
链表在计算机科学中被广泛应用,特别是在动态数据结构的实现中。它们可以用于构建堆栈、队列和哈希表等高级数据结构,并用于解决各种实际问题。
构造链表:
private static Node initLinkedList(int[] array) {
Node head = null, cur = null;
for (int i = 0; i < array.length; i++) {
Node newNode = new Node(array[i]);
// 为什么?
newNode.next = null;
if (i == 0) {
// 表示头结点
head = newNode;
cur = newNode;
} else {
// 非头节点的下一个节点(注意:这里是cur节点的下一个)
cur.next = newNode;
cur = newNode;
}
}
return head;
}
链表增加元素,首部,中间和尾部分别会有什么问题,需要怎么处理?
链表的新增
一个很重要的方法(获取链表长度)
/**
* 获取链表长度
*
* @param head 链表头节点
* @return 链表长度
*/
public static int getLength(Node head) {
int count = 0;
// 一般情况下保留头节点不丢
Node node = head;
// 注意不是if 是while(if只执行一次)
while (node != null) {
node = node.next;
count++;
}
return count;
}
在头部添加元素:
head.next = head; (转移下一个头节点就行)【当然前提需要head != null】
在中间添加元素:
找到目标节点的前一个??
targetNode
tatgetNode.next = cur.next;
cur.next = targetNode; [顺序不能颠倒]
注意:不要这样去写 链表容易断开。
cur.next = targetNode
在尾部添加元素:
找到最后一个的前一个??
和头节点的方式一致
cur.next = null;[这样就保证最后一个元素不可达,也就失去意义->最终JVM被回收掉]
链表的新增元素:
/**
* 链表插入
*
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 待插入位置,取值从2开始
* @return 插入后得到的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
// 考虑这三个参数的设计
// 首先当然要考虑 头节点为空的情况
if(head == null){
return nodeInsert; //直接返回要插入的节点
}
// 考虑边界问题
int length = getLength(head);
if (position > length + 1 || position < 1){
throw new IllegalArgumentException("Invalid position 非法参数异常");
}
// 考虑只有一个节点的情况/ 头节点非空
// 且插入第一个节点
// 注意:保留头节点的
if (position == 1){
nodeInsert.next = head;
// node.next = nodeInsert;// 这里位置搞错了
// 保留原来的头节点形式
head = nodeInsert;
return head;
}
// 接下来考虑 在中间的问题了
// 要找到目标节点的上一个
Node pnode = head;
int count = 1;//记住从 1 开始
while(count < position -1){
pnode.next = pnode;
count++;
}
// 此时node (cur = 目标节点的上一个)
// 不要这样写
// node.next = nodeInsert.next;// 此时链表已经断了
nodeInsert.next = pnode.next;
pnode.next = nodeInsert;
return head;
}
链表删除元素,首部,中间和尾部分别会有什么问题,需要怎么处理?
链表的删除
在头部删除元素
这个很简单-只需要将头节点换一下就行了
head = head.next; [当然还要判断链表是否为空🤩]
在中间删除元素
提示:todo:
在尾部删除元素
这个也是找到最后节点的前一个节点
让前一个节点指向null就行了【断链的基本操作😂】
cur.next = null;
链表的删除元素:
/**
* 删除节点
*
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
// 校验参数
// 确定 head 为空的特例
if (head == null){
return null;
}
// 判断边界问题
int length = getLength(head);
// position < 1 不行 =1 成立
if (position > length || position <= 0){
throw new IllegalArgumentException("Invalid position 非法参数异常");
}
// 如果删除的位置是头节点的话
// 这个比较特殊 也容易才处理
int count = 1;
if(position == 1){// 记住从 1 开始
return head.next;// 直接返回head的下一个节点
}
// else {
// // 在中间的话
// // 因为链表不可逆 我们要操作两个节点
// // 即跳过要删除的节点
// Node preNode = head;
// while(count < position - 1){
// preNode = preNode.next;
// count++;
// }
// Node curNode = preNode.next;
// preNode.next = curNode.next;
// }
Node preNode = head; // 类似保留头节点 搞一个前驱【毕竟链表不可逆】
while(count < position){
preNode = preNode.next; //
count++;
Node curNode = preNode.next;
preNode.next = curNode.next;// 跳过一个节点
}
return head;
}
双链表是如何构造的,如何实现元素的插入和删除?
提示:todo:
总结
提示:todo: