目录
一、链表的概念结构
链表:是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
如上图,每一个节点都会存储下一个节点的地址,那么就可以通过当前节点访问下一个节点,这些节点在逻辑上是连续的,但是在物理上不一定连续,因为每次申请的空间可能连续,可能不连续。
实际中,链表的结构非常多样:
1、单向或双向:
2、带头或不带头:
3、循环或非循环:
这些情况组合起来就有8种链表结构:
我们常用的是单向不带头非循环链表和双向不带头链表。
二、单向无头非循环链表的实现
public class SingleList {
private int usedSize;
static class ListNode {
public int val;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;//表示存储当前链表的头节点的引用
public void disPlay() {
ListNode ret = this.head;
while (ret != null) {
System.out.print(ret.val + " ");
ret = ret.next;
}
}
//查看链表中是否包含key
public boolean contains(int key) {
ListNode ret = this.head;
while (ret != null) {
if (ret.val == key) {
return true;
}
ret = ret.next;
}
return false;
}
public int size() {
/*Node ret = this.head;
int count = 0;
while (ret != null){
count++;
ret = ret.next;
}
return count;*/
return this.usedSize;
}
//头插法
public void addFirst(int data) {
ListNode node = new ListNode(data);
node.next = this.head;
this.head = node;
this.usedSize++;
}
//尾插法
public void addLast(int data) {
ListNode node = new ListNode(data);
ListNode ret = this.head;
if (head == null) {
head = node;
} else {
while (ret.next != null) {
ret = ret.next;
}
ret.next = node;
}
usedSize++;
}
private void checkIndex(int index) {
if (index < 0 || index > usedSize) {
throw new IndexNotLegalException("index位置不合法");
}
}
//任意位置插入
public void addIndex(int index, int data) {
checkIndex(index);
if (index == 0) {
addFirst(data);
}
if (index == usedSize) {
addLast(data);
}
ListNode node = new ListNode(data);
ListNode ret = head;
for (int i = 0; i < index - 1; i++) {
ret = ret.next;
}
node.next = ret.next;
ret.next = node;
usedSize++;
}
//删除第一次出现关键字为key的节点
public void remove(int key) {
if (head == null) {
return;
}
if (head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrevOfKey(key);
if (cur == null) {
return;
}
ListNode del = cur.next;
cur.next = del.next;
}
/**
* 找到关键字key的前驱
*
* @param key
* @return
*/
private ListNode searchPrevOfKey(int key) {
if (head == null) {
return null;
}
ListNode cur = head;
while (cur.next != null) {
if (cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}
//删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {
return;
}
ListNode prev = head;
ListNode cur = prev.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
usedSize--;
} else {
prev = cur;
cur = cur.next;
}
}
if (head.val == key) {
head = head.next;
usedSize--;
}
}
//清空链表
public void clear() {
//this.head = null;
ListNode cur = this.head;
while (cur != null) {
ListNode curNext = cur.next;
cur.next = null;
cur = curNext;
}
this.head = null;
}
}
三、双向无头非循环链表的实现
public class MyLinkedList {
//节点
public class ListNode{
public int val;
public ListNode next;
public ListNode prev;//记录当前节点的前驱
public ListNode(int data){
this.val = data;
}
}
public ListNode head;
public ListNode last;
//头插法
public void addFirst(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
this.last = node;
}else {
this.head.prev = node;
node.next = head;
head = node;
}
}
//尾插法
public void addLast(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
this.last = node;
}else {
this.last.next = node;
node.prev = this.last;
this.last = node;
}
}
private void checkIndex(int index){
if(index<0 || index>size()){
throw new RuntimeException("index坐标不合法");
}
}
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
checkIndex(index);
if(index == 0 || this.head == null){
addFirst(data);
return;
}
if(index == size()){
addLast(data);
return;
}
ListNode node = new ListNode(data);
ListNode cur = this.head;
for (int i = 1; i < index; i++) {
cur = cur.next;
}
node.next = cur.next;
node.prev = cur;
cur.next.prev = node;
cur.next = node;
}
//查找是否包含关键字key是否在双链表当中
public boolean contains(int key){
ListNode cur = this.head;
while (cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}
//删除第一次出现关键字为key的节点
public void remove(int key){
ListNode cur = this.head;
while (cur != null){
if(cur.val == key){
if(cur == this.head){
//删除头节点
this.head = this.head.next;
if(this.head != null){
//只有一个节点的情况
this.head.prev = null;
}
}else {
//删除中间节点
cur.prev.next = cur.next;
if(cur != this.last){
cur.next.prev = cur.prev;
}else {
//删除尾结点
this.last = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
//删除所有值为key的节点
public void removeAllKey(int key){
ListNode cur = this.head;
while (cur != null){
if(cur.val == key){
if(cur == this.head){
//删除头节点
this.head = this.head.next;
if(this.head != null){
//只有一个节点的情况
this.head.prev = null;
}
}else {
//删除中间节点
cur.prev.next = cur.next;
if(cur != this.last){
cur.next.prev = cur.prev;
}else {
//删除尾结点
this.last = cur.prev;
}
}
}
cur = cur.next;
}
}
//得到双链表的长度
public int size(){
ListNode cur = head;
int count = 0;
while (cur != null){
count++;
cur = cur.next;
}
return count;
}
public void display(){
ListNode cur = head;
while (cur != null){
System.out.println(cur.val);
cur = cur.next;
}
}
public void clear(){
/*this.head = null;
this.last = null;*/
ListNode cur = this.head;
while (cur != null){
if(cur.next != null) {
cur = cur.next;
cur.prev.next = null;
if(cur.prev != null) {
//处理头节点
cur.prev.prev = null;
}
}else {
//处理尾巴节点
this.head = null;
this.last = null;
cur = null;
}
}
}
}
四、LinedList的遍历
1、for循环遍历
2、for each遍历
3、使用迭代器正向遍历
4、使用迭代器反向遍历
五、ArrayList和LinedList的区别
1、存储空间
ArrayList在逻辑上是连续的,并且在物理内存上也是连续存储的;
LinkedList在逻辑上是连续的,但是在物理内存上不一定是连续的。
2、随机访问
ArrayList支持随机访问,并且时间复杂度为O(1);
LinkedList不支持随机访问,访问链表内元素需要遍历整个链表,时间复杂度为O(N)。
3、从头插入元素
ArrayList使用头插法效率较低,需要搬移元素,如果空间不够,还需要扩容,时间复杂度为O(N);
LinkedList只需修改引用的指向,没有容量的概念,不需要扩容,时间复杂度为O(1)。
4、应用场景
当元素需要频繁访问或高效存储时,ArrayList更适用;
当元素需要频繁删除或从任意位置插入时,LinkedList更适用;