前言
hello,大家好,我是阿旭啊。今天给大家带来数据结构第五篇:链表。希望能给大家一些小帮助~
链表是一种物理存储结构上非连续 存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 就像滴滴滴的小火车一样~
火车是车厢,在链表里叫做节点(结点)。
一.结点
一个结点分两个部分,上面部分为value域,存的是数值,下面部分为next域,存的是下一个数据的地址。最后一个结点的next域为null。
头结点:第一个节点叫做头结点,用head记录。
尾结点:最后一个结点叫做尾结点,
二.链表的种类
链表共八种:
我们主要学习单向不带头非循环和双向不带头非循环。
1.单向不带头非循环
2.单向不带头循环
将尾结点的next换成头结点的地址就是循环。
3.单向带头非循环
带头:头结点value为空。插入数据时候只能插在头结点之后。
不带头:头结点value不为空,可以在头结点之前插入数据,此时插入的数据成为新的头结点。
4.双向不带头非循环
双向链表比单向链表多了一个prev域,这个域存的是前一个数据的地址。
三.链表的方法(单向不带头非循环链表)
// 1、无头单向非循环链表实现
public class SingleLinkedList {
//头插法
public void addFirst(int data){ }
//尾插法
public void addLast(int data){ }
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){ }
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){ return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key){ }
//删除所有值为key的节点
public void removeAllKey(int key){ }
//得到单链表的长度
public int size(){ return -1;
}
public void clear() { }
public void display() {} }
四.链表的实现(单向不带头非循环链表)
首先,我们知道链表里面包含节点,节点里面包含value域和next域。这就很像外部类包含内部类,内部类包含变量一样。所以我们可以将其设置成内部类的形式。内部类为节点listnode包括int类型的value和引用对象listnode的next,外部类名为MySingleList:
public class MySingleList {
class ListNode {
public int value;
public ListNode next;
public ListNode(int value) {
this.value = value;
}
}
}
头结点是包含在链表当中的,所以我们在链表类中定义一个头结点,只要知道了头结点就可以根据next来找到之后的所有节点。
public ListNode head;
节点的类搞定之后,我们就可以写创建节点的createList方法了。结点的value值可以自己设定。next存储的是下一个节点的地址,而引用对象所保存的就是地址,所以直接将node2存入node1的next中就可以了。
public void createList(){
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}
然后我们写打印列表的函数show,我们先打印出来头结点的value值,然后将下一个节点设成头结点再次打印,直到头结点的地址为null时停止。如何将下一个节点设置成头结点呢,我们只需要将head引用到下一个节点的地址,也就是head.next。但是当我们打印完后会发现头结点已经没了,也就是说我们只能调用一次这个函数。这肯定是不行的,所以我们需要找一个head的替身来代替他去消失:
public void show(){
ListNode cur = head;
while (cur != null){
System.out.print(cur.value + " ");
cur = cur.next;
}
}
然后我们写得到列表长的的函数size。和打印链表相同,我们只有cur不为null就说明有一个节点,那么我们就可以定义一个计数器count加一,直到cur为null为止(当cur为null时,cur已经将最后一个结点遍历过去了):
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
然后我们来写头插法addFirst:在链表最前面插入一个元素并且将其设置为头节点。首先需要创建一个新的节点node,然后将原来的头结点的地址存到node的next中,最后将node设置成新的头结点:
public void addFirst(int data){
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
然后我们来写尾插法addLast:在链表的最后面插入一个元素。首先还是创建一个新的结点node,然后我们需要遍历到最后一个节点,也就是将cur指向最后一个结点。如果像上面两个方法遍历链表的话,cur指向的是最后一个结点的后面一个空节点,所以我们的循环终止条件要变一下。当cur.next等于null时,cur指向的就是最后一个结点。这时候cur.next等于null,我们只需要将新添加的元素的地址存进去就可以完成添加元素至最后了。
public void addLast(int data){
ListNode node = new ListNode(data);
ListNode cur = head;
while(cur.next != null){
cur = cur.next;
}
//cur指向的节点就是尾结点
cur.next = node;
}
然而当我们向空链表使用addLast方法的时候,我们会发现报错:空指针异常。原因是head为空导致cur为空,导致循环出错。所以我们需要单独来判断一次空链表的情况:
public void addLast(int data){
ListNode node = new ListNode(data);
if (head == null){
head = node;
return;
}
ListNode cur = head;
while(cur.next != null){
cur = cur.next;
}
//cur指向的节点就是尾结点
cur.next = node;
}
我们发现,当我们完成这两个添加元素之后,我们已经可以直接在空链表上面直接添加了,已经不再需要我们写的第一个createList方法了,它被淘汰了,所以我们的代码中就不用出现这个方法了。
下面我们写任意位置插入一个结点addIndext方法。既然涉及到了位置,那么第一步就是判断位置是否合法。当位置不合法的时候,我们扔出一个异常:IndexOutBoundsException:
public class IndexOutBoundsException extends RuntimeException{
public IndexOutBoundsException() {
}
public IndexOutBoundsException(String message) {
super(message);
}
}
如果我们要插入到三号位置,那么就要先找到二号位置的节点,然后把二号节点的next给到新添结点的next,把新添结点的地址给了二号完成插入。此时我们会发现,如果新添0号位置,找不到0号位置前面的节点。所以我们要把0号位置单独拿出来:
public void addIndex(int index, int data){
//0.判断未知的合法性
if (index < 0 || index > size()){
throw new IndexOutBoundsException("获取数值时候,位置不合法");
}
if (index == 0){
addFirst(data);
return;
}
//1.先找到index - 1位置的节点
ListNode node = new ListNode(data);
ListNode cur = head;
for (int i = 0;i < index - 1; i++){
cur = cur.next;
}
//2.进行插入
node.next = cur.next;
cur.next = node;
}
然后我们写判断链表中是否有key值contains方法。只需要遍历一遍链表,如果有一个节点的value值等于key返回true,如果没有就返回false
public boolean contains(int key){
ListNode cur = head;
while(cur != null){
//如果value是引用类型,那么需要用equals来进行比较
if (key == cur.value){
return true;
}
cur = cur.next;
}
return false;
}
然后我们写删除第一次出现的key的节点函数remove。首先我们找到我们要找的key的前一个位置的节点。使用while循环,当cur.next.value等于key的时候跳出循环,此时cur表示的就是前一个位置。然后我们把key的下一位置的地址传到cur的next中就完成了删除。此时我们会发现如果head的value就等于key,是没有前一个位置的。所以我们需要将这种情况单独分析。同样的,如果是空链表,直接返回就好了。如果没有我们要删除的数据的话,最后cur.next.value的值为null,此时打印没有你要查找的数据:
//删除第一次出现关键字为key的节点
public void remove(int key){
if (head == null){
return;
}
if (head.value == key){
head = head.next;
return;
}
ListNode cur = head;
while(cur.next.value != key){
cur = cur.next;
if (cur.next == null){
System.out.println("没有你要删除的数据:" + key);
return;
}
}
cur.next = cur.next.next;
}
然后我们写删除所有值为key的结点removeAllKey方法:第一种情况:空链表,直接返回。第二种情况:head的数值不是key。我们定义cur为head的替身,cur1为head.next的替身。当cur1的数值不为key时候,cur1往后移动一位(cur1 = cur1.next),当cur1的数值为key时候,将cur1.next放到cur.next中达到删除cur1的效果,此时cur1往后移动一位,再次进行上述判断,直到cur1为null。但是cur1是从第二个节点开始判断,头结点没有判断,所以我们在单独判断一次头结点是否需要删除。第三种情况:没有要删除的节点,我们可以设定一个特征值a。一开始a为0,当判断存在key值的时候a等于1 ,最后判断a的值来确定有没有key的值:
public void removeAllKey(int key){
if (head == null){
return;
}
ListNode cur = head;
ListNode cur1 = head.next;
int a = 0;
while (cur1 != null){
if (cur1.value == key){
cur.next = cur1.next;
a = 1;
}else{
cur = cur1;
}
cur1 = cur1.next;
}
if (head.value == key){
head = head.next;
return;
}
if (a == 0){
System.out.println("没有你要删除的数据:" + key);
}
}
最后我们写清空链表的代码,很简单直接头结点置空就可:
//清楚链表
public void clear() {
head = null;
}
}
完整代码:
自定义异常:
public class IndexOutBoundsException extends RuntimeException{
public IndexOutBoundsException() {
}
public IndexOutBoundsException(String message) {
super(message);
}
}
链表类:
public ListNode head;
// public void createList(){
// ListNode node1 = new ListNode(12);
// ListNode node2 = new ListNode(23);
// ListNode node3 = new ListNode(34);
// ListNode node4 = new ListNode(45);
// ListNode node5 = new ListNode(56);
// node1.next = node2;
// node2.next = node3;
// node3.next = node4;
// node4.next = node5;
// this.head = node1;
// }
//遍历单链表
public void show(){
ListNode cur = head;
while (cur != null){
System.out.print(cur.value + " ");
cur = cur.next;
}
System.out.println();
}
//得到链表长度
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
//判断链表中是否有key值
public boolean contains(int key){
ListNode cur = head;
while(cur != null){
//如果value是引用类型,那么需要用equals来进行比较
if (key == cur.value){
return true;
}
cur = cur.next;
}
return false;
}
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
//尾插法
public void addLast(int data){
ListNode node = new ListNode(data);
if (head == null){
head = node;
return;
}
ListNode cur = head;
while(cur.next != null){
cur = cur.next;
}
//cur指向的节点就是尾结点
cur.next = node;
}
//任意位置插入一个结点:第一个节点的序号为0
public void addIndex(int index, int data){
//0.判断未知的合法性
if (index < 0 || index > size()){
throw new IndexOutBoundsException("获取数值时候,位置不合法");
}
if (index == 0){
addFirst(data);
return;
}
//1.先找到index - 1位置的节点
ListNode node = new ListNode(data);
ListNode cur = head;
for (int i = 0;i < index - 1; i++){
cur = cur.next;
}
//2.进行插入
node.next = cur.next;
cur.next = node;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
if (head == null){
return;
}
if (head.value == key){
head = head.next;
return;
}
ListNode cur = head;
while(cur.next.value != key){
cur = cur.next;
if (cur.next == null){
System.out.println("没有你要删除的数据:" + key);
return;
}
}
cur.next = cur.next.next;
}
//删除所有值为key的节点
public void removeAllKey(int key){
if (head == null){
return;
}
ListNode cur = head;
ListNode cur1 = head.next;
int a = 0;
while (cur1 != null){
if (cur1.value == key){
cur.next = cur1.next;
a = 1;
}else{
cur = cur1;
}
cur1 = cur1.next;
}
if (head.value == key){
head = head.next;
return;
}
if (a == 0){
System.out.println("没有你要删除的数据:" + key);
}
}
//清楚链表
public void clear() {
head = null;
}
}
测试代码:
public class test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
System.out.println("=============================");
// mySingleList.createList();
mySingleList.addLast(12);
mySingleList.addLast(23);
mySingleList.addLast(34);
mySingleList.addLast(45);
mySingleList.addLast(56);
mySingleList.show();
System.out.println("=============================");
System.out.println(mySingleList.size());
System.out.println("=============================");
System.out.println(mySingleList.contains(12));
System.out.println(mySingleList.contains(67));
System.out.println("=============================");
mySingleList.addFirst(99);
mySingleList.show();
System.out.println("=============================");
mySingleList.addIndex(0, 10);
mySingleList.addIndex(2, 10);
mySingleList.addIndex(4, 10);
mySingleList.addIndex(6, 10);
mySingleList.show();
System.out.println("=============================");
mySingleList.removeAllKey(10);
mySingleList.removeAllKey(100);
mySingleList.show();
System.out.println("=============================");
mySingleList.clear();
mySingleList.show();
System.out.println("=============================");
}
}
运行结果:
=============================
12 23 34 45 56
=============================
5
=============================
true
false
=============================
99 12 23 34 45 56
=============================
10 99 10 12 10 23 10 34 45 56
=============================
没有你要删除的数据:100
99 12 23 34 45 56
=============================
=============================
总结
好啦~本篇文章就到此结束。希望大家能够多多点赞,多多评论,多多批评,大家下一篇再见!!!