第 2 章 データ構造とアルゴリズム - 線形テーブル

2.1 線形テーブルの概要

目次

2.1 線形テーブルの概要

 2.1.1. 線形テーブルの定義

2.1.2. 線形テーブルの関連プロパティ

2.2 線形テーブルの逐次記憶構造 - 逐次テーブル

2.2.1. 配列リストの概念

2.2.2. シーケンスリストの長所と短所

2.2.3. シーケンステーブル(静的、動的)の分類とコード定義

2.2.4. シーケンステーブルの基本操作(ポイント)


 2.1.1. 線形テーブルの定義

(1).線形テーブルは、同じ特性を持つ n (n>=0) 個のデータ要素の有限シーケンスであり、n は線形テーブルの長さ、つまりデータ要素の数を表します。以下に示すように:

2.1.2. 線形テーブルの関連プロパティ

(1) n=0 の場合は空のリストを意味し、n>0 の場合は通常 (a1, a2, a3...an) と記録され、a1 は線形リストの最初の要素を意味し、an は線形リストの最初の要素を意味します。尾部要素。

(2) このうち、 a(m-1) はa(m) の直接の先行者であり、 a(m) はa(m-1) の直接の後継者です。

(3)一意の後続要素のみを有するa1と一意の先行要素を有するanを除き、他の要素は一意の先行要素と後続要素のみを有する。

(4) 線形テーブルの論理構造の模式図:

 (5) 各データ要素の具体的な意味は、線形テーブルによって異なり、数値、記号、レコード、またはその他のより複雑な情報である場合もあります。

2.2 線形テーブルの逐次記憶構造 - 逐次テーブル

2.2.1. 配列リストの概念

(1) メモリ上に連続した記憶空間を設け、連続した記憶単位の集合を用いてデータ要素を順番に記憶するこの記憶方法を線形テーブルの逐次記憶構造、または略してシーケンシャルリスト呼びます一般に配列はストレージに使用され、データの追加、削除、検索などの操作は配列上で完了します。

(2) シーケンシャル記憶構造の特徴論理的に隣接するデータ要素は物理的位置も隣接している。

(3) 線形テーブルのデータ要素は同じ性質を持っているため、テーブルの i 番目の要素の格納アドレスを求めることが容易です

テーブル内の各要素が m 個のストレージ ユニットを占有し、最初に占有されたストレージ ユニットのストレージ アドレスがデータ要素のストレージ アドレスとして使用されると仮定すると、テーブル内の i 番目のデータの格納場所は次のようになります。

                                              LOC(ai)=LOC(a1)+(i-1)*m

LOC(a1) は、テーブルの最初のデータ要素 a1 の格納場所であり、線形テーブルの開始位置またはベース アドレスと呼ばれることがよくあります。

注: 線形テーブルではデータ要素のベース アドレスとサイズ m を決定するだけでよいため、テーブル内の任意の要素の格納アドレスは上記の式に従って計算でき、したがって、シーケンシャル テーブル内の任意の要素を次のように計算できます。ランダムにアクセスされますしたがって、線形テーブルの逐次記憶構造はランダム アクセス記憶構造です

(4) 数列テーブルと配列の違い: 配列は連続空間ですが、どの空間にも値を入れることができますが、数列テーブルは異なります。数列テーブルも連続空間ですが、データは順番に保存する必要があります

2.2.2. シーケンスリストの長所と短所

(1)メリット:

①:要素へのランダムアクセスは実装が容易であり、上記の位置決め式に従ってテーブル内の各要素の格納場所を決定するのが容易なので、i ノードを指定すると便利です。

②:シンプルで直感的。

(2)デメリット:
①:ノードの挿入と削除が難しい: テーブル内のノードが連続して格納されているため、ノードを挿入または削除する場合、挿入ポイント以降のノードを順番に後ろに移動するか、削除後のノードを移動する必要があります。ポイントが順番に前に移動します。

②:柔軟性のない拡張(静的) : テーブルを作成する際、テーブルの最大長を見積もることができない場合、確実な領域の確保が難しく、拡張に影響を与えます。

