顺序存储:顺序表/ArrayList
由于ArrayList的缺点,我们引入链式存储:链表
链表由多种结构:由头、无头、单向、双向、循环、非循环
排列组合后有八种,重点了解无头单向非循环链表和无头双向链表
无头单向非循环链表的实现
public class MySingleList {
//链表是由一个一个的结点所组成的,可以把Node定义成一个内部类
static class Node {
public int val;//存储的数据
public Node next;//存储下一个结点的地址
public Node(int val) {//先不设置next,创建一个新的结点时,还不知道下一个结点是什么
this.val = val;
}
}
}
链表是由一个个的结点所组成的,可以把Node定义成一个内部类
public Node head;//代表当前链表的头结点的引用
head 代表当前链表的头结点的引用
public void creatList(){//创建一个链表
Node node1 = new Node(12);
Node node2 = new Node(86);
Node node3 = new Node(33);
Node node4 = new Node(45);
node1.next =node2;
node2.next =node3;
node3.next =node4;
head=node1;//创建头结点
}
创建各个结点,给定val值,每个结点的next依次引用下一个结点的地址
确定头结点,head引用node1所引用的地址
public void disPlay() {//不要改变head
Node cur = head;//定义一个cur,让cur移动,head不动
//链表遍历完,head==null
//遍历到尾部,不打印最后一个,head.next==null
while (cur!=null){
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
1.通过cur来代替head,保证head不会发生改变
2.循环的判断依据是cur!=null,这样能遍历完整
cur.next!=null会少打印最后一个
public boolean contains(int key) {
Node cur = head;
while (cur != null) {//当cur为空时,遍历完
if (cur.val == key) {
return true;
}
cur = cur.next;//cur向后移动
}
return false;
}
public int size(){
int count = 0;
Node cur = head;
while (cur!=null) {//遍历一遍链表 o(n)
count++;
cur=cur.next;
}
return count ;
}
遍历数组,直到cur为null跳出循环,说明已经走完整个链表,返回记录的次数
public void addFirst(int data){
Node node = new Node(data);//新建一个结点
node.next=head;
head = node;
}
1.创建新的结点
2.将新结点的next域存放原本头结点的地址值
3.让新结点成为新的头结点
这样就完成了头插法的实现
public void addList(int data) {
Node node = new Node(data);
if (head == null) {//判断头结点为空的情况
head = node;//让新建的结点成为头节点
return;//
}
Node cur = head;//让cur代替head
while (cur.next != null) {
cur = cur.next;
}//当cur的next为null时,找到了最后一个结点
//找到最后一个结点后,插入node结点
cur.next = node;
}
1.创建一个新结点
2.判断头结点是否为空
3.遍历链表找到当前最后一个结点
4.将新结点插到末尾
链表的插入只是修改指向
public void addIndex(int index, int data) throws IndexOutOfException {
checkIndex(index);//先检查index的值是否合法
if (index == 0) {//如果index=0,调用头插法
addFirst(data);
return;
}
if (index == size()) {//如果index的值为链表长度,调用尾插法
addList(data);
return;
}
Node cur = findIndexSubOne(index);//找到index的前一个元素
Node node = new Node(data);
node.next = cur.next;
cur.next = node;
}
private void checkIndex(int index) {
if (index < 0 || index > size()) {
throw new IndexOutOfException("index位置不合法");
}
}
//走index-1步,返回当前索引的地址
private Node findIndexSubOne(int index) {
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
1.调用checkIndex方法先检查index的值是否合法,不合法抛出异常
2.判断索引,如果是0,调用头插法,如果等于链表长,调用尾插法
3.调用findIndexSubOne方法,找到index的前一个节点,返回地址值
4.创建一个新结点,使新结点的next域的值等于前一个结点next域的值
5.再让前一个结点的next域,引用新结点的地址值。
public void remove(int key) {
if (head == null) {//判断是否是空节点
return; //一个结点都没有
}
if (head.val == key) {//如果当前头结点的元素等于要删除的元素
head = head.next;//头结点向后移动
return;
}
Node cur = searchPrev(key);//找到key的前驱结点
if (cur == null) {//没有要删除的key
return;
}
Node del = cur.next;//要删除的结点
cur.next = del.next;
//or cur.next = cur.next.next;
}
private Node searchPrev(int key) {//找到key的前一个结点
Node cur = head;
while (cur.next != null) { //cur.next==null,说明cur已经走到最后一个结点
if (cur.next.val == key) {//如果cur下一个结点的值等于key
return cur;//找到key的前一个结点
}
cur = cur.next;
}
return null;//没有你要删除的结点
}
1.先定义searchPrev方法,遍历链表,如果cur的下一个结点的值=key,cur就为key的前驱结点
2.判断:如果头结点为空,链表中没有元素,直接返回
3.如果头结点的值,就是要删除的元素,头结点后移,直接返回
4.都不是,进入searchPrev方法,返回前驱结点cur;
5.如果返回的cur==null,说明遍历完链表,没有要删除的元素
6.要删除的结点del就是cur的下一个结点
7.让cur结点的next域引用del的下一个结点的地址;
完成删除
删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {//如果头结点为空,直接返回
return ;
}
Node prev = head;
Node cur = head.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
//cur = cur.next;
} else {
prev = cur;
//cur = cur.next;
}
cur = cur.next;
}
if (head.val == key) {//最后处理头结点
//如果头结点的值等于key,头结点后移
head = head.next;
}
}
1.判断头结点是否为空
2.设置prev为头结点,cur为下一个结点(头结点key的情况最后考虑)
3.遍历链表,直到cur==null为止
4.如果cur的值等于key,prev的next域引用cur下一个结点的地址,cur后移一位
5.否则,prev移动到cur,cur后移一位
6.最后处理头结点,如果头结点刚好等于key,将头结点后移一位。
public void clear(){//清空,让链表中所有的结点都不被引用
head =null;
}
清空,让链表中所有的结点都不被引用,head置为空
点击移步博客主页,欢迎光临~