就目前而言,相信大家对数组、链表还有栈都基本已经有了一些了解,本篇文章将以栈为主体,探究栈和数组,栈和链表之间的一些联系。
当然在开始对栈的学习之前,我们先回顾有关数组、链表的基础知识点。
学习代码就是一个不断遗忘且巩固的过程,如何让敲出来的代码在心中印象更为深刻呢?不妨为这些有规律的字母的排列组合赋予一些当下事物的灵动性。
在这里我不得不提到当下的热梗:诸如来自歌手2024中的“五旬老太守国门”、“叶赫那拉氏大战八国联军”等等,既然咱们英子在这期舞台上是那么的孤单无助,在本篇代码里我将在循环取出栈中元素时,为英子来点助力,毕竟,57岁正是拼搏的年龄,咱们英子不能输,一个不够就来俩,两个不够我就来N个,真舞台咱也上不去,那就只能在代码里为我们的歌手们呐喊助威了!(见三)
天天抖手,精神抖擞!
目录
一、链表与数组(Array and LinkedList)
1.关于数组 Array
2.关于链表 LinkedList
二、栈 (Stack)
1.泛型简介
2.什么是栈
3.数组与栈
1)栈的存与取
2)数组和栈删除之间的区别
3)代码实现
4.链表与栈
1)栈中数据的插入与删除
2)基本代码实现
3)头插法与尾插法
三、整体代码
一、链表与数组(Array and LinkedList)
1.关于数组 Array
数组在内存中是一段连续的空间,每段的编号都是从0开始的,依次递增,也称为数组的下标。
数组可以通过索引(下标)访问其任意位置的元素。
数组不能够自动扩容且长度固定不可变。
除了掌握数组的创建,还需要会使用的就是数组的存和取。
int[] array1 = new int[10];//动态初始化
int[] array2 = {1,2,3,4,5};//静态初始化
在整数型数组的动态初始化里,每个元素的初始值都为0,不同类型的数组初始值不同。
public class Test {
//存
public static void main(String[] args) {
int[] array = new int[5];
for (int i = 0; i < array.length; i++) {
array[i] = i * 2 + 1;
}
for (int i = 0; i < array.length; i++) {
System.out.println("array[" + i + "] = " + array[i]);
}
}
}
public class Test {
//取
public static void main(String[] args) {
int[] arr1 = {1,2,3,4,5};
for (int i = 0; i < arr1.length; i++) {
System.out.print(arr1[i]+" ");
}
System.out.print("\n");
int[] arr2 = new int[5];
for (int j = 0;j < arr2.length; j++){
int value = arr2[j];
System.out.print(value+" ");
}
}
}
运行结果:
1 2 3 4 5
0 0 0 0 0
2.关于链表 LinkedList
这里主要回顾单向链表。链表长度可变,且可以实现自动扩容,但不能通过下标进行访问。
链表使用节点对象储存数据,节点之间相互存储地址建立连接。
链表主要几个概念:头节点和尾结点。
头节点(head): 链表的第一个节点称为头节点,通常用来表示整个链表的起始位置。
尾节点(last): 链表的最后一个节点称为尾节点,其指针通常指向 null,表示链表的结束。
但与数组不同,链表中的元素在内存中不是连续存储的,而是通过指针(引用)相互连接起来。
图示的为链表的一个节点node,value是这个节点的所存储的数据值,next为下一节点的地址。
二、栈 (Stack)
1.泛型简介
为什么叫泛型?一般提到关于“泛”的都是联想到广泛、宽泛、空泛、泛泛之交等词语、所以不难得出泛具有适用性广、通用性强等特征。所以在Java中的泛型一般用在尚不确定的数据类型中,可以暂替多种数据类型,以下段代码为例。
public class pandora_box<T> {
private T value;
public void setValue(T value){
this.value=value;
System.out.println(value);
}
public T getValue(){
System.out.println(value);
return value;
}
public static void main(String[] args) {
pandora_box<String> box1 = new pandora_box<>();
box1.getValue();
box1.setValue("潘多拉魔盒");
pandora_box<Integer> box2 = new pandora_box<>();
box2.setValue(111);
box2.getValue();
}
}
输出结果:
null
潘多拉魔盒
111
111
2.什么是栈
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
底层可以使用数组或链表进行实现。
采取先进后出(LIFO:last in first of)原则: 即先存入的数据,最后才能取出
类比:放羽毛球的盒子,最先放进去的是最后一个拿出来的,或一个单向闭口的管道(最先存入的为栈底、最后一个存入的为栈顶)。
总结:栈的操作只涉及栈顶元素,而不会涉及栈底或中间的元素。
3.数组与栈
数组可以理解成为一种固定长度的栈。
1)栈的存与取
存:按照下标正常存入
取:从最后的元素开始依次向前
2)数组和栈删除之间的区别
数组:找到对应的下标,将改下标后一位置的元素逐一向前挪一个
栈:倒着一一取出直至找到需要删除的数据
对于插入数据数据呢?会很麻烦,一旦越过最后一个要取前面的步骤就会很繁琐。
3)代码实现
Object[ ]是一个数组,其中每个元素的类型都是Object。在 Java 中,Object 是所有类的祖先,因此 Object[ ] 数组可以存储任何类型的对象,包括基本数据类型的包装类对象和用户自定义的类对象。
由于Object是所有类的直接或间接父类,因此 Object[ ] 数组可以存储任意类型的对象,例如String、Integer、Double 等。当你不确定数组中需要存储哪种类型的对象时,可以使用 Object[ ] 数组作为一种通用的容器。(类比泛型的思想)
public class ArrayStack<E> {
Object[] stackArr = new Object[10];
int size=0;
public void add(E e){
//定义了一个公有的方法 add,用于向栈中添加元素
//该方法使用泛型类型 T 作为参数类型,表示可以接受任意类型的对象(即传入)
if (stackArr.length==size){
System.out.println("小二提示:您的客栈已满!");
return;
}
stackArr[size++]=e; //这行代码相当于stackArr[size]=e;size++;
//将传入的元素 t 添加到栈顶(数组中的下一个空位置),并将 size 自增
//这里利用 size 变量来记录当前栈中元素的个数,并在添加元素后更新 size
}
public E get(){
//当你从栈中取出元素时,根据栈的特性,应当从栈顶开始取出元素!!!
E element =(E)stackArr[size-1];//具体理解见word
stackArr[size-1]=null;
size--;
return element;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("stackArr:{ ");
for (int i = 0; i < size; i++) {
sb.append(stackArr[i]).append(" "); // 将每个元素的值拼接到 str 中
}
sb.append("}");
return sb.toString();
}
public static void main(String[] args) {
ArrayStack<Object> arrStack = new ArrayStack<>();
for (int i = 0; i < 10; i++){
arrStack.add(i);
}
System.out.println("存-"+arrStack.toString());
for (int i = 0; i < 10; i++){
System.out.print("取-"+arrStack.get()+" ");
}
}
}
输出结果:
存-stackArr:{ 0 1 2 3 4 5 6 7 8 9 }
取-9 取-8 取-7 取-6 取-5 取-4 取-3 取-2 取-1 取-0
4.链表与栈
链表可以理解为实现一种长度不固定的栈。
1)栈中数据的插入与删除
由于栈只能从栈顶开始对其中的数据进行取出,所以我们只能倒着一一取出直至找到需要删除的数据,那么对于插入数据数据呢?一样也很麻烦,一旦越过最后一个数据要取出前面的就会很麻烦。
2)基本代码实现
链表通过将数据存入节点对象中,而节点类的构造一般包括属性、数据变量、下一个节点对象变量(其中存储的为地址)。
添加或查找数据都可以通过一个头节点依次向下找。
class Node<E>{
E value;//数据变量
Node<E> next;
public Node(E value){
this.value=value;
}
public Node<E> setNext(){
return next;
}
public void getNext(){
this.next=next;
}
}
public class myLinkedList<E> {
Node<E> head;
int size;
public void add(E e){
Node<E> node = new Node<>(e);
//如果头节点为空
if(head==null){
head=node;
size++;
return;
}else{
//遍历链表找到最后一个节点,将新节点挂在最后一个节点上
//利用递归的思想
Node<E> current = head;
while (current.next != null){
current=current.next;
}
current.next=node;
size++;
}
}
public static void main(String[] args) {
myLinkedList<Integer> myll = new myLinkedList<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i<100000;i++){
myll.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("人工链表用时:"+(endTime-startTime)+"ms");
LinkedList<Integer> linkedList = new LinkedList<>();
long startTime1 = System.currentTimeMillis();
for (int i = 0; i<100000;i++){
linkedList.add(i);
}
long endTime1 = System.currentTimeMillis();
System.out.println("系统链表用时:"+(endTime1-startTime1)+"ms");
}
}
运行结果:
人工链表用时:5728ms
系统链表用时:4ms
思考:为何自制的链表与java系统自带的耗时差异如此之大?
可能的原因:在遍历上耗时过多。
3)头插法与尾插法
So如果我们不是通过找到头节点后再遍历循环挂到末尾上,直接让新节点成为头节点是不是就可以略去遍历循环的时间了呢?(头插法)
public void addFirst(E e){
Node<E> node = new Node<>(e);
if(head==null){
head=node;
size++;
return;
}else{
node.next=head;//此时假设新节点node已经成为头节点,则当时的头节点的位置处于新节点的下一个
head=node;//原本头节点的位置
size++;
}
}
运行结果
人工链表用时:98ms
系统链表用时:33ms
下面是尾插法的代码实现部分。
public void addLast(E e) {
Node<E> node = new Node<>(e);
if (head == null) {
head = node;
last = node;//此时头结点也是尾结点
size++;
return;
} else {
last.next = node;//此时假设新节点node已经成为头节点,则当时的头节点的位置处于新节点的下一个
last = node;//原本头节点的位置
size++;
}
}
运行结果:
人工链表用时:94ms
系统链表用时:96ms
不难看出:尾插法的运行速度甚至比头插法还有java自带的耗时更少。
三、整体代码(叶赫那拉大战八国联军)
class Node<E>{
E value;//数据变量
Node<E> next;
public Node(E value){
this.value=value;
}
public void setNext(Node<E> next){
this.next=next;
}
public Node<E> getNext(){
return next;
}
}
public class myLinkedList<E> {
Node<E> head;
Node<E> last;
int size;
public void addFirst(E e){
Node<E> node = new Node<>(e);
if(head==null){
head=node;
size++;
return;
}else{
node.next=head;//此时假设新节点node已经成为头节点,则当时的头节点的位置处于新节点的下一个
head=node;//原本头节点的位置
size++;
}
}
public void addLast(E e) {
Node<E> node = new Node<>(e);
if (head == null) {
head = node;
last = node;//此时头结点也是尾结点
size++;
return;
} else {
last.next = node;//此时假设新节点node已经成为头节点,则当时的头节点的位置处于新节点的下一个
last = node;//原本头节点的位置
size++;
}
}
public void add(E e){
Node<E> node = new Node<>(e);
//如果头节点为空
if(head==null){
head=node;
size++;
return;
}else{
//遍历链表找到最后一个节点,将新节点挂在最后一个节点上
//利用递归的思想
Node<E> current = head;
while (current.next != null){
current=current.next;
}
current.next=node;
size++;
}
}
@Override
public String toString(){
String str = "{";
Node<E> current = head;
while (current.next!=null){
str+=current.value+",";
current=current.next;
}
str+=current.value+"}";
return str;
}
public E get(int point){
if(point<0 || point>=size){
System.out.println("不好意思这位大人您越界了");
return null;
}
Node<E> current = head;
for (int i =0; i<point;i++){
current=current.next;
}
return current.value;
}
public static void main(String[] args) {
myLinkedList<String> stringList = new myLinkedList<>();
for (int i=0;i<6;i++){
stringList.addLast("叶赫那拉"+i);
}
for (int i=0;i<2;i++){
stringList.addFirst("八国联军"+i);
}
//注意顺序不能调换!!!
System.out.println(stringList.toString());
System.out.println(stringList.get(3));
}
public static void test(){
myLinkedList<Integer> myll = new myLinkedList<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i<1000000;i++){
//myll.add(i);
myll.addFirst(i);
//myll.addLast(i);
}
long endTime = System.currentTimeMillis();
System.out.println("人工链表用时:"+(endTime-startTime)+"ms");
LinkedList<Integer> linkedList = new LinkedList<>();
long startTime1 = System.currentTimeMillis();
for (int i = 0; i<1000000;i++){
linkedList.add(i);
}
long endTime1 = System.currentTimeMillis();
System.out.println("系统链表用时:"+(endTime1-startTime1)+"ms");
}
}
运行结果:
{八国联军1,八国联军0,叶赫那拉0,叶赫那拉1,叶赫那拉2,叶赫那拉3,叶赫那拉4,叶赫那拉5}
叶赫那拉1
写到最后发现这篇的结构与内容有点杂乱,可能是因为有段时间没有写博客的原因,让我逐渐调整调整,争取下一篇是那种短小精悍的小短篇,后续关于链表、数组、栈的内容还会出现,这次就当做一个初步了解啦。