前言:
大家好,我是良辰丫,今天我和大家一起了解一下数据结构中的链表,链表,顾名思义是用链子把一个个数据串连起了的,那么链表和顺序表又有什么不同呢?我们慢慢往下看。
个人主页:良辰针不戳
所属专栏:java数据结构
励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
期待大家三连,关注,点赞,收藏。
作者能力有限,可能也会出错,欢迎大家指正。
愿与君为伴,共探Java汪洋大海。
链表由一个个节点串起来,其中节点又包括数据域和指针域,节点如下图所示。
下图为一个简单的单链表图,可能画的不好,嘿嘿嘿,大家见谅哈。其中数据域存放数据,指针域存放下一节点的地址,通过地址就可以间接访问下一节点。
接下来我们就来搞一下单链表的基本操作,让大家更加深刻的了解链表。一般而言,我们经常使用无头的链表,我们今天就拿无头的链表进行举例。注意我们说的头节点是傀儡节点,就是它不存储链表的数据,只是一个哨兵作用。
先实例化一个新节点。
新节点与单链表建立联系。
head指向新的头结点。
大家看了上面的单链表头插法是不是感觉没那么难呢?嘿嘿嘿,只要找到了规律,其实一切都不是那么难。现在呢,大家需要记住一个先绑定后面的思想,这样防止前面链条断了无法串连起来链表。
public void addFirst(int data){
ListNode listNode = new ListNode(data);
listNode.next = head;
head = listNode;
}
- 同样也是先申请一个头结点,注意,数据结构是很严谨的,需要把很多情况都考虑到。
- 头结点为空,说明链表没有元素,插入元素的节点直接成为了头结点。
- 链表尾插法的空间复杂度为O(n),需要去遍历找到尾巴,才可以进行插入,相比于顺序表,不用担心它的容量不足。
尾节点与新节点进行连接。
public void addLast(int data){
ListNode listNode = new ListNode(data);
if(head == null) {
head = listNode;
return;
}
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = listNode;
}
- 插入的位置如果不合法,直接抛出异常(但是在牛客网等做题网站不能这样,会被当做错误处理)。
- 在首位置插入元素,直接调用头插法,在尾位置插入元素,直接调用尾插法。
- 如果是在中间进行插入,找到要插入的前一个位置,这里我们用cur进行遍历,先进行右边绑定,然后再连接左边。
public void addIndex(int index,int data)
throws ListIndexOutOfException{
checkIndex(index);
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
ListNode cur = findIndexSubOne(index);
ListNode listNode = new ListNode(data);
listNode.next = cur.next;
cur.next = listNode;
}
private ListNode findIndexSubOne(int index) {
ListNode cur = head;
int count = 0;
while (count != index-1) {
cur = cur.next;
count++;
}
return cur;
}
private void checkIndex(int index) throws ListIndexOutOfException{
if(index < 0 || index > size()) {
throw new ListIndexOutOfException("index位置不合法");
}
}
这个也是一个简单的遍历,找到关键字,返回true,否则就返回false。
public boolean contains(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
- 如果链表没有数据,直接return。
- 如果头结点的数据与关键字相同,头结点直接后移一位。
- 找到要删除节点的前驱节点,没有要删除的节点返回空。
- 直接cur.next = del.next,有的小伙伴可能会有疑问,一个指令就可以删除嘛。那么它原来的指向呢?就像给变量赋值,cur.next给它赋予了新的指向,del.next或许还指向地址为0x112的节点,但是在访问链表的时候已经访问不到del的节点,如果大家觉得不合适可以把del.next置为空。
public void remove(int key){
if(head == null) {
return ;//一个节点都没有
}
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
return;
}
ListNode del = cur.next;//要删除的节点
cur.next = del.next;
}
//找到要删除的前驱节点
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;//没有你要删除的节点
}
- 删除所有值为key的节点,这里我们使用双指针思想。
- 头结点为空时直接退出,让prev指向头结点,cur指向head.next,然后进行遍历,找到关键字的时候就进行上面的删除操作,如果没有找到,指针后移,直到遍历完链表。
public void removeAllKey(int key){
if(head == null) {
return;
}
/*while(head.val == key) {
head = head.next;
}*/
ListNode prev = head;
ListNode cur = head.next;
while (cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(head.val == key) {
head = head.next;
}
}
通过遍历记录单链表长度。
//得到单链表的长度 O(N)
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
单链表是通过头结点进行遍历的,头结点置为空时就找不到后面的节点了。
public void clear() {
head = null;
}
遍历打印单链表。
public void display() {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
后序:
今天的单链表就讲到这里,希望大家有一定的收获,以路为马,不负韶华,朝着新的彼岸前行,加油。