③:無駄が発生しやすい: 割り当てられた領域が大きすぎると、予約された領域が無駄になります。

2.2.3. シーケンステーブル(静的、動的)の分類とコード定義

シーケンス テーブルは通常、次の 2 つのカテゴリに分類されます。

(1).静的シーケンス テーブル:

コードは次のように定義されます。

#define N 100
typedef int SLDatatype;
struct Seqlist
{
	SLDatatype q[N];//开辟的连续空间
	int size;//有效数据个数
};

説明する:

①: N は開放される総スペースであり、変更を容易にするために定義マクロで定義します。

②: 異なる型のデータを保存しやすくするために、typedef を使用して型の名前を変更します。

③: 配列 q は開いた連続空間です。

④:size は有効なデータの数、つまり現在のテーブルに存在する要素の数です。

注: 静的シーケンス テーブルにはいくつかの欠点があります。静的シーケンス テーブルのような定義はハードコーディングされており、100 の N では十分ではない可能性があります。N 200 を与えたとしても、使用できるのは 10 個だけであり、スペースの重大な無駄です。そのため、通常は動的シーケンスリストという定義方法を使用します。

(2)動的シーケンステーブル:

コードは次のように定義されます。

typedef int SLDatatype;
typedef struct Seqlist
{
	SLDatatype* q;//指向动态开辟的数组
	int size;//有效数据个数
	int capicity;//容量空间大小
}SL;

説明する:

①: 型の名前変更は上記と同じです。

②: ここで定義したポインタ q は、動的に開かれた配列を指すために使用されます。

③:size は有効データ数、つまり連続して表示できるデータ数です。

④: Capicity は総容量スペース サイズであり、1 つは現在の最大スペースを記録するため、もう 1 つはその後のスペースの拡張を容易にするためです。

2.2.4. シーケンステーブルの基本操作(ポイント)

(1)、初期化関数

コードは次のように実装されます。

void SLInit(SL* ps)
{
	ps->capicity = 5;
	ps->size = 0;
	ps->q = (SLDatatype*)malloc(sizeof(SLDatatype) * 5);
	if (ps->q == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
}

説明する:

①: 仮パラメータは構造体ポインタであり、構造体オブジェクトのアドレスを受け取るために使用されます。仮パラメータは実パラメータの一時的なコピーであるため、全体の操作はアドレスによる呼び出しです。

②: 空のテーブルに初期化されるため、サイズは 0 に設定されます。初期容量は、テーブルがいっぱいかどうかを判断するために使用する必要があり、その後、次の目的にも使用できるため、最初に開いた領域と同じ大きさにする必要があります。シーケンシャル表スペースのサイズを拡張します。拡張後の容量は、新しいスペースと同じ大きさになります。

③: 動的シーケンステーブルはその名のとおり、連続した空間を動的に開くもので、ここでは malloc関数を使って動的に空間を開き、ポインタ q をそこに指すようにしています。実際の状況。

④: 通常は malloc 関数を使用した後、空間がオープンできたかどうかを確認しますが、オープンに失敗した場合は empty が返されるので、これを判定条件として使用します。友達はそれについて自分で学ぶことができます。

(2)スペースの解放 (破棄) 機能:スペースを動的に開放するため、使用後はスペースをオペレーティング システムに戻す必要があります。

コードは次のように実装されます。

void Sldestroy(SL* ps)
{
	free(ps->q);
	ps->q = NULL;
	ps->capicity = ps->size = 0;
}

次に挿入操作ですが、ここでの挿入は先頭挿入法と末尾挿入法に分けられ、同様に削除も先頭削除法、末尾削除法とも呼ばれます。

ここで注意すべき点は、データを挿入するときは、シーケンス テーブルがいっぱいかどうかを最初に判断する必要があることです。また、末尾と先頭の両方の挿入方法を判断する必要があるため、次のように、このための別の関数を作成します。

(3)テーブルがいっぱいかどうかを判断する関数:
コードは次のように実装されます。

void SLCheckCapicity(SL* ps)
{

	if (ps->size == ps->capicity)
	{
		SLDatatype* tmp = (SLDatatype*)realloc(ps->q, ps->capicity * 2 * sizeof(SLDatatype));
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		ps->q = tmp;
		ps->capicity *= 2;
	}
}

説明する:

①: 数列表には配列が含まれており、配列には添字が含まれているため、線形表の知識については、以下に示すように、より多くの図を描いて分析することをお勧めします。

シーケンステーブルが満杯の場合はsize==capicityとなることがわかり、ifの判定条件として利用できます。

 ②: 判定条件が成立したとき、つまりテーブルがいっぱいになったとき、領域を拡張する必要がありますが、ここでは realloc 関数を使用します (各パラメータが何を表しているのか、realloc 関数を自分で学習することをお勧めします)持つ関数と戻り値)。

ここでは先ほどの容量拡張機能を利用しますが、通常一度容量を拡張すると2倍、つまり元の容量の2倍に拡張され、同時に容量*=2で拡張後の容量が記録されます。スペースサイズです。

③: ここでなぜ新しいポインタ変数 tmp を作成して空間を拡張し、その tmp をポインタ q に直接代入しているかというと、realloc 関数の機能によるもので、realloc 関数は自分で学習することができます。

(4)末尾挿入方法: 名前の通り、末尾に要素を挿入します。

コードは次のように実装されます。

void SLPushBack(SL* pa, SLDatatype x)
{
	SLCheckCapicity(pa);
	pa->q[pa->size++] = x;
}

説明する:

①:仮引数のポインタ pa は上記と同様、オブジェクトのアドレスを受け取るために使用され、x は挿入される要素です。

②: 挿入方法に関係なく、まずテーブルがいっぱいかどうかを判断する必要があるため、上記で実装した SLCheckCapicity 関数を呼び出して判断します。

③: 次のステップは挿入です。size は既存の要素の数であり、添字は 0 から始まるため、以下に示すように、配列 q の添字サイズに x を配置するだけで済みます。つまり、q[ size]=x, あとはサイズを勝手に大きくして末尾挿入方法は完了です。

(5) 末尾挿入では、当然、末尾削除メソッド があり、その名前が示すように、末尾のデータを削除します。
コードは次のように実装されます。

void SLPopBack(SL* pa)
{
	//判断是否为空表
	if (pa->size == 0)
	{
		assert(pa->size > 0);
	}
	pa->size--;
}

説明する:

①: どのような方法で要素を削除する場合でも、最初にテーブルに要素があるかどうかを確認し、要素があれば削除できます。ここでの判定には2通りの方法があり、1つは例のassertという関数を使ってアサートする方法で、仮引数は式であり、式が偽の場合はassert関数が直接エラーを報告します。もう 1 つは、直接戻って削除機能を終了する方法です。

②:アイデア: シーケンシャルテーブルなので、末尾削除方法は単にサイズをデクリメントするだけで末尾要素にはアクセスできなくなり、次回データを追加する場合は元のデータを直接上書きできます。

(6)ヘッド挿入方式:その名の通り、ヘッドにデータを挿入します。

コードは次のように実装されます。

void SLPushFront(SL* pa, SLDatatype x)
{
	SLCheckCapicity(pa);
	int end = pa->size - 1;
	//从后往前移动数据
	while (end >= 0)
	{
		pa->q[end + 1] = pa->q[end];
		end--;
	}
	pa->q[0] = x;
	pa->size++;
}

説明する:

①:データも挿入されるので、まずSLCheckCapicity関数を呼び出してテーブルがいっぱいかどうかを確認します。

②:アイデア;では、先頭に挿入したいので、十分なスペースがあれば、元のテーブルのデータを 1 記憶スペース分だけ後方に移動し、最初の要素 q[0 にデータ x を入れれば済みます。 ] の位置では、挿入成功後に自動的にサイズを大きくすることができます。

(7)ヘッド削除方法その名の通り、ヘッド内のデータを削除します。

コードは次のように実装されます。

void SLPopFront(SL* pa)
{
	if (pa->size == 0)
	{
		assert(pa->size > 0);
	}
	int left = 1;
	while (left <= pa->size - 1)
	{
		pa->q[left-1] = pa->q[left];
		left++;
	}
	pa->size--;
}

