1. 二重リンクリストの概要
二重リンク リスト:最初から最後まで横断するか、終わりから始まりまでトラバースします。言い換えれば、リンク リスト接続のプロセスは 双方向であり、その実装原理は次のとおりです。 ノードは 順方向です。連結参照、および後方連結参照。
二重リンクリストの欠点:
- でノードを挿入または削除するたびに、2 つではなく 4 つの参照を処理する必要があり、実装が困難になります。 . 一部;
- 一方向リンク リストと比較して、より多くのメモリ スペースを占有します;
- ただし、これらの欠点は、二重リンク リストの利便性に比べれば取るに足らないものです。
二重リンクリストの構造:
- 二重リンク リストには、最初のノードを指すヘッドポインタだけでなく、 tailポインタは最後のノードを指します。
- 各ノードは 3 つの部分で構成されます: アイテムストレージ データ、prev< a i =4> は前のノードを指し、next は次のノードを指します。
- 二重リンク リストの最初のノードの前のノードはnullを指しています。
- next は二重リンク リストの最後のノードを指しますnull;
二重リンクリストに対する一般的な操作 (メソッド):
- append(element): リンクされたリストの末尾に新しい項目を追加します。
- inset(position,element): リンクされたリストの特定の位置に新しい項目を挿入します。
- get(element): 対応する位置の要素を取得します。
- IndexOf(element): リンク リスト内の要素のインデックスを返します。リンク リストに要素がない場合は -1 を返します。
- update (position, element): 特定の位置にある要素を変更します。
- RemoveAt(position): リンクされたリストの特定の位置から項目を削除します。
- isEmpty(): リンク リストに要素が含まれていない場合は trun を返し、リンク リストの長さが 0 より大きい場合は false を返します。
- size(): 配列の長さ属性と同様に、リンクされたリストに含まれる要素の数を返します。
- toString(): リンクされたリスト項目は Node クラスを使用するため、JavaScript オブジェクトから継承されたデフォルトの toString メソッドをオーバーライドして、要素の値のみを出力するようにする必要があります。
- forwardString(): 前方トラバーサル ノード文字列形式を返します。
- backwordString(): 逆に通過したノードの文字列形式を返します。
2. 二重リンクリストクラスのカプセル化
2.0. 二重リンクリストクラスの作成
まず、二重リンク リスト クラス DoubleLinklist を作成し、基本属性を追加してから、二重リンク リストの一般的なメソッドを実装します。
//封装双向链表类
function DoubleLinklist(){
//封装内部类:节点类
function Node(data){
this.data = data
this.prev = null
this.next = null
}
//属性
this.head = null
this.tail ==null
this.length = 0
}
2.1.append(要素)
コード:
//append方法
DoubleLinklist.prototype.append = data => {
//1.根据data创建新节点
let newNode = new Node(data)
//2.添加节点
//情况1:添加的是第一个节点
if (this.length == 0) {
this.tail = newNode
this.head = newNode
//情况2:添加的不是第一个节点
}else {
newNode.prev = this.tail
this.tail.next = newNode
this.tail = newNode
}
//3.length+1
this.length += 1
}
プロセスの詳細な説明:
ノードを追加するときは、さまざまな状況が考えられます。
- ケース 1: 最初のノードが追加されます。先頭と末尾が新しいノードを指すようにするだけです。
-
ケース 2: 以下の図に示すように、追加されるのは最初のノードではありません。関連する参照のポイントを変更するだけで済みます。
- パス: newNode.prev = this.tail: 1 を指すように確立します。
- パス: this.tail.next = newNode: 2 へのポインタを確立します。
- パス: this.tail = newNode: 3 へのポインタを作成します
指す変数の順序の変更に注意し、末尾が常に変更前の元のリンク リストの最後のノードを指すように、最後に指す末尾を変更します。
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试append方法
list.append('aaa')
list.append('bbb')
list.append('ccc')
console.log(list);
試験結果:
- 次の方向:
- 前の方向:
2.2.toString()の概要
コード:
//将链表转变为字符串形式
//一.toString方法
DoubleLinklist.prototype.toString = () => {
return this.backwardString()
}
//二.forwardString方法
DoubleLinklist.prototype.forwardString = () => {
//1.定义变量
let current =this.tail
let resultString = ""
//2.依次向前遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.prev
}
return resultString
}
//三.backwardString方法
DoubleLinklist.prototype.backwardString = () => {
//1.定义变量
let current = this.head
let resultString = ""
//2.依次向后遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.next
}
return resultString
}
プロセスの詳細な説明:
文字列を取得する 3 つの方法:toString()、forwardString()< a i=4>、**backwardString()** の実装原則は同様ですが、backWardString メソッドのみが例として使用されます。
- 現在指しているノードを記録するために現在の変数を定義します。まず、current が最初のノードを指すようにし、次に current = current.next を逆方向にトラバースします。 while ループでは、(current) が current! である限り、リンク リストを走査する条件として使用されます。 = null はトラバースを続けるため、リンクされたリスト内のすべてのノードのデータを取得できます。
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试字符串方法
list.append('aaa')
list.append('bbb')
list.append('ccc')
console.log(list.toString());
console.log(list.forwardString());
console.log(list.backwardString());
試験結果:
2.3.insert(位置,要素)
コード:
//insert方法
DoubleLinklist.prototype.insert = (position, data) => {
//1.越界判断
if (position < 0 || position > this.length) return false
//2.根据data创建新的节点
let newNode = new Node(data)
//3.插入新节点
//原链表为空
//情况1:插入的newNode是第一个节点
if (this.length == 0) {
this.head = newNode
this.tail = newNode
//原链表不为空
}else {
//情况2:position == 0
if (position == 0) {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
//情况3:position == this.length
} else if(position == this.length){
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
//情况4:0 < position < this.length
}else{
let current = this.head
let index = 0
while(index++ < position){
current = current.next
}
//修改pos位置前后节点变量的指向
newNode.next = current
newNode.prev = current.prev
current.prev.next = newNode
current.prev = newNode
}
}
//4.length+1
this.length += 1
return true//返回true表示插入成功
}
プロセスの詳細な説明:
ノードの挿入は、次のようなさまざまな状況に分類できます。
元のリンク リストが空の場合:
- ケース 1: 挿入された新しいノードはリンク リストの最初のノードです。先頭と末尾の両方が newNode を指すようにするだけで済みます。
元のリンク リストが空でない場合:
- ケース 2: 位置 == 0 の場合、次の図に示すように、リンク リストの先頭にノードが追加されます。
まず、this.head.prev = newNode を渡し、1 を指すように変更します。
次に、 newNode.next = this.head を渡し、ポイントを 2 に変更します。
最後に、this.head = newNode を渡し、ポイントを 3 に変更します。
- ケース 3:position == this.length、つまり、次の図に示すように、リンク リストの末尾にノードを追加します。
まず、 this.tail.next = newNode を渡し、ポイントを 1 に変更します (ここでは this.tail が this.head ではなく、元のリンク リストの最後のノードを指すために使用されていることに注意してください。 length>1 の場合、 this.head != this.tail.)
次に、 newNode.prev = this.tail を渡し、ポイントが 2 に変更します。
最後に、 this.tail = newNode を渡し、ポイントが 3 に変更します。
- ケース 4: 0 <position <this.length、つまり、次の図に示すように、新しいノードが位置 = 1 に挿入されると仮定して、リンク リストの中央に新しいノードを挿入します。
まず、変数 current を定義する必要があります。前のアイデアに従って、while ループを通じて位置の次のノードを見つけます。ループが終了すると、index = Position
以下の図に示すように、position = 1 の場合、電流は Node2 を指します。このように、動作電流は Node2 を間接的に動作させることと等価であり、Node1 も current.prev を通じて間接的に取得できます。 newNode の前のノードと次のノードを取得した後、それらの prev 変数と next 変数のポイントを変更することで newNode を挿入できます。
パス: newNode.next = current、ポイントを 1 に変更します。
パス: newNode.prev = current.prev、ポイントを 2 に変更します。
パス: current.prev.next = newNode、ポイントを 3 に変更します。
current.prev のポイントは最後に変更する必要があることに注意してください。そうしないと、操作する必要がある Node1 を current.prev から取得できなくなります。
パス: current.prev = 現在、ポイントを 4 に変更します。
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试insert方法
list.insert(0, '插入链表的第一个元素')
list.insert(0, '在链表首部插入元素')
list.insert(1, '在链表中间插入元素')
list.insert(3, '在链表尾部插入元素')
console.log(list);
alert(list)
試験結果:
2.4.get(位置)
コード:
//get方法
DoubleLinklist.prototype.get = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
//获取元素时position不能等于length
return null
}
//2.获取元素
let current = null
let index = 0
//this.length / 2 > position:从头开始遍历
if ((this.length / 2) > position) {
current = this.head
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while(index-- > position){
current = current.prev
}
}
return current.data
}
プロセスの詳細な説明:
current と Index の 2 つの変数を定義し、前述の考え方に従って、取得する必要がある位置の後のノードが見つかるまで while ループのトラバーサルを通じて現在のノードと対応するインデックス値 Index を取得します。このとき、index = pos =x 、そして現在の .data を返すだけで十分です。
リンクリスト内のノード数が多い場合、この検索方法は効率的ではありません。改善方法は次のとおりです。
リンク リスト内のノード数を取得するには this.length を使用する必要があります。そうしないと、エラーが報告されます。
- this.length / 2 > 位置の場合: 先頭 (head) からトラバースを開始します。
- this.length / 2
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试get方法
list.append('a')
list.append('b')
list.append('b1')
list.append('b2')
list.append('b3')
list.append('b4')
list.append('b5')
list.append('b6')
list.append('b7')
console.log(list.get(0));
console.log(list.get(7));
試験結果:
2.5.indexOf(要素)
コード:
//indexOf方法
DoubleLinklist.prototype.indexOf = data => {
//1.定义变量
let current = this.head
let index = 0
//2.遍历链表,查找与data相同的节点
while(current){
if (current.data == data) {
return index
}
current = current.next
index += 1
}
return -1
}
プロセスの詳細な説明:
(current) を条件として使用し、while ループを通じてリンク リスト内のすべてのノードを走査します (停止条件は current = null です)。各ノードをトラバースするときに、current が指す現在のノードのデータと受信データを比較します。
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试indexOf方法
list.append('a')
list.append('b')
list.append('c')
console.log(list.indexOf('a'));
console.log(list.indexOf('c'));
試験結果:
2.7.update(位置,要素)
コード:
//update方法
DoubleLinklist.prototype.update = (position, newData) => {
//1.越界判断
if (position < 0 || position >= this.length) {
return false
}
//2.寻找正确的节点
let current = this.head
let index = 0
//this.length / 2 > position:从头开始遍历
if (this.length / 2 > position) {
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while (index -- > position) {
current = current.prev
}
}
//3.修改找到节点的data
current.data = newData
return true//表示成功修改
}
プロセスの詳細な説明:
(index++
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试update方法
list.append('a')
list.append('b')
console.log(list.update(1, 'c'));
console.log(list);
試験結果:
2.8.removeAt(位置)
コード:
//removeAt方法
DoubleLinklist.prototype.removeAt = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
return null
}
//2.删除节点
//当链表中length == 1
//情况1:链表只有一个节点
let current = this.head//定义在最上面方便以下各种情况返回current.data
if (this.length == 1) {
this.head = null
this.tail = null
//当链表中length > 1
} else{
//情况2:删除第一个节点
if (position == 0) {
this.head.next.prev = null
this.head = this.head.next
//情况3:删除最后一个节点
}else if(position == this.length - 1){
current = this.tail//该情况下返回被删除的最后一个节点
this.tail.prev.next = null
this.tail = this.tail.prev
}else{
//情况4:删除链表中间的节点
let index = 0
while(index++ < position){
current = current.next
}
current.next.prev = current.prev
current.prev.next = current.next
}
}
//3.length -= 1
this.length -= 1
return current.data//返回被删除节点的数据
}
プロセスの詳細な説明:
ノードを削除するときは、いくつかの状況が考えられます。
リンク リストの長さが 1 の場合:
- ケース 1: リンク リスト内のすべてのノードを削除します。リンク リストの先頭と末尾が null を指すようにするだけです。
リンク リストの長さが 1 を超える場合:
-
ケース 2: リンクされたリストの最初のノードを削除します。
パス: this.head.next.prev = null、1 を指すように変更します。
パス: this.head = this.head.next、ポイントを 2 に変更します。
Node1 には他のノードを指す参照がありますが、Node1 を指す参照はないため、Node1 は自動的にリサイクルされます。
-
ケース 3: リンクされたリストの最後のノードを削除します。
パス: this.tail.prev.next = null、1 を指すように変更します。
パス: this.tail = this.tail.prev、2 を指すように変更します。
- ケース 4: リンク リストの中央にあるノードを削除します。
while ループを通じて削除する必要があるノード (position = x など) を検索すると、次の図に示すように、削除する必要があるノードは Node(x+1) になります。
パス: current.next.prev = current.prev、1 を指すように変更します。
パス: current.prev.next = current.next、2 を指すように変更します。
このようにすると、Node(x+1) を指す参照はなくなります (ただし、current は Node(x+1) を指しますが、current は一時変数であり、メソッドの実行後に破棄されます)。その後、Node(x +1) は自動的に削除されます。
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
//2.测试removeAt方法
list.append('a')
list.append('b')
list.append('c')
console.log(list.removeAt(1));
console.log(list);
試験結果:
2.9. その他の方法
その他のメソッドには次のものがあります。remove(element)、isEmpty()、size()、getHead()、getTail()
コード:
/*--------------------其他方法-------------------*/
//八.remove方法
DoubleLinklist.prototype.remove = data => {
//1.根据data获取下标值
let index = this.indexOf(data)
//2.根据index删除对应位置的节点
return this.removeAt(index)
}
//九.isEmpty方法
DoubleLinklist.prototype.isEmpty = () => {
return this.length == 0
}
//十.size方法
DoubleLinklist.prototype.size = () => {
return this.length
}
//十一.getHead方法:获取链表的第一个元素
DoubleLinklist.prototype.getHead = () => {
return this.head.data
}
//十二.getTail方法:获取链表的最后一个元素
DoubleLinklist.prototype.getTail = () => {
return this.tail.data
}
テストコード:
//测试代码
//1.创建双向链表
let list = new DoubleLinklist()
/*------------其他方法的测试--------------*/
list.append('a')
list.append('b')
list.append('c')
list.append('d')
//remove方法
console.log(list.remove('a'));
console.log(list);
//isEmpty方法
console.log(list.isEmpty());
//size方法
console.log(list.size());
//getHead方法
console.log(list.getHead());
//getTead方法
console.log(list.getTail());
試験結果:
2.10. 完全な実装
//封装双向链表
function DoubleLinklist(){
//封装内部类:节点类
function Node(data){
this.data = data
this.prev = null
this.next = null
}
//属性
this.head = null
this.tail ==null
this.length = 0
//常见的操作:方法
//一.append方法
DoubleLinklist.prototype.append = data => {
//1.根据data创建新节点
let newNode = new Node(data)
//2.添加节点
//情况1:添加的是第一个节点
if (this.length == 0) {
this.tail = newNode
this.head = newNode
//情况2:添加的不是第一个节点
}else {
newNode.prev = this.tail
this.tail.next = newNode
this.tail = newNode
}
//3.length+1
this.length += 1
}
//二.将链表转变为字符串形式
//2.1.toString方法
DoubleLinklist.prototype.toString = () => {
return this.backwardString()
}
//2.2.forwardString方法
DoubleLinklist.prototype.forwardString = () => {
//1.定义变量
let current =this.tail
let resultString = ""
//2.依次向前遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.prev
}
return resultString
}
//2.3.backwardString方法
DoubleLinklist.prototype.backwardString = () => {
//1.定义变量
let current = this.head
let resultString = ""
//2.依次向后遍历,获取每一个节点
while (current) {
resultString += current.data + "--"
current = current.next
}
return resultString
}
//三.insert方法
DoubleLinklist.prototype.insert = (position, data) => {
//1.越界判断
if (position < 0 || position > this.length) return false
//2.根据data创建新的节点
let newNode = new Node(data)
//3.插入新节点
//原链表为空
//情况1:插入的newNode是第一个节点
if (this.length == 0) {
this.head = newNode
this.tail = newNode
//原链表不为空
}else {
//情况2:position == 0
if (position == 0) {
this.head.prev = newNode
newNode.next = this.head
this.head = newNode
//情况3:position == this.length
} else if(position == this.length){
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
//情况4:0 < position < this.length
}else{
let current = this.head
let index = 0
while(index++ < position){
current = current.next
}
//修改pos位置前后节点变量的指向
newNode.next = current
newNode.prev = current.prev
current.prev.next = newNode
current.prev = newNode
}
}
//4.length+1
this.length += 1
return true//返回true表示插入成功
}
//四.get方法
DoubleLinklist.prototype.get = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
//获取元素时position不能等于length
return null
}
//2.获取元素
let current = null
let index = 0
//this.length / 2 > position:从头开始遍历
if ((this.length / 2) > position) {
current = this.head
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while(index-- > position){
current = current.prev
}
}
return current.data
}
//五.indexOf方法
DoubleLinklist.prototype.indexOf = data => {
//1.定义变量
let current = this.head
let index = 0
//2.遍历链表,查找与data相同的节点
while(current){
if (current.data == data) {
return index
}
current = current.next
index += 1
}
return -1
}
//六.update方法
DoubleLinklist.prototype.update = (position, newData) => {
//1.越界判断
if (position < 0 || position >= this.length) {
return false
}
//2.寻找正确的节点
let current = this.head
let index = 0
//this.length / 2 > position:从头开始遍历
if (this.length / 2 > position) {
while(index++ < position){
current = current.next
}
//this.length / 2 =< position:从尾开始遍历
}else{
current = this.tail
index = this.length - 1
while (index -- > position) {
current = current.prev
}
}
//3.修改找到节点的data
current.data = newData
return true//表示成功修改
}
//七.removeAt方法
DoubleLinklist.prototype.removeAt = position => {
//1.越界判断
if (position < 0 || position >= this.length) {
return null
}
//2.删除节点
//当链表中length == 1
//情况1:链表只有一个节点
let current = this.head//定义在最上面方便以下各种情况返回current.data
if (this.length == 1) {
this.head = null
this.tail = null
//当链表中length > 1
} else{
//情况2:删除第一个节点
if (position == 0) {
this.head.next.prev = null
this.head = this.head.next
//情况3:删除最后一个节点
}else if(position == this.length - 1){
current = this.tail//该情况下返回被删除的最后一个节点
this.tail.prev.next = null
this.tail = this.tail.prev
}else{
//情况4:删除链表中间的节点
let index = 0
while(index++ < position){
current = current.next
}
current.next.prev = current.prev
current.prev.next = current.next
}
}
//3.length -= 1
this.length -= 1
return current.data//返回被删除节点的数据
}
/*--------------------其他方法-------------------*/
//八.remove方法
DoubleLinklist.prototype.remove = data => {
//1.根据data获取下标值
let index = this.indexOf(data)
//2.根据index删除对应位置的节点
return this.removeAt(index)
}
//九.isEmpty方法
DoubleLinklist.prototype.isEmpty = () => {
return this.length == 0
}
//十.size方法
DoubleLinklist.prototype.size = () => {
return this.length
}
//十一.getHead方法:获取链表的第一个元素
DoubleLinklist.prototype.getHead = () => {
return this.head.data
}
//十二.getTail方法:获取链表的最后一个元素
DoubleLinklist.prototype.getTail = () => {
return this.tail.data
}
}
3. リンクリスト構造の概要
一方向リンク リストには head と next という 2 つの属性があり、二重リンク リストには head、tail、next、prev という 4 つの属性があります。それらのポインティングを処理することは、それらを正しく接続してチェーンを形成することと同じであり、これは単純なリンク リストの実装です。
リンク リストは実際の開発でよく使用されます。たとえば、Java のLinkList は二重リンク リストです。
3.1.注意点
- リンクされたリストでは、current = current.next は、左から右に current --> current.next と見なすことができます。つまり、current は current の次のノードを指します。
- ノード削除の原則: オブジェクトに他のオブジェクトへの参照があるかどうかに関係なく、オブジェクトへの参照がない限り、オブジェクトはリサイクル (削除) されます。
- パラメータに位置があるものはすべて範囲外と判断する必要があります。
3.2. リンクリストの追加、削除、変更、問い合わせ
二重リンク リストを例に挙げます。リンク リストの追加、削除、変更は、リンク リスト内の対応するノードを取得し、ポインティングを変更することに他なりません。その中の prev 変数と next 変数の。
- ケース 1: 頭 と 尾< のみです。必要な /span>0 。 0 <= length <=2 2 つの変数は操作が必要な変数を取得できます (ここでは簡単に取得できるという意味です。もちろん、head.next.next... を通じて目的のノードを取得したいとします)。または tail.prev.prev... も可能です)、この場合はリンク リストの長さ:
- ケース 2: 操作する必要がある変数を取得するために tail と head に依存できない場合は、while ループ トラバーサルを使用して、操作する必要があるノードを見つけることができます。 :
この場合、リンク リストの位置 = x に新しいノードを挿入したい場合は、current を通じて位置 Node(x+1) の後のノードを取得し、current.prev Node( を通じて位置にある前のノードを取得できます。 x); 次に、Node(x+1) および Node(x) の prev 変数と next 変数のポインティングを変更して、位置 pos=x に新しいノードを挿入します。
3.3. リンクリスト参照点の変更
newNode によって参照されるポインターを最初に変更し、次に他の参照を変更する必要があります。
- 状況 1: 操作する必要があるノードが先頭参照と末尾参照を通じて取得できる場合、最終的に先頭変数または末尾変数のポインティングが変更されます (それぞれリンク リストの最初と最後のノードを指すため、それらは変更される可能性があります)。他のノードを取得するために必要です)。
- ケース 2: current を使用して操作する必要があるノードを取得する場合、最後に current.next または current.prev のポインタを変更します。以下の図に示すように、current.next と current.prev は 2 つのノード Node(x+2) と Node(x) を表すため、これらのポイントを変更すると、Node(x) または Node(x+2) を取得できなくなります。 )、
3.4. リンクされたリストをたどる
2 つの横断アイデアを蓄積する
- 指定された位置 = x 位置にある次のノードとインデックス値を取得します。
ループ終了後、index = Position = xとなり、変数currentはNode(x+1)を指し、変数indexの値はNode(x+1)のインデックス値xになります。
- リンクされたリスト内のすべてのノードを走査します。
current.next = null で、current がリンク リストの最後のノードを指している場合、ループを停止します。