04 -链表相关知识

关于链表

大多数链表都是检验coding能力的,通过熟练度不断提升。在面试中一二面会进行考察,基本必须AC,是很基础的考察项。

关于null

JVM虚拟机里面规定了一个地址为null,然后链表最后一个元素中其实也是存了一个地址,只不过该地址指向null所在的内存空间。所以最后一个元素指向null。(可以理解为启动前约定好,某一个地址就叫null)

单链表的反转及双链表的反转

  1. 链表的遍历
    首先获得头指针,
    循环
    Node cur = head;
    while (cur != null){
    	// 中间处理流程
    	System.out.println(cur.value);
    	//切记不要忘了,更新下一个节点。while循环中一定要更新节点
    	cur = cur.next;
    }
    
  2. 反转注意事项:
    定义一个反转方法后, 该方法一定要返回新的头指针,并在调用时进行接收。否则会被GC(引用为0即被GC)。
  3. 反转大体步骤
    3.1 先用next记录下一个节点的地址
    3.2 对当前节点的指针进行反转
    3.3.将pre和当前节点head,进行更新 --> 往前移动
    代码:
package class04;

public class LinkedList {
   public static class Node {
       public int value;
       public Node next;

       public Node(int value) {
           this.value = value;
       }
   }

   public static class DoubleNode {
       public int value;
       public DoubleNode last;
       public DoubleNode next;

       public DoubleNode(int value) {
           this.value = value;
       }
   }

   // 链表都是需要头节点来进行访问的
   public static Node reverseLinkedList(Node head) {
       Node pre = null;
       Node next = null;
       // 修改 Node 中Next指针指向Pre
       // 顺序: 首先保存next, 然后修改指针指向pre,然后将pre和head一同更新到下一个位置
       while (head != null) {
           next = head.next;
           head.next = pre;
           pre = head;
           head = next;
       }
       return pre;
   }

   public static DoubleNode reverseDoubleList(DoubleNode head) {
       DoubleNode pre = null;
       DoubleNode next = null;
       while (head != null) {
           // 1. 先把下一个节点位置 记住
           next = head.next;
           // 2. 修改指针 (此时可以随意修改指针)
           head.last = next;
           head.next = pre;
           // 3.更新pre 和 head
           pre = head;
           head = next;
       }
       return pre;
   }

   public static void printLinkedList(Node head) {
       while (head != null) {
           System.out.println(head.value);
           head = head.next;
       }
   }

   public static void printDoubleList(DoubleNode head) {
       System.out.println("Forward");
       DoubleNode pre = null; // 栈中声明的变量需要赋初值
       while (head != null) {
           System.out.println(head.value);
           pre = head;
           head = head.next;
       }
       System.out.println("Reverse");
       while (pre != null) {
           System.out.println(pre.value);
           pre = pre.last;
       }
   }

   public static void main(String[] args) {
       Node node1 = new Node(1);
       node1.next = new Node(2);
       node1.next.next = new Node(3);
       // 1 -> 2 -> 3

       System.out.println("Linked List original order:");
       printLinkedList(node1);
       System.out.println("Linked List reverse order:");
       node1 = reverseLinkedList(node1);
       printLinkedList(node1);


       DoubleNode head = new DoubleNode(1);
       head.next = new DoubleNode(2);
       head.next.next = new DoubleNode(3);
       head.next.last = head;
       head.next.next.last = head.next;
       // 1 -> 2 -> 3

       System.out.println("Double Linked List original order:");
       printDoubleList(head);
       System.out.println("Double Linked List reverse order:");
       head = reverseDoubleList(head);
       printDoubleList(head);
   }
}

单链表实现栈和队列

重点:

  1. 栈 : 后进先出
  2. 队列: 先进先出
    那么,对于单链表而言,先出的必定使用head指向,所以根据谁先出,确定head位置。比如,栈,后进先出,那么Head指向后进的。队列,先进先出,那么Head指向先进的。
    所以,
    栈: 只需要head,头插法
    队列: 需要head和tail, 尾插法

PS: 无论是头插法还是尾插法,都要进行(2个)分支判断,1.为空(head==null)直接赋值 2.为不空(head!=null) 要对新增元素进行指针调整.
关键点: 就是用head来指向 出值 的元素对象。

