携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
数据结构之链表
📌声明和赋值:普通类型和引用类型
普通类型
int x;
double y;
在执行上述代码后,我们会得到分别为32位和64位的框,如下图所示:
而当我们为该内存盒赋值的时候,例如,如果我们执行以下代码:
x = -1431195969;
y = 567213.112;
上面的内存狂会被填充如下:
引用类型
我们声明一个Walrus类
public static class Walrus {
public int weight;
public double tuskSize;
public Walrus(int w, double ts) {
weight = w;
tuskSize = ts;
}
}
假如执行下面代码:
Walrus someWalrus;
someWalrus = new Walrus(1000, 8.3);
第一行是引用类型的声明创建一个64位的内存盒,用于存储该引用类型所指向的对象(无论是什么类,引用类型的变量都是64位)。
第二行是引用类型的赋值。new Walrus(1000, 8.3); 会创建一个海象,然后我们会得到一个由32位和64位的盒子共同组成的海象。
等创建好这个海象后,其地址会由new操作符返回,然后将这些位赋值到someWalrus的内存盒中。
引用类型的直接赋值
Walrus a = new Walrus(1000, 8.3);
Walrus b;
b = a;
执行了第一行之后,a中就保存了Walrus的地址。而b = a; 是将a中的地址赋值给b,这样b和a就一起指向了Walrus。
所以任何对b的修改也会影响到a。
🔵IntList
1.定义IntList类
public class IntList {
public int first; //数据域
public IntList rest; //剩余结点
public IntList(int f, IntList r) {
first = f;
rest = r;
}
}
2.构建5→10→15的链表
如果我们想要构建一个5→10→15的链表,可以这样👇🏻
方法一:从头到尾构建(缺点:如果链表越长,我们需要写的rest越多)
IntList L = new IntList(5, null);
L.rest = new IntList(10, null);
L.rest.rest = new IntList(15, null);
方法二:从尾到头构建(缺点:类似于递归,不太好理解)
IntList L = new IntList(15,null);
L = new IntList (10, L);
L = new IntList (5, L);
3.计算IntList的size的方法
非递归版(链表调用的时候可以返回size)⇒💡 巧用this指针
public int size() {
int ret = 0;
IntList p = this;
while (p.rest != null) {
ret++;
p = p.rest;
}
return ret;
}
递归版
public int size(){
if(this.rest == null)
return 1;
return 1 + this.rest.size();
}
🈲退出循环的条件不能改为this == null ,因为如果this为空,那上一层调用size()方法的就是空指针,会收到NULLPointer的错误!
4.获取指定index的元素
编写一个方法:返回列表第i项的item(下标从0开始)
public int get(int index) {
if (index < 0 || index >= this.size()) {
return -1;
} else {
IntList p = this;
while (index != 0) {
p = p.rest;
index--;
}
return p.first;
}
}
实际上,上述的IntList是不太好的。
- 增加结点,get,size()方法都有可能用到递归。使其他的程序员难以理解。所以我们将构建一个新的类SLList[单链表]
- 每个结点的数据域的名字竟然是first,很容易让人误解成首结点。next结点也被命名为rest。
🔵IntList→SLList
1. 改名(将IntList改为IntNode,将数据域改成item,将下一个结点名改为next
public class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
2. 重新创建一个SLList的单独类,让用户与这个类交互
成员变量:头结点
构造函数:创建一个IntNode结点
public class SLList(){
public IntNode first;
public SLList(int x){
IntNode first = new IntNode(x,null);
}
}
✅在利用构造函数创建新SLList节点的时候不需要传入next指针为空(封装了一层)。只需要传入数据域即可,如下例。
//原方法
IntList node1 = new IntList(5,null);
//现方法
SLList node1 = new SLList(5);
3.增添addFirst&getFirst
SLLis有了first(头指针)的成员变量,所以添加头节点很容易
public void addFirst(int x){
first = new IntNode(x,first);
}
获取头节点也很容易
public int getFirst(){
return first.item;
}
4.构建链表5→10→15
从后往前构建,利用我们刚刚写的addFirst函数.左侧为修改后,右侧为修改前。
SLList List = new SLList(15);
List.addFirst(10);
List.addFirst(5);
IntList L = new IntList(15, null);
L = new IntList(10, L);
L = new IntList(5, L);
5.嵌套类
将IntNode类和SLList类放在两个.java文件中实际是没有必要的。IntNode实际上是为了构建SLList而创建的。⇒ 所以我们可以将IntNode类的声明嵌套在SLList类的声明中。
public class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
public class SLList(){
//嵌套类
public class IntNode {
public int item;
public IntNode next;
public IntNode(int i, IntNode n) {
item = i;
next = n;
}
}
//自己的成员变量和构造函数
public IntNode first;
public SLList(int x){
IntNode first = new IntNode(x,null);
}
}
6.声明为静态变量
因为IntNode这个嵌套类并不需要使用SLList中的任何方法和变量,所以我们可以将IntNode声明为static 。改成 public static class IntNode ,IntNode将无法访问SLList中的所有方法和变量。
💡如果内部的类不需要使用外部类的成员和方法,尽量使其成为静态的。
7. addLast( )&size( )
addLast()
public void addLast(int x){
IntNode p = first;
while(p.next!=null){
p = p.next;
}
p。next = new IntNode(x,null);
}
size()递归实现(因为是嵌套类,所以不能在原函数上递归,需要再创建基于IntNode的递归函数。
public int size(){
return NodeSize(first);
}
public int NodeSize(IntNode p){
if(p.next==null){
return 1;
}
return 1+NodeSize(p.next);
}
8.以空间换时间→创建变量size
如果要统计链表的长度,size()方法需要遍历整个链表,当链表很长的时候,耗时很大。所以我们在SLList中增加一个成员变量size。
在每次增加结点时size++,在删除结点时size-1.
9.空链表造成的空指针问题
在封装了嵌套类后,我们创建一个空链表(无参的构造函数)将非常容易
public SLList (){
first = null;
size = 0;
}
但如果我们这个空链表增加一个元素(使用addLast方法),会出现null指针异常
public void addLast(int x) {
size += 1;
IntNode p = first;
//p本来就是null指针,p.next就出现访问错误了。
while (p.next != null) {
p = p.next;
}
p.next = new IntNode(x, null);
}
10.哨兵结点
哨兵结点の数据域:随便保存一个值
哨兵结点のnext指针:指向实际的第一个结点
包含项目 5、10 和 15 的 SLList
将如下所示: