目录
2.8修改索引为index位置的元素为新值,返回修改前的元素值
数组这种结构适用于频繁查询低频插入和修改的场景,若频繁插入和删除时,由于需要进行元素的搬移以及扩容等操作,浪费空间,性能开销较大,因此引入了链表。
链表是逻辑连续的,不是物理连续的。
物理连续指的是前一个元素一定是位于后一个元素之前,典型的就是数组。
逻辑连续是指每一个结点之间通过一个“钩子”连接,没有这个钩子,每个节点之间是彼此独立的毫无关系。优点就是可以在任意结点之前或者之后插入新的结点,对其他的节点并不造成影响。不需要考虑空间是否够用以及扩容问题,不会造成空间的浪费。
由于链表可以根据是否带头结点、是否循环、是否双向,排列组合有8中结构,只需要考虑三种结构:带向不带头的单链表(单向链表),单项带头链表,双向不带头链表。
一、线性表接口
定义线性表接口Seqlist。定义接口带来的好处:就是可以以非常低的成本来更换具体的子类。
public interface SeqList {
void add(int val);
// 在索引为index的位置插入新元素
void add(int index,int val);
// 查询线性表中是否包含指定元素val
boolean contains(int val);
// 返回索引为index的元素值
int get(int index);
// 修改索引为index位置的元素为新值,返回修改前的元素值
int set(int index,int newVal);
// 删除线性表中索引为index的元素,返回删除前的元素值
int removeByIndex(int index);
// 删除第一个值为val的元素
void removeByValueOnce(int val);
// 在线性表中删除所有值为val的元素
void removeAllValue(int val);
}
二、单链表
有两个特殊的结点:头结点:只有头结点没有前驱。尾结点:只有尾结点没有后继。
单链表只能从前向后遍历,无论是插入还是删除方法在链表中都要找到操作位置的前驱结点。
2.1 单链表的结构定义
val表示该结点中存储的数值,next属性保存下一个结点的地址,若没有下一个结点则为null。
size表示该链表中保存的有效元素个数,head 保存了第一个结点的地址。
public class MyLinkedList implements SeqList {
private int size;
private Node head;
private class Node {
private int val;
private Node next;
public Node(int val) {
this.val = val;
}
}
}
2.2 头插法
必须先让新结点先挂在原先头结点的前面,然后再让head指向新的结点。
public void addFirst(int val) {
Node node = new Node(val);
node.next = head;
head = node;
size ++ ;
}
2.3中间位置的插入
首先要排除不合法的情况,然后根据index的特殊索引选择头插法,然后剩下的情况再继续分析,首先需要遍历该列表停在index位置的前驱节点,然后将该位置的next指向新的node,将node的next修改为原位置的next,最后size++。
public void add(int index, int val) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("add index illegal!");
}
if (index == 0) {
addFirst(val);
return;
}
Node prev = head;
for (int i = 1; i <index; i++) {
prev = prev.next;
}
Node node = new Node(val);
node.next = prev.next;
prev.next = node;
size++;
}
2.4尾插法
尾插法不必全部写出,在按照索引插入中,index == size时就是尾插, 并且同样是有前驱节点,尾结点本来指向的就是空,所以新的尾结点也应该指向空。所以尾插法中写入 add(size,val)即可。
public void add(int val) {
add(size,val);
}
2.5遍历链表
不能直接使用head进行遍历,遍历一次之后,头结点的地址就找不到了。所以创建一个n使他等于head,用n进行遍历。
public String toString() {
StringBuilder sb = new StringBuilder();
for(Node n = head;n!=null;n = n.next){
sb.append(n.val);
sb.append("->");
if(n.next == null){
sb.append("NULL");
}
}
return sb.toString();
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addFirst(1);
myLinkedList.add(3);
myLinkedList.add(5);
myLinkedList.addFirst(7);
myLinkedList.add(2,10);
System.out.println();
}
调用三种添加方法,尾插3,头插1,尾插5,头插7,在索引为3的位置插入4。最后的结果应为1,3,5,4,7。
2.6查询线性表中是否包含指定元素
创建一个Node类型的n使他等于head,然后遍历整个链表,当第一次遇见n的val等于指定元素,即返回true。如果遍历到最后还是没有,则返回false。遍历到最后意味着n==null。
public boolean contains(int val) {
for(Node n = head;n!= null;n = n.next){
if(n.val == val){
return true;
}
}
return false;
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.add(3);
myLinkedList.addFirst(1);
myLinkedList.add(5);
myLinkedList.add(7);
myLinkedList.add(3,4);
System.out.println(myLinkedList.toString());
System.out.println(myLinkedList.contains(40));
System.out.println(myLinkedList.contains(4));
}
2.7返回索引为index的元素值
首先要创建一个rangeCheck来判断index是否合法。然后遍历链表,找到索引为index的结点,返回该结点的值。
public int get(int index) {
if(!rangCheck(index)){
throw new IllegalArgumentException("get index illegal!");
}
Node node = head;
for(int i = 0;i<index;i++){
node = node.next;
}
return node.val;
}
private boolean rangCheck(int index) {
return index >= 0 && index < size;
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.add(3);
myLinkedList.addFirst(1);
myLinkedList.add(5);
myLinkedList.add(7);
myLinkedList.add(3,4);
System.out.println(myLinkedList.toString());
System.out.println(myLinkedList.get(2));
}
2.8修改索引为index位置的元素为新值,返回修改前的元素值
public int set(int index, int newVal) {
if(!rangCheck(index)){
throw new IllegalArgumentException("get index illegal!");
}
Node node = head;
for(int i = 0;i<index;i++){
node = node.next;
}
int temp = node.val;
node.val = newVal;
return temp;
}
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.add(3);