LinkedList とリンクされたリスト
序文
このブログでは主に以下の内容についてお話します
- ArrayList の欠陥
- リンクされたリスト
- リンクリスト関連のoj
- LinkedList のアナログ実装
- LinkedListの使用
- ArrayList と LinkedList の違い
ArrayList の欠陥
前回のブログでは ArrayList の使い方について説明しましたが、ArrayList の最下層は配列を使って要素を格納していることが分かりましたが、最下層は
連続
した空間なので、ArrayList 内の任意の位置に要素を挿入したり削除したりする場合は、後続の要素を移動する必要があります。移動すると、時間計算量は O(n) であり、効率が比較的低いため、ArrayList は、任意の位置での挿入と削除が多数あるシナリオには適していません。したがって、LinkedList は Java コレクション、つまりリンク リスト構造に導入されます。
リンクされたリスト
リンクリストの概念と構造
リンク リストは、物理的な記憶構造における不連続な記憶構造であり、データ要素の論理的順序は、リンク リスト内の参照リンクの順序によって実現されます。
実際のリンク リストの構造は非常に多様で、次の状況を組み合わせると 8 つのリンク リスト構造になります。
- 一方向または双方向
- 先頭ノードまたは先頭ノードではない
- 循環または非循環リンクリスト
主に2つの連結リスト構造について説明します。
- ヘッドレス一方向非巡回リンクリスト
结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等
- ヘッドレス二重リンク リスト: Java のコレクション フレームワーク ライブラリの LinkedList の基礎となる実装は、ヘッドレス二重リンク リストです。
リンクリストの実装
import java.util.List;
public class MySingleList {
static class ListNode {
public int val;//存储的数据
public ListNode next;//存储下一个节点的地址
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;// 代表当前链表的头节点的引用
public void createLink() {
ListNode listNode1 = new ListNode(12);
ListNode listNode2 = new ListNode(45);
ListNode listNode3 = new ListNode(23);
ListNode listNode4 = new ListNode(90);
listNode1.next = listNode2;
listNode2.next = listNode3;
listNode3.next = listNode4; /* */
head = listNode1;
}
/**
* 遍历链表
*/
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();
}
/**
* 从指定位置开始打印链表
* @param newHead
*/
public void display(ListNode newHead) {
//如果说 把整个链表 遍历完成 那么 就需要 head == null
// 如果说 你遍历到链表的尾巴 head.next == null
ListNode cur = newHead;
while (cur != null) {
System.out.print(cur.val+" ");
cur = cur.next;
}
System.out.println();
}
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur = head;
while (cur != null) {
if(cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得到单链表的长度 O(N)
public int size(){
int count = 0;
ListNode cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//头插法 O(1)
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;
}
//任意位置插入,第一个数据节点为0号下标
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;
}
/**
* 找到 index-1位置的节点的地址
* @param index
* @return
*/
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位置不合法");
}
}
//删除第一次出现关键字为key的节点 O(N)
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;
}
/**
* 找到关键字key的前一个节点
* @param key
* @return
*/
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的节点
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;
}
}
/**
* 保证链表当中 所有的节点 都可以被回收
*/
public void clear() {
head = null;
//StringBuilder sb = "fdfa";
}
}
説明: リンク リストの演習用のブログがあります。構造を質問の種類から分離するようにしてください。
LinkedListの使用
LinkedList の公式ドキュメント
https://docs.oracle.com/javase/8/docs/api/java/util/LinkedList.html
LinkedList の最下層は双方向のリンク リスト構造です (リンク リストで後述します)。リンク リストは要素を連続空間に格納しないため、要素は別々のノードに格納され、ノード間は参照によって接続されます。なので、任意の位置に挿入や削除を行うことができます。要素を追加する際、要素を移動する必要がなく、比較的効率が高くなります。
コレクション フレームワークでは、LinkedList は List インターフェイスも実装します。
- LinkedList は List インターフェイスを実装します
- LinkedList の最下層は二重リンク リストを使用します
- LinkedList は RandomAccess インターフェイスを実装していないため、LinkedList はランダム アクセスをサポートしていません
- LinkedList 内の任意の位置での要素の挿入と削除はより効率的であり、時間計算量は O(1) です。
- 任意の位置に挿入するシーンにはLinkedListの方が適しています
LinkedList の他の一般的なメソッドの紹介
方法 | 説明する |
---|---|
ブール値 add(E e) | テールプラグe |
void add(int インデックス, E 要素) | インデックス位置に e を挿入します |
boolean addAll(Collection<? extends E> c) | cに要素を挿入します |
E 削除(int インデックス) | インデックス位置要素を削除する |
ブール値の削除(オブジェクトo) | 最初に見つかった o を削除します |
E get(int インデックス) | 添え字のインデックス位置要素を取得します |
E set(int インデックス, E 要素) | 添字インデックス位置要素を要素に設定します |
ボイドクリア() | 空の |
ブール値には (オブジェクト o) が含まれます | o が線形表にあるかどうかを判断します |
intindexOf(オブジェクトo) | 最初の o のインデックスを返します |
int lastIndexOf(オブジェクト o) | 最後の o の添え字を返します |
リスト subList(int fromIndex, int toIndex) | インターセプトパーツリスト |
LinkedList の走査
// foreach遍历
for (int e:list) {
System.out.print(e + " ");
}
// 使用迭代器遍历---正向遍历
ListIterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.print(it.next()+ " ");
}
// 使用反向迭代器---反向遍历
ListIterator<Integer> rit = list.listIterator(list.size());
while (rit.hasPrevious()){
System.out.print(rit.previous() +" ");
}
ArrayList と LinkedList の違い
配列リスト | リンクリスト |
---|---|
物理的に連続 論理的に連続 | 物理的に連続していない |
ランダム アクセスは O(1) をサポートします | ランダムアクセスはO(N)をサポートします |
O(N)を挿入 | 挿入(1) |
効率的なストレージ + アプリケーション シーン要素への頻繁なアクセス | 任意の位置での頻繁な挿入と削除 |