特点:
插入,弹出,查看 ----> 时间复杂度 都为O(1);

package class04;

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class Code02_LinkedListToQueueAndStack {

	// 首先定义 单链表节点 使用泛型
	public static class Node<V> {
		public V value;
		public Node<V> next;

		public Node(V value) {
			this.value = value;
			this.next = null;
		}
	}

	// 队列需要 head 和 tail,并且为了随时可以返回size,还要一个属性为size
	// 使用封装,private
	// 总结: 单链表实现队列,头尾指针,尾插法
	public static class MyQueue<V> {
		private Node<V> head;
		private Node<V> tail;
		private int size;

		// 构造器
		public MyQueue() {
			head = null;
			tail = null;
			size = 0;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public int size() {
			return size;
		}

		// 入队: 插入元素时, 将元素转化为节点对象, 然后调整首尾指针
		public void offer(V value) {
			Node<V> cur = new Node<>(value);
			// 如果 队列 为空, 则将首尾指针一同指向节点
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				// 如果 队列中已经有节点了,依据 队列先入先出 的原则,head指针指向先入的元素,因为出队顺序依据head指针,
				// 先入先出,意味着 head指针 指向 最先进入的元素,后面的元素,通过尾插法插入
				tail.next = cur;
				tail = cur;
			}

			// 最后不要忘记 因为队列 新增了元素 , 所以要 size++
			size++;
		}

		// 出队: 出队 意味着两件事情: 1. 返回队首中的首个元素 2.去除返回的元素
		public V poll() {
			// 首先定义 返回元素为null. 有两个作用,如果队列为空,直接返回;如果队列不空,会在if语句中进行赋值。
			// 泛型针对的是 引用数据类型
			V ans = null;
			if (head != null) {
				ans = head.value;
				head = head.next;
				size--;
			}
			// 要注意一个场景:队列中只有一个元素,此时出队,头指针为null,尾指针指向上一个元素
			// 存在问题,1. 头尾指针没有及时统一 2.最重要一点: 若tail依然指向出队元素,该对象无法被JV,释放,从而出现脏数据,导致内存泄漏
			// 所以要在每次出队之后进行检查,是否头指针变为了空
			if (head == null) {
				tail = null;
			}
			return ans;
		}

		// 返回队首元素的值,但是不移除该元素
		public V peek() {
			V ans = null;
			// 两种情况,
			// 1. 队列为空,不处理,【直接返回ans】
			// 2. 队列不空(head != null), 对ans进行赋值,【返回ans】
			if (head != null) {
				ans = head.value;
			}
			return ans;
		}
	}

	// 单链表实现栈,头指针+头插法 因为后入先出,后进入的先出,head意味着先出
	public static class MyStack<V> {
		private Node<V> head;
		private int size;

		// 构造器: 空构造器,对属性进行初始化
		public MyStack() {
			head = null;
			size = 0;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public int size() {
			return size;
		}

		// 入队: 头插法
		// 这里可以注意到, 无论是头插法还是尾插法,都要进行(2个)分支判断,1.为空(head==null)直接赋值 2.为不空(head!=null) 要对新增元素进行指针调整
		public void push(V value) {
			Node<V> cur = new Node<>(value);
			if (head == null) {
				head = cur;
			} else {
				cur.next = head;
				head = cur;
			}
			size++;
		}

		// 出队
		public V pop(){
			V ans = null;
			if(head != null) {
				ans = head.value;
				head = head.next;
				// 一定要注意 对size-- 因为要调整size属性
				size--;
			}
			return ans;
		}

		public V peek(){
			V ans = null;
			if (head != null) {
				ans = head.value;
			}
			return ans;
		}

	}

	public static void testQueue() {
		MyQueue<Integer> myQueue = new MyQueue<>();
		Queue<Integer> test = new LinkedList<>();
		int testTime = 5000000;
		int maxValue = 200000000;
		System.out.println("测试开始!");
		for (int i = 0; i < testTime; i++) {
			if (myQueue.isEmpty() != test.isEmpty()) {
				System.out.println("Oops!");
			}
			if (myQueue.size() != test.size()) {
				System.out.println("Oops!");
			}
			double decide = Math.random();
			if (decide < 0.33) {
				int num = (int) (Math.random() * maxValue);
				myQueue.offer(num);
				test.offer(num);
			} else if (decide < 0.66) {
				if (!myQueue.isEmpty()) {
					int num1 = myQueue.poll();
					int num2 = test.poll();
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			} else {
				if (!myQueue.isEmpty()) {
					int num1 = myQueue.peek();
					int num2 = test.peek();
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			}
		}
		if (myQueue.size() != test.size()) {
			System.out.println("Oops!");
		}
		while (!myQueue.isEmpty()) {
			int num1 = myQueue.poll();
			int num2 = test.poll();
			if (num1 != num2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("测试结束!");
	}

	public static void testStack() {
		MyStack<Integer> myStack = new MyStack<>();
		Stack<Integer> test = new Stack<>();
		int testTime = 5000000;
		int maxValue = 200000000;
		System.out.println("测试开始!");
		for (int i = 0; i < testTime; i++) {
			if (myStack.isEmpty() != test.isEmpty()) {
				System.out.println("Oops!");
			}
			if (myStack.size() != test.size()) {
				System.out.println("Oops!");
			}
			double decide = Math.random();
			if (decide < 0.33) {
				int num = (int) (Math.random() * maxValue);
				myStack.push(num);
				test.push(num);
			} else if (decide < 0.66) {
				if (!myStack.isEmpty()) {
					int num1 = myStack.pop();
					int num2 = test.pop();
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			} else {
				if (!myStack.isEmpty()) {
					int num1 = myStack.peek();
					int num2 = test.peek();
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			}
		}
		if (myStack.size() != test.size()) {
			System.out.println("Oops!");
		}
		while (!myStack.isEmpty()) {
			int num1 = myStack.pop();
			int num2 = test.pop();
			if (num1 != num2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("测试结束!");
	}

	public static void main(String[] args) {
		testQueue();
		testStack();
	}

}

双链表实现双向队列

并且,单链表无法实现双向队列。
关键点: 1. 头尾指针。
2. 在插入或弹出节点时,注意将指针设为空,从而令JVM自动释放引用为0 的 对象。

先show我的错误代码: 错误点已经标记: 没有考虑完全。 在出队时 不同于单链表队列。 因为要通过访问指针,并更改为null,来将 节点 通过JVM内部机制进行回收。

package class04;

public class DoubleLinkedListToDeque {
    // 定义双链表节点
    public static class Node<V> {
        // 定义属性 (成员变量)
        public V value;
        public Node<V> last;
        public Node<V> next;

        // 定义构造器
        public Node(V v) {
            value = v;
            last = null;
            next = null;
        }
    }

    public static class MyDeque<V> {
        // 头尾指针,和size
        private Node<V> head;
        private Node<V> tail;
        private int size;

        // 构造器
        public MyDeque() {
            head = null;
            tail = null;
            size = 0;
        }

        // 开始定义方法

        public boolean isEmpty() {
            return size == 0;
        }

        public int size() {
            return size;
        }

        // 双向队列 pushHead pushTail pollHead pollTail
        // poll是队列数据结构实现类的方法,从队首获取元素,同时获取的这个元素将从原队列删除;
        // pop是栈结构的实现类的方法,表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常
        public void pushHead(V value) {
            // 接收value 创建泛型 节点对象
            Node<V> cur = new Node<>(value);

            // 头插法
            if (head == null) {
                head = cur;
                tail = cur;
            } else {
                // 双链表的头插法
                // Step1: 调整双链表指针
                cur.next = head;
                head.last = cur;
                // Step2: 调整头节点head的位置
                head = cur;
                // 这里忽视了一个事情!!! 我们操作的是 双链表, 有两个指针,next 和 last,全部要进行调整
                // 所以进行头插法的时候 要注意,是 进行单链表的头插法,还是双链表的头插法
            }
            size++;
        }

        public void pushTail(V value) {
            Node<V> cur = new Node<>(value);
            // 双链表的尾插法
            if (head == null) {
                head = cur;
                tail = cur;
            } else {
                // 1. 调整 双链表 指针
                tail.next = cur;
                cur.last = tail;
                // 2. 调整tail指针
                tail = cur;
            }
            size++;
        }

        public V pollHead() {
            V ans = null;
            if (head != null) {
                ans = head.value;
                head = head.next;
                // 错误标记!!!!
                // 若是只有一个节点时,会出现 空指针异常
                head.last = null;
                size--;
            }
            if (head == null) {
                tail = null;
            }
            return ans;
        }

        public V pollTail() {
            V ans = null;
            if (head != null) {
                ans = tail.value;
                tail = tail.last;
                // 错误标记!!!!
                // 若是只有一个节点时,会出现 空指针异常
                tail.next = null;
                size--;
            }
            if (tail == null) {
                head = null;
            }
            return ans;
        }

        public V peekHead() {
            V ans = null;
            if (head != null) {
                ans = head.value;
            }
            return ans;
        }

        public V peekTail() {
            V ans = null;
            if (tail != null) {
                ans = tail.value;
            }
            return ans;
        }
    }
}

正确代码:
其实在出队时,只要清晰考虑 队列长度为 01>1的情况即可完成。不同于单链表,只需要考虑 0>0。 因为单链表只需要通过头尾指针跳转就可令JVM删除脏数据,但是双链表需要通过访问移动后头尾指针所指元素的next,last来删除脏数据。这使得双链表删除时,poll方法,需要多考虑1的情况。
```java
package class04;

import java.util.Deque;
import java.util.LinkedList;

public class Code03_DoubleLinkedListToDeque {

	/* 注释部分为左神代码
	public static class Node<V> {
		public V value;
		public Node<V> last;
		public Node<V> next;

		public Node(V v) {
			value = v;
			last = null;
			next = null;
		}
	}

	public static class MyDeque<V> {
		private Node<V> head;
		private Node<V> tail;
		private int size;

		public MyDeque() {
			head = null;
			tail = null;
			size = 0;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public int size() {
			return size;
		}

		public void pushHead(V value) {
			Node<V> cur = new Node<>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				cur.next = head;
				head.last = cur;
				head = cur;
			}
			size++;
		}

		public void pushTail(V value) {
			Node<V> cur = new Node<>(value);
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				tail.next = cur;
				cur.last = tail;
				tail = cur;
			}
			size++;
		}

		public V pollHead() {
			V ans = null;
			if (head == null) {
				return ans;
			}
			size--;
			ans = head.value;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				head = head.next;
				head.last = null;
			}
			return ans;
		}

		public V pollTail() {
			V ans = null;
			if (head == null) {
				return ans;
			}
			size--;
			ans = tail.value;
			if (head == tail) {
				head = null;
				tail = null;
			} else {
				tail = tail.last;
				tail.next = null;
			}
			return ans;
		}

		public V peekHead() {
			V ans = null;
			if (head != null) {
				ans = head.value;
			}
			return ans;
		}

		public V peekTail() {
			V ans = null;
			if (tail != null) {
				ans = tail.value;
			}
			return ans;
		}

	}*/
	// 定义双链表节点
	public static class Node<V> {
		// 定义属性 (成员变量)
		public V value;
		public Node<V> last;
		public Node<V> next;

		// 定义构造器
		public Node(V v) {
			value = v;
			last = null;
			next = null;
		}
	}

	public static class MyDeque<V> {
		// 头尾指针,和size
		private Node<V> head;
		private Node<V> tail;
		private int size;

		// 构造器
		public MyDeque() {
			head = null;
			tail = null;
			size = 0;
		}

		// 开始定义方法

		public boolean isEmpty() {
			return size == 0;
		}

		public int size() {
			return size;
		}

		// 双向队列 pushHead pushTail pollHead pollTail
		// poll是队列数据结构实现类的方法,从队首获取元素,同时获取的这个元素将从原队列删除;
		// pop是栈结构的实现类的方法,表示返回栈顶的元素,同时该元素从栈中删除,当栈中没有元素时,调用该方法会发生异常
		public void pushHead(V value) {
			// 接收value 创建泛型 节点对象
			Node<V> cur = new Node<>(value);

			// 头插法
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				// 双链表的头插法
				// Step1: 调整双链表指针
				cur.next = head;
				head.last = cur;
				// Step2: 调整头节点head的位置
				head = cur;
				// 这里忽视了一个事情!!! 我们操作的是 双链表, 有两个指针,next 和 last,全部要进行调整
				// 所以进行头插法的时候 要注意,是 进行单链表的头插法,还是双链表的头插法
			}
			size++;
		}

		public void pushTail(V value) {
			Node<V> cur = new Node<>(value);
			// 双链表的尾插法
			if (head == null) {
				head = cur;
				tail = cur;
			} else {
				// 1. 调整 双链表 指针
				tail.next = cur;
				cur.last = tail;
				// 2. 调整tail指针
				tail = cur;
			}
			size++;
		}

		/*public V pollHead() {
            V ans = null;
            if (head != null) {
                ans = head.value;
                head = head.next;
                // 错误标记!!!!
                // 若是只有一个节点时,会出现 空指针异常
                head.last = null;
                size--;
            }
            if (head == null) {
                tail = null;
            }
            return ans;
        }

        public V pollTail() {
            V ans = null;
            if (head != null) {
                ans = tail.value;
                tail = tail.last;
                // 错误标记!!!!
                // 若是只有一个节点时,会出现 空指针异常
                tail.next = null;
                size--;
            }
            if (tail == null) {
                head = null;
            }
            return ans;
        }*/
		public V pollHead(){
			V ans = null;
			if (head == null) {
				return ans;
			}
			ans = head.value;
			head = head.next;
			// 对只有一个元素 或 多个元素 进行 分支结构
			// 目的都是 JVM 释放 脏数据
			if(head == null) {
				tail = null;
			} else {
				head.last = null;
			}
			size--;
			return ans;
		}

		public V pollTail(){
			V ans = null;
			if (head == null) {
				return ans;
			}
			// 否则 代表队列一定不空,则
			ans = tail.value;
			tail = tail.last;
			if (tail == null) {
				head = null;
			} else {
				tail.next = null;
			}
			size--;
			return ans;
		}


		public V peekHead() {
			V ans = null;
			if (head != null) {
				ans = head.value;
			}
			return ans;
		}

		public V peekTail() {
			V ans = null;
			if (tail != null) {
				ans = tail.value;
			}
			return ans;
		}
	}

	public static void testDeque() {
		MyDeque<Integer> myDeque = new MyDeque<>();
		Deque<Integer> test = new LinkedList<>();
		int testTime = 5000000;
		int maxValue = 200000000;
		System.out.println("测试开始!");
		for (int i = 0; i < testTime; i++) {
			if (myDeque.isEmpty() != test.isEmpty()) {
				System.out.println("Oops!");
			}
			if (myDeque.size() != test.size()) {
				System.out.println("Oops!");
			}
			double decide = Math.random();
			if (decide < 0.33) {
				int num = (int) (Math.random() * maxValue);
				if (Math.random() < 0.5) {
					myDeque.pushHead(num);
					test.addFirst(num);
				} else {
					myDeque.pushTail(num);
					test.addLast(num);
				}
			} else if (decide < 0.66) {
				if (!myDeque.isEmpty()) {
					int num1 = 0;
					int num2 = 0;
					if (Math.random() < 0.5) {
						num1 = myDeque.pollHead();
						num2 = test.pollFirst();
					} else {
						num1 = myDeque.pollTail();
						num2 = test.pollLast();
					}
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			} else {
				if (!myDeque.isEmpty()) {
					int num1 = 0;
					int num2 = 0;
					if (Math.random() < 0.5) {
						num1 = myDeque.peekHead();
						num2 = test.peekFirst();
					} else {
						num1 = myDeque.peekTail();
						num2 = test.peekLast();
					}
					if (num1 != num2) {
						System.out.println("Oops!");
					}
				}
			}
		}
		if (myDeque.size() != test.size()) {
			System.out.println("Oops!");
		}
		while (!myDeque.isEmpty()) {
			int num1 = myDeque.pollHead();
			int num2 = test.pollFirst();
			if (num1 != num2) {
				System.out.println("Oops!");
			}
		}
		System.out.println("测试结束!");
	}

	public static void main(String[] args) {
		testDeque();
	}

}



## K个节点的组内逆序调整
![在这里插入图片描述](https://img-blog.csdnimg.cn/08b82f392c9e4c539deb713f4c4620ce.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAd2Fsa2trZXI2NjY=,size_20,color_FFFFFF,t_70,g_se,x_16)


## 链表的顺序合并


## AddTwoNumber

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值