説明する:

①:データを削除する 同様に、まずテーブルが空かどうかを確認します。

②:アイデア:要素を削除しているため、最初の要素の後ろにあるサイズ 1 のデータを 1 つの記憶領域分前に移動し、最初の要素を上書きしてからサイズを減らすだけで済みます。データの損失を防ぐために、前から後ろに移動するように注意してください。

(8).添え字 pos が付いている位置にデータ x を挿入します

コードは次のように実装されます。

void SLInsert(SL* ps, int pos, SLDatatype x)
{
	assert(pos <= ps->size && pos >= 0);//判断插入位置是否无效
	SLCheckCapicity(ps);
	int end = ps->size-1;
	while (end >=pos)
	{
		ps->q[end + 1] = ps->q[end];
		end--;
	}
	ps->q[pos] = x;
	ps->size++;
}

説明する:

①:仮引数のポインタ ps はオブジェクトアドレスを受け取るために使用され、pos はデータを挿入する位置(添字)、x は挿入するデータの値です。

②:アイデア:元の pos 位置と pos 位置以降の値を順番に次の記憶領域に移動します (後ろから前に移動することを忘れないでください)。その後、pos と添え字が付けられた位置に x を置きます。

③: ライブラリ関数アサーションassert は、添え字 pos の挿入が正当かどうかを判断するために使用されます。pos は 0 以上、size 以下でなければなりません。

④: テーブルがいっぱいかどうかを判断し、関数 SLCheckCapicity を呼び出す必要があります。

⑤: 動きにはループが含まれ、配列には添え字が含まれるため、半分の労力で 2 倍の結果を達成するには、絵を描いて各添え字を分析する必要があります。

 最後に、pos で添え字が付けられた位置に x を配置し、サイズを大きくします。

(9). 操作 (9) を実行すると、次のことがわかります。

①: pos==size の場合、末尾挿入アルゴリズムなので、この関数は末尾挿入に再利用できます。


//尾插
void SLPushBack(SL* pa, SLDatatype x)
{
	//正常方法:
	/*SLCheckCapicity(pa);
	pa->q[pa->size++] = x;*/

	//复用函数SLInsert
	SLInsert(pa, pa->size, x);
}

②: pos==0 の場合、頭挿入アルゴリズムであり、原理は以下と同じです。

//头插
void SLPushFront(SL* pa, SLDatatype x)
{
	//正常方法:
	//SLCheckCapicity(pa);
	//int end = pa->size - 1;
	从后往前移动数据
	//while (end >= 0)
	//{
	//	pa->q[end + 1] = pa->q[end];
	//	end--;
	//}
	//pa->q[0] = x;
	//pa->size++;

	//复用函数SLInsert
	SLInsert(pa, 0, x);
}

コードの量が大幅に削減されていることがわかります。 

(10) pos 位置のデータを削除します

コードは次のように実装されます。

//删除pos位置的数据,下标为pos-1
void SLErase(SL* ps, int pos)
{
	assert(ps->size != 0 && pos >= 0 && pos < ps->size);
	//直接将pos位置覆盖
	int left = pos + 1;
	while (left < ps->size)
	{
		ps->q[left-1] = ps->q[left];
		left++;
	}
	ps->size--;
}

説明する:

①:仮パラメータ内。pos は削除するデータの添字です。

②: 次に、添え字 pos の値が有効かどうかを判断する必要がありますが、ここでもライブラリ関数assert が使用されます。

③: アイデア: pos に添字が付いた位置と pos 以降のデータを 1 つの記憶領域分前に移動し、削除するデータを上書きして削除の目的を達成し、最終的にサイズを削減するだけです。

④: 描画と分析が必要ですが、友達が自分で試してみることもできます。

(11). 10 回目の操作の後、次のことがわかります。

①:pos==0の場合、先頭挿入アルゴリズムとなります。

//この章の知識はまだ終わっていません。編集者は今後も更新し続けます。

おすすめ

転載: blog.csdn.net/hffh123/article/details/131962368