一、单链表
单链表是一种链式存储结构,即物理存储单元上是非连续、非顺序性的,数据元素的逻辑是通过链表中的指针实现的,链表由一系列的结点组成,结点的构成包括一个数据域和指针域,指针域存放后继结点的存储地址,结点可以在运行时动态生成。
- 首先定义单链表,用内部类来实现Entry结点类的创建
为了体现Java语言的封装性,将其基本属性均设置为私有,可以通过相应get()方法返回需要的属性值;
class Link<T> {
class Entry<T> { //定义结点类
private T data;
private Entry<T> next;
public Entry() { //构造函数(头节点)
this.data = null;
this.next = null;
}
public Entry(T value) { //构造函数(数据节点)
this.data = value;
this.next = null;
}
//得到结点数据
public T getData() {
return this.data;
}
}
private int size; //链表长度
private Entry<T> head; //头节点
public Link() {
this.head = new Entry<>();
}
}
然后实现单链表相关的方法
1、某些方法涉及到传入位置参数,要对其参数进行基本的合法性校验
判断索引合法性
//判断索引合法性
public boolean checkRange(int index) {
if (index < 0 || index > getSize() ) {
//throw new RuntimeException("非法参数");
return false;
}
return true;
}
2、头插法
注意:
- 应该保证能找到后面的数据结点,先使待插入结点(entry)的指针域保存头结点(head)后面的数据节点的地址,然后再让头结点指向待插入结点,size++;
- 不能直接让head.next指向entry,这样会使后面的数据节点全部丢失!
//添加元素:头插法
public void insertHead(T value) {
Entry<T> entry = new Entry<>(value);
entry.next = this.head.next;
this.head.next = entry;
size++;
}
3、尾插法
注意:
- 首先要使当前结点cursor遍历到尾结点;
- 再使尾结点指向待插入结点,size++;
//添加元素:尾插法
public void insertTail(T value) {
Entry<T> entry = new Entry<>(value);
Entry<T> cursor = this.head;
while (cursor.next != null) { //遍历到尾结点
cursor = cursor.next;
}
cursor.next = entry;
size++;
}
4、任意位置插入
注意:
- 判断插入位置的合法性;
- 若pos参数合法,先使cursor遍历到插入位置的前一个结点,然后再将entry插入进来,size++;
- 类似于头插法,先让entry指向cursor.next,然后让cursor指向entry;
//任意位置插入元素
public void insertPos(int pos,T value) {
if (!checkRange(pos)) {
return;
}
int count = 0;
Entry<T> cursor = this.head;
while (count < pos-1) {
count++;
cursor = cursor.next;
}
Entry<T> entry = new Entry<>(value);
entry.next = cursor.next;
cursor.next = entry;
size++;
}
5、删除值为value的结点(只删除第一个匹配到的结点)
注意:
- 定义cursor遍历链表,定义previous为cursor的前驱结点;若cursor.data不等于value,则cursor = cur.next,previous = previous.next;
- 当cursor.data == value时,说明cursor是需要删除的结点;
- 让previous指向cursor的下一个结点,即完成了cursor结点的删除,size- -;
public boolean remove(T value) {
Entry<T> previous = this.head;
Entry<T> cursor = this.head.next;
while (cursor != null) {
if (cursor.data != value) {
previous = previous.next;
cursor = cursor.next;
} else {
previous.next = cursor.next;
size--;
return true;
}
}
return false;
}
6、删除任意位置结点
注意:
- 对pos参数进行合法性校验;
- 若pos合法,让cursor遍历到pos的前一个结点;
- 让cursor指向pos位置的下一个结点,即cursor.next = cursor.next.next,size- -;
//删除指定位置结点
public boolean remove(int pos) {
if (checkRange(pos)) {
int count = 0;
Entry<T> cursor = this.head;
while (count < pos-1) {
cursor = cursor.next;
count++;
}
cursor.next = cursor.next.next;
size--;
return true;
}
return false;
}
7、得到链表长度
- 可以通过返回size的值来获取链表长度;
//得到链表长度
public int getSize() {
return this.size;
}
- 若没有定义size属性,也可以通过遍历链表获得链表长度;
public int getLength() {
Entry<T> cursor = this.head.next;
int count = 0;
while (cursor != null) {
count++;
cursor = cursor.next;
}
return count;
}
8、打印链表元素
- 写一个show()方法打印链表元素
//打印链表所有数据
public void show() {
Entry<T> cursor = this.head.next;
while (cursor != null) {
System.out.print(cursor.data + " ");
cursor = cursor.next;
}
System.out.println();
}
- 重写toString()方法
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
Entry<T> cursor = this.head.next;
while (cursor != null) {
buffer.append(cursor.getData() + " ");
cursor = cursor.next;
}
return buffer.toString();
}
在写面试题的时候经常会碰到链表相关的题目,这里整理一些典型的例题,点击单链表相关面试真题。