リニアテーブル
1. 線形テーブルの基本概念と実現
1.1 線形テーブルの定義
線形リストは、同じ特性を持つデータ要素の有限シーケンスです。
- 同じ特性を理解するにはどうすればよいですか? 限定?シーケンス?
同じ機能: すべての要素が同じデータ型に属します
。 制限: 要素の数が制限されています
。 シーケンス: シーケンスは特定の配置順序を参照せず、データ要素の挿入順序に従って保存されます。
シーケンスに含まれる要素の数は線形テーブルの長さと呼ばれ、n (n≧0) で表されます。n はゼロに等しくてもよく、これは線形リストが空のリストであることを示すことに注意してください。
線形リストは、学生の列と考えることができる単純なデータ構造です。
学生の数は線形テーブルの長さに対応し、学生の数は制限されています。これは、線形テーブルが有限数列であることを示します。これは、チームの全員が学生であることを示します。これは、線形テーブルのデータ要素が同じ特徴を持っています; 線形テーブル 順序付けすることも、順序付けしないこともできます。生徒が身長に従って前に背が低く、背が高い人が後ろに並ぶと、線形テーブルの秩序性が反映されます。
1.2 線形テーブルの論理的特性
引き続き、説明のために定義の例を取り上げます。学生のチームでは、チームの先頭には 1 人の学生だけがおり、チームの最後には 1 人の学生だけがいます。
列の先頭の生徒の前には他の生徒はなく、列の最後尾の生徒の後ろにも他の生徒はいません。
列の先頭と最後尾の生徒を除いて、生徒が 1 人おきの場合、直前と直後に立つ生徒は 1 人だけです。
同じことが線形テーブルにも当てはまります。ヘッダ要素と末尾要素は 1 つだけです。ヘッダ要素には先行要素がなく、末尾要素には後続要素がありません。ヘッダ要素と末尾要素を除き、他の要素には直接の先行要素が 1 つだけあり、直接の後継者は一人だけ。以上が線形テーブルの論理特性です。
次の図のデータの要素 3 の場合、その直前の先行要素は 2、直後の後継要素は 4 です。
1.3 リニアテーブルの記憶構造
線形テーブルの記憶構造には、シーケンシャル記憶構造とチェーン記憶構造の 2 種類があります。前者を逐次リスト、後者を連結リストと呼びます。以下の 2 つのストレージ構造を比較して紹介します。
(1) シーケンステーブル
シーケンシャル テーブルは、線形テーブル内のすべての要素を論理的な順序に従って、指定された格納場所から開始する連続した記憶空間に格納します。このように、線形リストの最初の要素の格納場所は指定された格納場所であり、 2 番目の要素の格納場所は1 番目の要素の格納場所のi+1
すぐ後ろになります。i
(2) リンクリスト
連結リストストレージでは、各ノードには、格納された要素の情報だけでなく、要素間の論理関係の情報も含まれます。たとえば、単一リンクリストの先行ノードには、後続ノードのアドレス情報が含まれます。アドレス情報により、後継ノードの位置がわかります。
(3) 2 つのストレージ構造の比較
シーケンス テーブルは、図 a に示す部屋の列のようなものです。各部屋の左側の数字は、部屋から点 0 までの距離であり、部屋番号も表します。部屋の長さは 1 です。したがって、0時の位置さえわかれば、部屋番号からどの部屋の位置をすぐに知ることができる、これがシーケンステーブルの第一の特徴であるランダムアクセス機能です。また、図aから、5つの部屋が占める土地は隣り合っている、つまり連続して空間を占めており、土地ブロックの数は6であることがわかります。その土地に新しい部屋を配置した場合旧部屋(シーケンステーブル操作)の場合、区画数は増減しません。これがシーケンス テーブルの 2 番目の特徴です。つまり、シーケンス テーブルには連続した記憶領域が必要です。ストレージの割り当ては事前にのみ行うことができ、一度割り当てられると、運用中は変更されません。
もう一度リンクリストを見ると、図bのように4つの部屋が点在しており、各部屋の右側には次の部屋への方向を示す矢印が付いています。したがって、最後の部屋に行きたい場合は、最初の部屋から開始して、最後の部屋に到達するために最初の 3 つの部屋を通過する必要があり、最後の部屋の位置を直接知ることはできません。リンク リストはランダムアクセスをサポートしません。また、図 b から、リンク リスト内の各ノードは、次のノードの場所へのポインタを保存するために領域の一部を割り当てる必要があることがわかります。そのため、リンク リスト内のノードの記憶領域使用率は、リンク リスト内のノードの記憶領域使用率がわずかに低いです。シーケンステーブルのそれ。リンクされたリスト内の現在のノードの位置は、初期位置に対する相対的なオフセットによって決定されるのではなく、その先行ノード内のアドレス情報によって示されます。したがって、リンクリストのノードはメモリ内のどこにでも分散することができ、すべてのノードが必要とするスペースをリンクリストに一度に分割する必要はなく、必要に応じていくつかのノードを一時的に分割する必要があります。リンクされたリストが記憶領域の動的割り当てをサポートしていることがわかります。
図 a に示すシーケンス テーブルの右端のテーブル ノード スペースは使用されません (シーケンス テーブルには新しいデータを挿入する余地がまだあります)。部屋 1 と部屋 2 の間に部屋を挿入する場合は、No の後の部屋.2 は 1 つの位置だけ戻す必要があります (部屋を自由に移動できると仮定して)。つまり、シーケンス テーブルを挿入するときに複数の要素を移動する必要があります。リンク リストの場合、これは必要ありません。図 b に示すリンク リストの場合、最初の部屋と 2 番目の部屋の間に新しい部屋を挿入する場合は、部屋の後ろにある方向矢印を変更するだけで済みます。新しく挿入された部屋の矢印は新しく挿入された部屋を指し、次に新しく挿入された部屋の矢印は 2 番目の部屋を指します。つまり、リンクされたリストへの挿入操作では要素を移動する必要はありません。
(4) リンクリストの5つの形式
1) 単一リンクリスト
データ フィールドに加えて、各ノードには、後続ノードを指すために使用されるポインター フィールドも含まれています。
ここでは、リーダー ノードのある単一リンク リストとリーダー ノードのない単一リンク リストを区別する必要があります。
①ヘッドノードとの単結合リストにおいて、ヘッドポインタheadはヘッドノードを指し、ヘッドノードの値フィールドには何も情報が含まれず、データ情報はヘッドノードの後継ノードから格納される。ヘッドポインタ head は常に NULL に等しくなく、head->next
NULL に等しい場合、リンクされたリストは空になります。
②先頭ノードのない単連結リストの先頭ポインタheadは開始ノードを直接指しており、headがNULLの場合は連結リストは空になります。
つまり、この 2 つの最も明らかな違いは、単一リンク リストの先頭ノードに情報を格納しないノードがあることです (リストの長さなど、リンク リストのプロパティを説明する一部の情報のみを格納します)。 ) ただし、記号としてのみ使用されますが、先頭ノードのない単一のリンク リストでは、リンク リストのすべてのノードに情報が格納されます。
2) 二重リンクリスト
単一リンク リストは開始ノードから終端ノードまでのみ移動できますが、終端ノードから開始ノードまで逆に移動することはできません。終端ノードから始点ノードまでデータ列を出力する必要がある場合、単結合リストでは非常に面倒な操作となる。この種の問題を解決するために、二重リンクリストが構築されます。
以下の図に示すように、先頭ノードを持つ二重リンクのリストです。二重リンク リストは、現在のノードの先行ノードを指すポインタ フィールドを単一リンク リストのノードに追加します。このようにして、後続ノードから前ノードを容易に見つけることができ、出力終端ノードから開始ノードまでのデータ列を実現することができる。
同様に、二重リンク リストも、単一リンク リストと同様に、先頭ノードのある二重リンク リストと先頭ノードのない二重リンク リストに分けられます。ヘッド ノードのある二重リンク リストのhead->next
場合、 が NULL の場合、リンク リストは空になります。ヘッド ノードのない二重リンク リストの場合、 head が NULL の場合、リンク リストは空になります。
3) 循環単一リンクリスト
単連結リストの構造を理解すると、循環単連結リストは比較的単純になります。単一リンク リストの最後のポインタ フィールド (ヌル ポインタ) をリンク リストの最初のノードにポイントするだけです (ヘッド ノードではなく最初のノードがここで言及されている理由は次のとおりです。循環単一リンク リストが最初のノードである場合)ノード ノードの場合、最後のノードのポインタ フィールドはヘッド ノードを指す必要があります。循環単一リンク リストにヘッド ノードがない場合、最後のポインタ フィールドは開始ノードを指す必要があります)。
循環単一リンク リストは、任意のノードから開始してリンク リスト内の任意のノードにアクセスできます。また、単一リンク リストは、任意のノードから開始した後、ノード自体とその背後にあるすべてのノードにのみアクセスできます。ヘッド ノードを持つ循環単一リンク リストの場合、head が head->next に等しい場合、リンク リストは空になります。ヘッドノードのない循環単一リンク リストの場合、head が NULL に等しい場合、リンク リストは空になります。。
次の図に示すように、これは先頭ノードを持つ循環的な単一リンク リストです。
以下の図に示すように、これはヘッド ノードのない循環的な単一リンク リストです。
4) 循環二重リンクリスト
循環シングルリンクリストと同様に、循環ダブルリンクリストの構築はダブルリンクリストから導出されます。つまり、終端ノードの次のポインタはリンクリストの最初のノードを指し、その前のポインタはリンクリストの最初のノードを指します。図に示すように、リンク リストの最初のノードは終端ノードを指します。
循環二重リンクリストにも先頭ノードと非先頭ノードがあります。head が NULL に等しい場合、head ノードのない循環二重リンク リストは空になります。head ノードを含む循環二重リンク リストには空の状態で null ポインタはなく、head と等しくなければなりませんhead->next
。head->prior
したがって、空であるかどうかを判断するには、head->next
と head, Prior の 2 つのポインタのいずれかが head ポインタと等しいかどうかを確認するだけで済みます。以下に示すように:
したがって、以下の4つの文のいずれかが真であれば、先頭ノードの循環二重連結リストは空であると判断できる。
head->next == head;
head->prior == head;
head->next == head && head->prior == head;
head->next == head || head->prior == head;
次の図に示すように、上記の 4 種類のリンク リストを 4 種類の道路と比較できます。
単一リンク リストは、図 a に示す一方通行車線に似ており、車両は一方向にのみ走行できます。二重リンク リストは、図 b に示す双方向車線に似ており、車両は左から走行できます。右へ、または右から左へ;ループ 単一リンク リストは図 c に示す一方通行の円形車線に似ており、車両はこの車線を一方向に走行できます。円形の二重リンク リストは双方向の円形車線に似ています。図 d に示すように、車両はこの車線内を 2 方向に走行できます。
5) 静的リンクリスト
次の図に示すように、静的リンク リストは 1 次元配列で表されます。
左側の図は静的リンク リスト、右側の図はそれに対応する一般的なリンク リストです。一般的なリンク リストのノード空間はメモリ全体から取得され、静的リンク リストは構造体配列から取得されます。配列内の各ノードには 2 つのコンポーネントが含まれています: 1 つはデータ要素コンポーネント データで、もう 1 つはポインター コンポーネントであり、配列内の現在のノードのすぐ後続ノードの位置を示します (これは、配列内のノードの位置と同じです)。一般的なリンク リストの次のポインタは同等です)。
注: 静的リンク リストのポインタは、通常呼ばれる C 言語でメモリ アドレスを格納するために使用されるポインタ変数ではなく、配列の添え字を格納する整数変数です。これにより、後続のノードの位置がわかります。配列を見つけることができ、その機能が実際のポインタに似ているため、ポインタと呼ばれます。
2. 線形テーブルの構造定義と基本操作
2.1 線形テーブルの構造定義
#define maxSize 100 // 这里定义一个整型常量maxSize,值为100
- シーケンステーブル構造定義
typedef struct
{
int data [maxSize]; // 存放顺序表元素的数组
int length; // 存放顺序表的长度
}Sqlist; // 顺序表类型的定义
下図に示すように、シーケンシャルテーブルにはテーブルの要素を格納する配列data[]
と要素数を示す変数が含まれますlength
。
- 単一リンクリストノードの定義
typedef struct LNode
{
int data; // data 中存放结点数据域
struct LNode *next; // 指向后继结点的指针
}LNode; // 定义单链表结点类型
次の図は、単一リンク リストのノード構造図を示しています。
- 二重リンクリストノード定義
typedef struct DLNode
{
int data; // data 中存放结点数据域
struct DLNode *prior; // 指向前继结点的指针
struct DLNode *next; // 指向后继结点的指针
}DLNode; // 定义双链表结点类型
次の図は、ダブル リンク リストのノード構造図を示しています。
2.2 シーケンステーブルの操作
(1) 要素値による検索アルゴリズム
順序付きリスト内で値が e に等しい最初の要素を検索し、そのインデックスを返します。
検索オペレーションコードは以下のとおりです。
int findElem (Sqlist L,int e)
{
int i;
for(i=0;i<L.length;++i)
if(e == L.data[i])
return i; // 若找到,则返回下标
return -1; // 没找到,返回-1,作为失败标记
}
(2) データ要素の挿入アルゴリズム
順序テーブル L の位置 p (0≦p≦length) に新しい要素 e を挿入します。p の入力が正しくない場合は、挿入に失敗したことを示す 0 が返され、p の入力が正しければ、順序表の p 番目の要素以降が 1 つ右に移動され、新しい要素が返されます。新しい要素を挿入するために要素が空になり、シーケンス テーブルの長さが増加します。1、挿入操作が成功した場合、1 を返します。
挿入オペレーションコードは次のとおりです。
int insertElem (Sqlist &L, int p, int e) // L本身要发生改变,所以用引用型
{
int i;
if (p<0 || p>L.length || L.length == maxSize) //位置错误或者表长已经达至
return 0; //顺序表的最大允许值,此时插入不成功,返回0
for(i=L.length-1;i>=p;--i)
L.data[i+1] = L.data[i]; // 从后往前,逐个将元素往后移动一个位置
L.data[p] = e; // 将e放在插入位置p上
++(L.length); //表内元素多了一个,因此表长自增1
return 1; //插入成功,返回1
}
(3) データ要素を削除するアルゴリズム
順序表 L の添え字 p (O ≦ p ≦ length-1) を持つ要素を削除し、成功した場合は 1 を返し、成功しなかった場合は 0 を返し、削除した要素の値を e に代入します。
分析します:
表中の添字 p の要素を削除するには、その後ろの要素を 1 つずつ前に移動して、p の位置の要素を上書きするだけで削除の目的は達成されます。挿入操作の要素の右シフトを要素の左シフトに変更するだけです。挿入操作で右に移動する場合は右端の要素から移動を開始する必要があり、削除操作で左に移動する場合は左端の要素から移動を開始する必要があると考えるのが自然です。
削除オペレーションコードは次のとおりです。
int deleteElem (Sqlist &L, int p,int &e) // 需要改变的变量用引用型
{
int i;
if (p<0 || p>L.length-1)
return 0; // 位置不对返回0,代表删除不成功
e=L.data[p]; // 将被删除元素赋值给e
for (i=p;i<L.length-1;++i) // 从p位置开始,将其后边的元素逐个前移一个位置
L.data[i] = L.data[i+1];
--(L.length); // 表长减 1
return 1; // 删除成功,返回1
}
2.3 単一リンクリストの操作
(1) 挿入動作
p がノードを指していると仮定すると、p が指すノードの後に s が指すノードを挿入する操作を次の図に示します。
その声明は次のとおりです。
s->next = p->next;
p->next = s;
(2) 削除操作
p がノードを指していると仮定すると、p が指すノードの後ろにある s が指すノードを削除する操作を次の図に示します。
その声明は次のとおりです。
p->next = s->next;
free(s); // 调用函数free()来释放s所指结点的内存空间
(3) テーブル作成操作
- 尾栓
尾栓
配列 a に既に n 個の要素が格納されていると仮定して、末尾挿入メソッドを使用してリンク リスト C を構築します。
void createlistR(LNode *&C,int a[], int n)
{
LNode *s,*r; //s用来指向新申请的结点,r始终指向C的终端结点
int i;
C=(LNode*)malloc(sizeof(LNode)); // 申请C的头结点空间
C->next=NULL;
r=C; //r指向头结点,因为此时头结点就是终端结点
for(i=0;i<n;++i)
{
s=(LNode*)malloc(sizeof(LNode)); //s指向新申请的结点
s->data=a[i]; //用新申请的结点来接收a 中的一个元素
r->next=s; //用r来接纳新结点
r=r->next; //r指向终端结点,以便接纳下一个到来的节点
}
r->next=NULL; //数组a中所有的元素都已装入链表C,C的终端结点指针域置为NULL,C建立完成
}
- ヘッド挿入
ヘッド挿入法による単一リンクリストを構築するプロセスを次の図に示します。
void createlistF(LNode *&C,int a[],int n)
{
LNode *s;
int i;
C=(LNode*)malloc(sizeof(LNode));
C->next=NULL;
for (i=0;i<n;++i)
{
s=(LNode*)malloc(sizeof(LNode));
s->data=a[i];
/*下边两句是头插法的关键步骤*/
s->next=C->next; //s所指新结点的指针域next指向C中的开始结点
C->next=s; //头结点的指针域next指向s结点,使得s成为新的开始
}
}
2.4 二重リンクリストの操作
(1) ノードの挿入
二重リンクリストの p が指すノードの後にノード s が挿入されたとします。
その動作ステートメントは次のとおりです。
s->next=p->next;
s->prior=p;
p->next=s;
s->next->prior=s; //假如p指向最后一个结点,则本行可去掉
ポインタの変更プロセスを次の図に示します。
(2) ノードの削除
二重リンクリスト内のノード p の後続ノードを削除するとします。
その動作ステートメントは次のとおりです。
q=p->next;
p->next=q->next;
q->next->prior=p;
free(q);
ポインタの変更プロセスを次の図に示します。