上記では、構造体の構造を通じてキューと循環キューを実現しましたが、私たちは他の教師の指導の中で、この構造体を使用してリンクリスト、キュー、スタックなどのデータ構造を実現することを初めて学んだかもしれません。この記事で皆さんに伝えたい配列の構造を使用してリンク リスト、単調スタック、単調キューを実装できるということです。
目次
序文
リンクリストの形は以前から気づいていましたが、これはそれを実現するための構造なのでしょうか?
typedef struct ListNode {
int data;
struct ListNode* next;
}List;
しかし、リンク リストをシミュレートするのに必要なのはこれら 2 つの配列だけだと言ったら、あなたは信じますか? !!
head 表示头节点
e[N] 表示存储结点数值的数组
ne[N] 表示结点的下一个结点的位置
idx 表示当前存储元素的位置 当前存储到哪里了就是
次に、単連結リストと二重連結リストを実装してみましょう。
1. 配列構造を使用する利点
日々の勉強の中で、先生に教えていただいたのは構造体で実装したリンクリストですが、例えば10万個のノードを作りたい場合、この場合は構造体を使うと時間がかかりすぎてしまいます。長くてスペースが大きすぎると、配列とは対照的に、非常に有利に見えます。
1. アルゴリズムに取り組むとき、配列の形式を使用してリンク リストをシミュレートすると計算速度が速くなり、アルゴリズムを作成したりゲームをしたりする子供に適しています。
2. 筆記試験が適切であれば、リンクリストの基本機能を作成して実現し、要素を挿入および削除し、添字などに従って必要な要素を直接検索する方が速くなります。
配列とリンク リストの長所と短所を見てみましょう。
1. アレイの長所と短所
配列について知る:
配列は線形構造であり、記憶空間はメモリ上で連続(物理的に連続)しているため、配列を作成する際には必ず指定されたサイズの空間を最初に適用する必要があります。(一度のお申込みでスペースのサイズを指定可能です)
アドバンテージ:
連続メモリの特性により配列のアクセス速度が非常に速く、インデックス添字以降はO(1)の時間計算量でのアクセスが実現できます。
欠点:
1. 任意の位置での削除と挿入を行う場合、一部の要素の移動が伴うため、配列の任意の位置での削除と挿入の時間計算量は O(n) です。
例えば:
1> 点 i の後にデータを挿入すると、i+1 の位置とそれに続く要素が必要になり、全体として 1 ビット後方に移動して (ループ操作の場合)、挿入されたデータを i+1 の位置に置きます
2> i 点以降の要素を削除するには、i+1 以降の要素を全体として 1 ビット移動し、総要素数を 1 つ減らす必要があります。
以上が配列のメリットとデメリットですが、配列はO(1)まではすぐにアクセスできますが、任意に要素を削除したり挿入したりすると、O(n)に到達するまでに時間がかかります。
2. リンクリストのメリットとデメリット
リンクされたリストを知る
1. リンク リストも線形構造ですが、その記憶領域は不連続 (物理的には不連続、論理的には連続) であり、リンク リストの長さは不確実であり、動的拡張をサポートします。要素を挿入するたびに、新しいノードを適用し、値を割り当ててリンク リストに挿入する必要があります。
利点:
データを挿入または削除するときに、ポインタの位置を変更するだけで済みます。配列のように部分全体を移動する必要がありません。プロセス全体に要素の移動が含まれないため、時間の複雑さが軽減されます。リンク リスト内の挿入および削除操作は O(1) です欠点:
任意の位置にあるノードの数値フィールドを探す場合、それを走査する必要があり、時間計算量は O(n) です。
ただし、任意の位置に要素を挿入または削除する場合は、指定された要素のノード位置を見つける必要があるため、結合リストの挿入と削除を組み合わせても O(n) です。
3. まとめ
配列またはリンク リストに関係なく、検索の時間計算量は O(n) であり、条件を満たすデータが見つかるまで検索を 1 つずつ実行する必要があります。与えられた場合、ポインタのアドレスは最初の要素を挿入および削除するだけです。N ビット要素が追加されると、結合された時間計算量は O(n) になります。
しかし、連結リストを配列形式で実装すると、指定した要素位置の挿入や削除が容易になるでしょうか? N 番目の位置に要素を挿入および削除すると、O(1) の時間計算量が直接求められます。そして、リンク リストの削除と挿入はすべて O(1) であるため、削除または挿入操作全体の全体的な時間計算量は O(1) となり、通常のリンク リストよりもはるかに高速になります。
次に、配列を使用してリンク リストを実装します。
1. 構造と初期化を理解する
まずは初期化の準備を図で理解しましょう
C++ は配列を定義するための変数をサポートしているため、C++ を使用すると理解しやすくなります。
初期化コード:
//使用c++更简单,先用c++的形式实现
const int N = 100010;
int head, e[N], ne[N], idx; //全局变量
void init() {
head = -1;
idx = 0;//进行初始化的操作,idx为当前链表中(数组)最后一个元素(末尾),下标位置
}
2. x をヘッドノードに挿入します。
リンクされたリストへのいわゆるヘッド挿入です。
グラフィック:
実際、プロセスの次のポインタが次のノードの値を記録する ne[N] 配列に置き換えられる点を除けば、通常のリンク リストのヘッダーと同じです。
コードは以下のように表示されます:
void add_to_head(int x) {
e[idx] = x;//将x数值存入到e[]数组中
ne[idx] = ne[head];//将idx新插入的结点的下一个位置存储到ne[idx]中 ,全局变量 ne以及n数组初始化为0
head = idx;
idx++;
}
3. k 番目に挿入された値の後の位置に x を挿入します
グラフィック:
問題を説明したいと思います。ne[N] 配列に格納されている値は、ノードの追加または削除、挿入または削除のいずれであっても繰り返されないため、管理する必要はありません。実際、e[ ne[head]] ヘッドノードの次のノードの値を取得するためのもので、ne[]配列は添字としてのみ使用されます。
k回目の挿入後の位置にxを挿入しますが、同時にこのときのidxが新たなk回目の挿入データの添え字となり、つまりk回目の挿入後の位置に再度挿入されます。値 x は、実際には最後に新しく挿入されたノードの後に挿入されます。
グラフィック:
コードは以下のように表示されます:
//在第k个插入的数字之后插入数据
void add(int k, int x) {
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
4. k回目に挿入したノードを削除します
//将下标为k的点后面的点删掉
void remove(int k, int ne[]) {
ne[k] = ne[ne[k]];//表示k的下一个位置(ne)为下一个位置的下一个位置,这样跳过了原来的ne[k]结点
}//使用的时候应该是 删除的是k之后的点
リンクされたリストと同様に、直接スキップするだけです
3. 完全なコードデモ
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
const int N = 100010;
int n, e[N], ne[N], idx, head;
//初始化
void init() {
head = -1;
idx = 0;
}
//头插
void add_to_head(int x) {
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
//在第k个插入的数字之后插入数据
void add(int k, int x) {
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
//删除第k的插入的数据
void remove(int k) {
ne[k] = ne[ne[k]];
}
int main()
{
init();
add_to_head(1);
add_to_head(2);
add_to_head(3);
add_to_head(4);
add_to_head(5);
add(2-1, 10);
add(2-1, 2);
add(2-1, 3);
add(2-1, 4);
add(2-1, 5);
add_to_head(50);
for (int i = head; i != -1; i=ne[i]) {
printf("%d ", e[i]);
}
printf("\n");
for (int i = head; i != -1; i = ne[i]) {
printf("%d ", ne[i]);
}
return 0;
}
第四に、配列は二重リンクリストを実現します。
1. 初期化
//初始化
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int m;
const int N = 100010;
int e[N], l[N], r[N], idx;
void init() {
//0表示为左端点,1表示为右端点
r[0] = 1;
l[1] = 0;
idx = 2;//从2开始
}
データを記録するには e[N] 配列を使用し、ノードの左右のポインタを表すために l[N]、r[N] 配列を使用します。最初に、0 は左の端点を表し、1 は右の端点を表します、次に r、r[N] l 2 つの配列レコード、値の添字は 2 から始まります
e[] はノードの値を示し、l[] はノードの左ポインタを示し、r[] はノードの右ポインタを示し、idx は現在使用されているノードを示します。
2. k 番目に挿入された点の右側に x を挿入します
//在第k次插入的点的右边插入x;
void add(int k, int x) {
e[idx] = x;//数值x给当前idx位置的e数组存储
r[idx] = r[k];//将新节点的左右两端分别连接k的后一个结点r[k]和k本身
l[idx] = k;
l[r[k]] = idx;//然后将k的右端点的左端点连接idx
r[k] = idx;//最后将k的右端点连接idx
}
3. k 番目の点を削除します
//删除第k个点
void remove(int k) {
//就是将k的左端点和右端点相互连接
l[r[k]] = l[k];
r[l[k]] = r[k];
}
5、完全なコード
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<assert.h>
//使用数组的形式实现双向链表
//e[N] 表示存储结点的数值
//l[N] 表示当前结点的左结点位置
//r[N] 表示当前结点的右节点位置
//idx 表示当前结点存储的位置
//初始化
int m;
const int N = 100010;
int e[N], l[N], r[N], idx;
void init() {
//0表示为左端点,1表示为右端点
r[0] = 1;
l[1] = 0;
idx = 2;//从2开始
}
//在第k次插入的点的右边插入x;
void add(int k, int x) {
e[idx] = x;//数值x给当前idx位置的e数组存储
r[idx] = r[k];//将新节点的左右两端分别连接k的后一个结点r[k]和k本身
l[idx] = k;
l[r[k]] = idx;//然后将k的右端点的左端点连接idx
r[k] = idx;//最后将k的右端点连接idx
}
//删除第k个点
void remove(int k) {
//就是将k的左端点和右端点相互连接
l[r[k]] = l[k];
r[l[k]] = r[k];
}