《算法通关村第一关——链表青铜挑战笔记》Java
链表的相关概念
节点和头节点
链表中,每个节点由值和指向下一个节点的地址组成,也称为结点。
第一个节点称作头结点。知道了头结点就可以遍历整个链表。
虚拟节点
其实就是一个节点dummyNode,其next指针指向head。在算法中要获取head节点,或者从方法(函数)里返回的时候,则应使用dummyNode。
另外注意,dummmyNode的值不会被使用,可以初始化为0或-1,它的作用只是用来处理首部节点。
JVM是如何构造链表的
public class BasicLink {
public static void main(String[] args) {
int[] a = {1, 2, 3, 4, 5, 6};
Node head = initLinkedList(a);
System.out.println(head);
}
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.next = newNode;
cur = newNode;
}
}
return head;
}
static class Node {
public int val;
public Node next;
Node(int x) {
val = x;
next = null;
}
}
}
链表的增加和删除
/**
* 构造链表,使用静态内部类定表示结点,实现增加和删除元素的功能
*
*/
public class BasicLinkList {
static class Node {
final int data;
Node next;
public Node(int data) {
this.data = data;
}
}
/**
* 获取链表长度
*
* @param head 链表头节点
* @return 链表长度
*/
public static int getLength(Node head) {
int length = 0;
Node node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
/**
* 链表插入
*
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 待插入位置,取值从2开始
* @return 插入后得到的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
// 需要判空,否则后面可能会有空指针异常
if (head == null) {
return nodeInsert;
}
//越界判断
int size = getLength(head);
if (position > size + 1 || position < 1) {
System.out.println("位置参数越界");
return head;
}
//在链表开头插入
if (position == 1) {
nodeInsert.next = head;
// return nodeInsert;
//上面return还可以这么写:
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
/**
* 删除节点
*
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
if (head == null) {
return null;
}
int size = getLength(head);
//思考一下,这里为什么是size,而不是size+1
if (position > size || position <1) {
System.out.println("输入的参数有误");
return head;
}
if (position == 1) {
//curNode就是链表的新head
return head.next;
} else {
Node cur = head;
int count = 1;
while (count < position - 1) {
cur = cur.next;
count++;
}
Node curNode = cur.next;
cur.next = curNode.next;
}
return head;
}
/**
* 输出链表
*
* @param head 头节点
*/
public static String toString(Node head) {
Node current = head;
StringBuilder sb = new StringBuilder();
while (current != null) {
sb.append(current.data).append("\t");
current = current.next;
}
return sb.toString();
}
}
双向链表是如何构造的
它在单项链表的基础上新增了对向前指向的结点。
双向链表的结点结构:
class ListNode { int data; Node prev; // 前一个节点的引用 Node next; // 后一个节点的引用 public ListNode(int data) { this.data = data; } }
双向链表的遍历方式(循环和递归)或者从前往后和从后往前
双向循环从前往后循环
Node current = head; while (current!= null){ System.out.println(current.data);//操作数据 current = current.next }
双向链表从后往前递归
public void traverse(Node node){ if(node!= null){ System.out.println(node.data);//操作数据 traverse(node.prev); } return;//递归终止条件 } traverse(tail);//tail为尾部节点
双向链表的插入和删除
//头插法
public void addFirst(int data){
//创建新节点
ListNode node = new ListNode(data);
//处理是空链表时的情况
if(this.head == null){
this.head = node;
}else{
//正常情况,next和prev都要进行处理
node.next = this.head;
this.head.prev = node;
this.head = node;
}
}
//尾插法 如法炮制
public void addLast(int data){
ListNode node = new ListNode(data);
if(this.next == null){
this.next = node;
}else{
node.prev = this.last;
this.last.next = node;
this.last = node;
}
}
//中间插入
/*
3.先处理在 index == 0 的情况下插入数据,直接使用头插法(addFirst)
4.再处理在 index == size()的情况下插入数据,使用尾插法(addLast)
5.最后处理正常情况(在双链表中只要找到了index指向的那个节点就可以直接插入新节点)
*/
public boolean addIndex(int index, int data){
//1.先判是否为空链表
if(this.head == null){
return false;
}
//2.然后判断index(下标)的值是否合法,合法范围:0<= index<= size()(链表当前的节点个数)
if(index < 0 || index > length()){
return false;
}
//3.先处理在 index == 0 的情况下插入数据,直接使用头插法(addFirst)
if(index == 0){
addFirst(data);
return true;
}
//4.再处理在 index == size()的情况下插入数据,使用尾插法(addLast)
if(index == length()){
addLast(data);
return true;
}
//5.最后处理正常情况(在双链表中只要找到了index指向的那个节点就可以直接插入新节点)
ListNode node = new ListNode(data);
ListNode cur = this.head;
while(index>0){
cur = cur.next;
index--;
}
node.next = cur;
node.prev = cur.prev;
cur.prev.next = node;
cur.prev = node;
return true;
}
删除结点
删除首结点
public void deleteFirst(){
//若链表只有一个结点
if(this.head.next == null){
this.last = null;
}else{
this.head.next.prev = null;
}
this.head = this.head.next;
return;
}
删除尾结点
public void deleteLast(){
if(this.last.prev ==null){
this.head = null;
}else{
this.last.prev.next = null;
}
this.last = this.last.prev;
return;
}
删除中间结点
public boolean deleteIndex(int index){
if(this.head.next == null){
this.head=null;
this.last=null;
return false;
}
if(index < 1 || index > length()){
return false;
}
if(index = 1){
this.deleteFirst();
return true;
}else if(index = length()){
this.deleteLast();
return true;
}else{
DoubleListNode cur = this.head;
while(index > 0){
if(cur.value == key){
cur.next = cur.prev.next;
cur.prev = cur.next.prev;
return true;
}else{
cur = cur.next;
index--;
}
}
return false;
}
}
删除第一次val为key的结点 ——remove (int key)
public void remove(int key){
//1.先判断是不是空链表
if(this.head == null){
return;
}
//2.处理head结点的value值为key的情况
if(this.head.value == key){
this.head = this.head.next;
//注意,要清除前记录点
this.head.prev = null;
return;
}
//3.正常循环遍历,注意区分找到的结点是否为尾结点。
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
if(cur.next == null){
//最后一个结点
cur.last = cur.prev;
cur.prev.next = null;
cur.prev = null;
return;
}else{
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
return;
}
}else{
cur = cur.next;
}
}
return;
}
删除所有val值为key的结点——removeAll(int key)
//删除所有值为key的节点
public void removeAll(int key){
//判断是否为空链表
if(this.head == null){
return;
}
//先处理正常情况
ListNode cur = this.head.next;
//循环遍历链表
while(cur != null){
//找到了
if(cur.val == key){
//先判断是不是尾节点
if(cur.next == null){
//是尾节点,进行处理
cur.prev.next = null;
cur.prev = null;
}else{
//不是尾结点,就这样处理
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
}
cur = cur.next; //该节点是与不是都要往后走
}
//处理头节点的情况
if(this.head.val == key){
this.head = this.head.next;
//判断头节点处理完毕后是不是成了一个空链表
if(this.head != null){
this.head.prev = null;
}
}
}