public E getFirst() {
return get(0);
}
public E getLast() {
return get(getSize() - 1);
}
// 设置节点元素值
public void set(int index, E element) {
// 从头节点开始遍历
Node current = dummyHead.next;
for (int i = 0; i < index; i++) current = current.next;
current.element = element;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append(String.format(“LinkedList: size = %d\n”, getSize()));
Node current = dummyHead.next;
for (int i = 0; i < getSize(); i++) {
str.append(current.element).append(“->”);
current = current.next;
}
str.append(“null”);
return str.toString();
}
// main函数测试
public static void main(String[] args) {
LinkedList linkedList = new LinkedList<>();
for (int i = 0; i < 5; i++) {
linkedList.add(i, i);
System.out.println(linkedList);
}
// 删除首尾节点
linkedList.removeFirst();
linkedList.removeLast();
System.out.println(linkedList);
}
}
/*
输出内容:
LinkedList: size = 1
0->null
LinkedList: size = 2
0->1->null
LinkedList: size = 3
0->1->2->null
LinkedList: size = 4
0->1->2->3->null
LinkedList: size = 5
0->1->2->3->4->null
LinkedList: size = 3
1->2->3->null
*/
实现了链表后,我们仿照之前的动态数组,也实现一下栈和队列这两种较基础的数据结构。如果需要了解栈和队列,可以看之前的这篇文章。
在写代码前,我们先来分析一下如何实现。栈是一种后进先出的结构,而通过上面对链表的学习,可以发现链表的 head 头节点位置与栈的栈顶非常相似,节点可以通过头节点直接进入链表,也可以直接从头节点脱离链表,而且这两种操作的时间复杂度都是 O(1) 级别的(prev 节点无需移动)。找到了这个特点,就可以很快地利用链表实现栈。
我们还是使用之前的接口实现栈:
package com.algorithm.stack;
public interface Stack {
void push(E element); // 入栈
E pop(); // 出栈
E peek(); // 查看栈顶元素
int getSize(); // 获取栈长度
boolean isEmpty(); // 判断栈是否为空
}
具体实现:
package com.algorithm.stack;
import com.algorithm.linkedlist.LinkedList;
public class LinkedListStack implements Stack{
private LinkedList linkedList; // 使用链表储存栈元素
public LinkedListStack(){
linkedList = new LinkedList<>();
}
// 把链表头作为栈顶,始终对链表头进行操作
// 入栈
@Override
public void push(E element) {
linkedList.addFirst(element);
}
// 出栈
@Override
public E pop() {
return linkedList.removeFirst();
}
// 查看栈顶元素
@Override
public E peek() {
return linkedList.getFirst();
}
// 查看栈中元素个数
@Override
public int getSize() {
return linkedList.getSize();
}
// 查看栈是否为空
@Override
public boolean isEmpty() {
return linkedList.isEmpty();
}
@Override
public String toString() {
return “Stack: top [” + linkedList + “] tail”;
}
// main函数测试
public static void main(String[] args) {
LinkedListStack stack = new LinkedListStack<>();
for (int i=0;i<5;i++){
stack.push(i);
System.out.println(stack);
}
stack.pop();
System.out.println(stack);
}
}
/*
输出结果:
Stack: top [0->null] tail
Stack: top [1->0->null] tail
Stack: top [2->1->0->null] tail
Stack: top [3->2->1->0->null] tail
Stack: top [4->3->2->1->0->null] tail
Stack: top [3->2->1->0->null] tail
*/
##数组栈VS链表栈
截至目前,我们已经通过两种方式实现了栈,接下来不妨对比一下两种实现方式的性能孰高孰低。可以通过出栈和入栈两种操作进行评估:
package com.algorithm.stack;
import java.util.Random;
public class PerformanceTest {
public static double testStack(Stack stack, int testNum){
// 起始时间
long startTime = System.nanoTime();
// 使用随机数测试
Random random = new Random();
// 入栈测试
for (int i=0;i<testNum;i++) stack.push(random.nextInt(Integer.MAX_VALUE));
// 出栈测试
for (int i=0;i<testNum;i++) stack.pop();
// 结束时间
long endTime = System.nanoTime();
// 返回测试时长
return (endTime - startTime) / 1000000000.0;
}
public static void main(String[] args) {
// 数组栈
ArrayStack arrayStack = new ArrayStack<>();
double arrayTime = testStack(arrayStack, 1000000);
System.out.println("ArrayStack: " + arrayTime);
// 链表栈
LinkedListStack linkedListStack = new LinkedListStack<>();
double linkedTIme = testStack(linkedListStack, 1000000);
System.out.println("LinkedListStack: " + linkedTIme);
}
}
/*
输出结果:
// testNum = 10万次的测试结果
ArrayStack: 0.0167257
LinkedListStack: 0.0120104
// testNum = 100万次的测试结果
ArrayStack: 0.0509282
LinkedListStack: 0.2121052
*/
第一次使用10万个随机数进行测试时,两者的性能差不多,链表似乎还有小小的优势;而当使用100万个数测试时,链表要明显慢于数组。原因是链表在添加节点的过程中,需要不断地new一个新的节点,而这个new的过程需要寻找新的地址,所以随着次数的增大,耗时变得越来越明显。而数组是先统一申请一批,满了再继续通过resize申请(个数根据数组长度)。但是如果先执行链表,后执行数组,又会出现不同的结果:
public class PerformanceTest {
… …
public static void main(String[] args) {
// 链表栈
LinkedListStack linkedListStack = new LinkedListStack<>();
double linkedTime = testStack(linkedListStack, 1000000);
System.out.println("LinkedListStack: " + linkedTime);
// 数组栈
ArrayStack arrayStack = new ArrayStack<>();
double arrayTime = testStack(arrayStack, 1000000);
System.out.println("ArrayStack: " + arrayTime);
}
}
/*
输出结果:
LinkedListStack: 0.0368811
ArrayStack: 0.054051
*/
这下链表又比数组快了😂!猜想应该是先跑链表时,空闲空间比较多,找新地址的开销还不大。但是如果在数组已经占用了100万个地址的情况下,再寻找地址就没那么容易了。
实现了栈,再来看队列。队列是一种先进先出的结构,对应到链表,可以使用链表的 head 头节点模拟出队操作(O(1)的时间复杂度)。如果知道了链表尾部的位置,就可以通过从链表尾部添加节点来模拟入队操作,并且这个操作的时间复杂度也是 O(1)。所以我们要再多维护一个 tail 节点,意味着我们要对刚才的链表稍作调整。
同样使用之前的队列接口进行实现:
package com.algorithm.queue;
public interface Queue {
void enqueue(E element); // 入队
E dequeue(); // 出队
E getFront(); // 获取队首元素
int getSize(); // 获取队列长度
boolean isEmpty(); // 判断队列是否为空
}
具体实现:
package com.algorithm.queue;
import java.lang.String;
public class LinkedListQueue implements Queue{
// 节点类
private class Node{
public E element; // 节点储存的元素值
public Node next; // 指向的下一个链接节点
public Node(E element, Node node){
this.element = element;
this.next = node;
}
public Node(E element){
this.element = element;
this.next = null;
}
public Node(){
}
}
private Node head, tail; // 增加tail节点
private int size;
public LinkedListQueue(){
head = tail = null;
size = 0;
}
@Override
public int getSize(){
return size;
}
@Override
public boolean isEmpty(){
return getSize() == 0;
}
// tail入队
@Override
public void enqueue(E element){
// 如果队列为空,则将head和tail都置为入队的第一个节点
if (tail == null){
head = tail = new Node(element);
}else{ // 其他情况下在tail处链接即可
tail.next = new Node(element);
tail = tail.next;
}
size++;
}
// head出队
@Override
public E dequeue(){
if (isEmpty()) throw new IllegalArgumentException(“Empty queue, enqueue first!”);
// 将当前head标记为待出队节点
Node delNode = head;
// head.next节点替代当前head
head = head.next;
// 将出队节点置为空,脱离链表
delNode.next = null;
size–;
// 如果出队后head为空,说明队列为空,则将tail也置为null
if (head == null) tail = null;
return delNode.element;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
最后希望可以帮助到大家!
千千万万要记得:多刷题!!多刷题!!
之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!
篇幅有限,以下只能截图分享部分的资源!!
(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)
(2)刷的算法题(还有左神的算法笔记)
(3)面经+真题解析+对应的相关笔记(很全面)
(4)视频学习(部分)
ps:当你觉得学不进或者累了的时候,视频是个不错的选择
在这里,最后只一句话:祝大家offer拿到手软!!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-6BgQ8Igi-1712754717707)]
最后希望可以帮助到大家!
千千万万要记得:多刷题!!多刷题!!
之前算法是我的硬伤,后面硬啃了好长一段时间才补回来,算法才是程序员的灵魂!!!!
篇幅有限,以下只能截图分享部分的资源!!
(1)多线程(这里以多线程为代表,其实整理了一本JAVA核心架构笔记集)
[外链图片转存中…(img-aeceJzHY-1712754717708)]
(2)刷的算法题(还有左神的算法笔记)
[外链图片转存中…(img-qK2xPQCK-1712754717708)]
(3)面经+真题解析+对应的相关笔记(很全面)
[外链图片转存中…(img-ODH8kop2-1712754717708)]
(4)视频学习(部分)
ps:当你觉得学不进或者累了的时候,视频是个不错的选择
在这里,最后只一句话:祝大家offer拿到手软!!
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-vZL7DYHD-1712754717709)]