目次
1.スタック
スタックとキューの実装を記述するヘッダー ファイルは次のとおりです。
- Seqstack.h: シーケンシャル スタックの定義と実装。
- Lkstack.h: チェーン スタックの定義と実装。
- Sequeue.h: シーケンス キューの定義とその実装。
- Lkqueue.h: チェーンキューの定義と実装。
(1) スタックの基本概念
① 定義
スタック : テーブルの片端(テーブルの端)でのみ挿入、削除が可能な線形テーブルです。
- 挿入と削除が可能な端(テーブルの端)をスタックの先頭(Top)と呼びます。
- もう一方の端 (ヘッダー) はスタックの底部(Bottom)と呼ばれます。
- リストに要素がない場合、それは空のスタックと呼ばれます
- プッシュ - スタックの先頭に要素を挿入します
- Pop - スタックの最上位から要素を削除します
- スタックとキューは、限定された操作を備えた線形テーブルである特別な線形テーブルとみなすことができます。
② 模式図
【例】本の山や皿の山
【回路図】
③スタックの特徴
最初のうちの最後の:
- スタック内の要素は、 a 1 、a 2 、a 3 、... a n の順序でスタックにプッシュされ 、スタックからポップされた最初の要素がスタックの最上位要素になる必要があります。
- 言い換えれば、スタックの変更は後入れ先出しの原則に従って実行されます。
- したがって、スタックは後入れ先出し線形リスト( LIFO ) と呼ばれます。
スタックの目的 - 処理されるデータを一時的に保存するためによく使用されます
④スタックの基本動作
- スタックを初期化します: InitStack(S);
- スタックが空であると判断します: EmptyStack (S);
- スタックにプッシュします: Push (S,x);
- ポップ:ポップ(S);
- スタックの最上位を取得します: GetTop(S);
(2) スタックの順次実装
① シーケンススタックと普通名詞
- シーケンシャル スタック -つまり、スタックのシーケンシャル実装
- スタック容量 - スタックに保存できる要素の最大数
- スタックトップポインターtop - スタック内の現在のトップ要素の位置を示します。
- スタックが空です - スタックに要素がない場合、スタックが空であることを意味します
- スタックフル - 配列スペースがいっぱいになると、スタックフルと呼ばれます
- アンダーフロー - スタックが空で、スタック操作が必要な場合、それは「アンダーフロー」と呼ばれます。
- オーバーフロー —— スタックがいっぱいの場合、プッシュ操作を実行する必要がある場合は「オーバーフロー」と呼ばれます。
②シーケンシャルスタックの型定義
【シーケンシャルスタックタイプ】
const int maxsize = 6; // 定义常量,表示栈的最大大小 typedef struct seqstack { DataType data[maxsize]; // 定义存储元素的数组 int top; // 顶部指针,指向栈顶元素下标 } SeqStk; // 定义顺序栈的结构体
【回路図】
【コード説明】
seqstack
このコードでは、ストレージ要素の配列data
とトップ ポインターを 含むシーケンシャル スタック構造を定義しますtop
。最上位ポインタはtop
スタックの最上位要素の添字位置を示すために使用され、要素を格納する配列はdata
スタック内の要素を格納するために使用されます。- さらに、キーワードを使用して スタックの最大サイズを示す
const int
定数を定義し 、プログラム全体で定数を便利に使用できるようにします。maxsize
- 上記のコードは
DataType
データ型を示すプレースホルダーであり、実際のニーズに応じて必要なデータ型に置き換えることができることに注意してください。【イラスト】
SeqStk *s ; /*定义一顺序栈s*/
規約スタックの最初の要素は data[1] に格納され、次のようになります。
- s -> top==0 はシーケンススタック s が空であることを意味します
- s -> top==maxsize-1 は、順次スタック s がいっぱいであることを意味します
③シーケンシャルスタックの基本動作
Ⅰ. 初期化
【サンプルコード】
// 初始化栈 int Initstack(SeqStk* stk) { stk->top = 0; // 将栈的顶部指针设置为0,表示栈为空 return 1; // 返回1表示初始化成功 }
【回路図】
【コード説明】
1.このコードは、スタックを初期化する関数を実装します
Initstack
。2.関数のパラメータは
SeqStk
構造体 へのポインタでありstk
、これを通じてスタックの関連プロパティを操作できます。3.コード内のコメントは、各行の動作を説明しています。
stk->top = 0;
スタックの最上位ポインタを 0 にtop
設定し、スタックが空であることを示します。return 1;
スタックが正常に初期化されたことを示す 1 を返します。4.この関数の目的は、後続のプッシュおよびポップ操作のためにスタックの最上位ポインタを 0 に初期化することです。
5. 1 を返す関数は成功の兆候として使用でき、実際のニーズに応じて適切に変更できることに注意してください。
Ⅱ. スタックが空であると判断する
【サンプルコード】
// 判断栈是否为空 int EmptyStack(SeqStk* stk) { if (stk->top == 0) // 如果栈的顶部指针为0,表示栈为空 return 1; // 返回1表示栈为空 else return 0; // 返回0表示栈不为空 }
【コード説明】
1.このコードは、スタックが空かどうかを判断する関数を実装します
EmptyStack
。2.関数のパラメータは 、スタックの最上位ポインタを取得できる へ
SeqStk
のポインタです。stk
top
3.コード内のコメントは、各行の動作を説明しています。
if (stk->top == 0)
スタックの先頭ポインタが 0top
の、スタックが空であることを意味します。return 1;
スタックが空であることを示す 1 を返します。else return 0;
スタックの先頭ポインタが 0top
でない、スタックが空ではないことを意味し、0 を返します。4.この関数の目的はスタックが空かどうかを判断することであり、戻り値 0 または 1 に従って論理的に判断できます。
5.条件判定では 単一の代入演算子
==
ではなく、等価関係を表現するために 使用する必要があることに注意してください。=
6. スタックが空の場合、戻り値は 1 であり、それ以外の場合、戻り値は 0 です。
Ⅲ. スタックにプッシュ
【サンプルコード】
// 入栈操作 int Push(SeqStk* stk, DataType x) { /* 数据元素x进顺序栈stk */ if (stk->top == maxsize - 1) // 判断栈是否上溢(满栈) { error("栈满"); // 输出错误信息,表示栈已满 return 0; // 返回0,表示入栈失败 } else { stk->top++; // 修改栈顶指针,指向新的栈顶 stk->data[stk->top] = x; // 将元素x插入新的栈顶位置 return 1; // 返回1,表示入栈成功 } }
【コード説明】
1.このコードはスタック操作関数を実装しています
Push
。2.関数のパラメータに
SeqStk
は、stk
x
3.コード内のコメントは、各行の動作を説明しています。
if (stk->top == maxsize - 1)
スタックの最上位ポインタが に等しいtop
かどうかmaxsize - 1
、つまりスタックがいっぱいかどうかを判断します。error("栈满");
スタックがいっぱいの場合は、「stack full」というエラーメッセージを出力します。return 0;
0 を返し、プッシュが失敗したことを示します。stk->top++;
スタック トップ ポインタtop
が増加し、新しいスタック トップ位置を指します。stk->data[stk->top] = x;
要素をスタックの新しい先頭位置x
に。return 1;
1 を返し、スタックが成功したことを示します。4.この関数の目的は、データ要素を
x
順次スタックにプッシュし、スタックがいっぱいの場合にエラー メッセージを出力することです。5.戻り値 0 はスタッキングが失敗したことを示し、戻り値 1 はスタッキングが成功したことを示します。
Ⅳ. ポッピング
【サンプルコード】
// 出栈操作 int Pop(SeqStk* stk) { /* 顺序栈stk的栈顶元素退栈 */ if (stk->top == 0) // 判断栈是否下溢(空栈) { error("栈空"); // 输出错误信息,表示栈已空 return 0; // 返回0,表示出栈失败 } else { stk->top--; // 修改栈顶指针,指向新的栈顶 return 1; // 返回1,表示出栈成功 } } /* Pop */
【コード説明】
1.このコードは、スタックをポップする関数を実装します
Pop
。2.関数のパラメータは
SeqStk
構造体へのポインタ ですstk
。3.コード内のコメントは、各行の動作を説明しています。
if (stk->top == 0)
スタックの先頭ポインタが 0top
かどうか。error("栈空");
スタックが空の場合は、「スタックが空です」というエラーメッセージが出力されます。return 0;
スタックからのポップアウトに失敗したことを示す 0 を返します。stk->top--;
スタックの先頭ポインタは、スタックの新しい先頭を指すようにtop
デクリメントされます。return 1;
1 を返し、スタックが正常にポップされたことを示します。4.この関数の目的は、順次スタック
stk
の。5.戻り値 0 はポップアウトが失敗したことを示し、戻り値 1 はポップアウトが成功したことを示します。
6.最後の
/* Pop */
は関数のコメントで、コード セグメントがスタック操作を実装していることを示します。
Ⅴ. スタックの最上位要素を取得します
【サンプルコード】
// 获取栈顶元素 DataType GetTop(SeqStk* stk) { if (EmptyStack(stk)) // 判断栈是否为空 return NULLData; // 如果为空,返回空数据元素 else return stk->data[stk->top]; // 返回栈顶元素 }
【コード説明】
1.このコードは、スタックの最上位要素を取得する関数を実装します
GetTop
。2.関数のパラメータは
SeqStk
構造体stk
。3.コード内のコメントは、各行の動作を説明しています。
if (EmptyStack(stk))
EmptyStack
関数 を呼び出して、スタックが空かどうかを判断します。return NULLData;
スタックが空の場合は、空のデータ要素を返します。else return stk->data[stk->top];
スタックが空でない場合は、スタックの最上位要素を返します。4.この関数の目的は、順次スタック
stk
の。スタックが空の場合は、空のデータ要素を返します。5.それ以外の場合は、スタックの最上位要素の値を返します。
なお
NULLData
、実際の状況に応じて、 データの種類に応じて適宜変更することができる。
(3) スタックリンク実装 -スタックチェーン
①スタックチェーンの定義
- スタックの連結された記憶構造は連結スタックと呼ばれ、操作が限定された単一連結リストであり、挿入および削除操作はヘッダ位置のみに限定されます。
- スタックの先頭ポインタは、リンクされたリストの先頭ポインタです。
② 連鎖スタックの型説明
【チェーンスタックタイプ】
// 定义链式栈节点结构体 typedef struct node { DataType data; // 数据元素 struct node* next; // 指向下一个节点的指针 } LkStk;
【知らせ】
- アンダーフロー条件: LS -> next==NULL
- オーバーフロー:スタックフル現象を考慮しません
【コード説明】
1.このコードは、チェーンされたスタック ノードの構造を定義します
LkStk
。2.次の 2 つのメンバー変数が含まれています。
DataType data;
データ要素を格納する変数。ここではカスタム データ型DataType
であると。struct node* next;
チェーン構造を実装するために使用される、次のノードへのポインター。3.この構造はチェーン スタック内のノードを表し、各ノードにはデータ要素と次のノードへのポインタが含まれます。
4.ノードを連続的に接続することでチェーンスタック構造を形成できます。
③チェーンスタックの基本操作
Ⅰ. 初期化
【サンプルコード】
// 初始化链式栈 void InitStack(LkStk* LS) { LS = (LkStk*)malloc(sizeof(LkStk)); // 分配内存空间,LS指向新的栈顶节点 LS->next = NULL; // 将栈顶节点的指针指向NULL,表示栈为空 }
【コード説明】
1.このコードは、チェーン スタックを初期化する関数を実装します
InitStack
。2.関数のパラメータは
LkStk
構造体LS
。3.コード内のコメントは、各行の動作を説明しています。
LS = (LkStk*)malloc(sizeof(LkStk));
メモリ空間の動的割り当てを使用して、 ポインタをスタックの新しい最上位ノードにmalloc
ポイントします。LS
LS->next = NULL;
スタックの最上位ノードのポインタを にnext
割り当てNULL
、リンクされたスタックが空であることを示します。4.この関数の目的は、チェーン スタックを初期化することです。
5.メモリ空間を動的に割り当てることにより、スタック トップ ノードを作成し、そのポインタをポイントして、
NULL
リンクされたスタックが空であることを示します。6. パラメータはポインタであるため、関数内でポインタの値を変更しても、関数の呼び出し時に渡される実際のパラメータには影響しないことに注意してください。
7.したがって、関数呼び出し後に新しく割り当てられたスタックトップノードにアクセスできるようにしたい場合は、関数のパラメータをダブルポインタに変更するか、新しく割り当てられたスタックトップノードポインタを返すことをお勧めします。
Ⅱ. スタックが空であると判断する
【サンプルコード】
// 判断链式栈是否为空 int EmptyStack(LkStk* LS) { if (LS->next == NULL) // 判断栈顶节点指针是否为空 return 1; // 如果为空,返回1表示栈为空 else return 0; // 如果不为空,返回0表示栈不为空 }
【コード説明】
1.このコードは、リンクされたスタックが空かどうかを判断する関数を実装します
EmptyStack
。2.
LkStk
関数のパラメータは、LS
関数へのポインタです。3.コード内のコメントは、各行の動作を説明しています。
if (LS->next == NULL)
next
スタックの先頭ノードのポインタが空かどうか 、つまり連鎖したスタックが空かどうかを判定します。return 1;
スタックが空の場合は、スタックが空であることを示す 1 を返します。return 0;
スタックが空でない場合は、スタックが空ではないことを示す 0 を返します。4.この関数の目的は、リンクされたスタックが空かどうかを判断することです。
5.スタックの最上位ノードのポインタが空かどうかを判断して、チェーン スタックの状態を判断します。
6. スタックの最上位ノードのポインタが空の場合は、スタックが空であることを意味し、そうでない場合は、スタックが空ではないことを意味します。
7.戻り値 1 はスタックが空であることを示し、戻り値 0 はスタックが空ではないことを示します。
Ⅲ. スタックにプッシュ
[定義]スタックにプッシュ - スタックの先頭に要素 xを挿入します。
- 新しいノードを生成します(チェーン スタックはオーバーフローしません)
- 新しいノードをチェーン スタックに挿入し、それをスタックの新しい最上位ノードにします
【サンプルコード】
// 将元素入栈 void Push(LkStk* LS, DataType x) { LkStk* temp; // 定义临时指针变量temp temp = (LkStk*)malloc(sizeof(LkStk)); // 分配内存空间,创建新的节点 temp->data = x; // 将输入的元素x赋值给新节点的数据域 temp->next = LS->next; // 将新节点的next指针指向原来栈顶节点的next指针 LS->next = temp; // 将栈顶指针指向新节点,使其成为新的栈顶节点 }
【回路図】
【コード説明】
1.このコードは、要素をスタックにプッシュする関数を実装します
Push
。2.関数のパラメータには、
LkStk
構造体へのポインタLS
と、スタックにプッシュされる要素を示すDataType
タイプの要素 が含まれます。x
3.コード内のコメントは、各行の動作を説明しています。
LkStk* temp;
temp
新しく作成されたノードを指す 一時ポインター変数を定義します 。temp = (LkStk*)malloc(sizeof(LkStk));
を使用してメモリ空間をmalloc
動的に割り当て、新しいノードを作成します。temp->data = x;
スタックにx
プッシュする要素を新しいノードのデータフィールドに割り当てます。temp->next = LS->next;
新しいノードのnext
ポインタを元のスタックトップノードのnext
ポインタに向けて、チェーン構造の接続を実現します。LS->next = temp;
スタックの最上位ポインタを新しいノードLS->next
に、新しいノードをスタックの新しい最上位ノードにします。4.この関数の目的は、要素を
x
スタックにプッシュすることです。5.まず新しいノードを作成し、新しいノードのデータ フィールドに要素を割り当てます。
6. 次に、新しいノードの
next
ポインタを元のスタック トップ ノードのnext
ポインタにポイントし、次にスタック トップ ポインタを新しいノードにポイントします。プッシュ操作。
Ⅳ. ポッピング
[定義]ポップアウト - スタックの先頭にある要素を削除して戻ります。
- アンダーフロー問題を考えてみる
- アンダーフローしない場合は、スタックの最上位要素を取り出し、チェーンスタックから最上位ノードを削除して、ノードをシステムに戻します。
【サンプルコード】
// 将元素出栈 int Pop(LkStk* LS) { LkStk* temp; // 定义临时指针变量temp if (!EmptyStack(LS)) // 判断栈是否为空 { temp = LS->next; // 将temp指针指向栈顶节点 LS->next = temp->next; // 将栈顶指针指向栈顶节点的下一个节点,跳过当前栈顶节点 free(temp); // 释放当前栈顶节点的内存空间 return 1; // 返回1表示出栈成功 } else return 0; // 返回0表示栈为空,无法进行出栈操作 }
【回路図】
【コード説明】
1.このコードは、スタックから要素をポップする関数を実装します
Pop
。2.関数のパラメータは 、操作対象のチェーン スタックを表すために使用される
LkStk
構造体へのポインタ です。LS
3.コード内のコメントは、各行の動作を説明しています。
LkStk* temp;
temp
現在のスタックのトップ ノードを指す 一時ポインター変数を定義します 。if (!EmptyStack(LS))
スタックが空かどうかを判定するには、EmptyStack()
判定を補助する関数を使用します。temp = LS->next;
一時ポインタ変数がスタックの最上位ノードtemp
を。LS->next = temp->next;
スタック トップ ポインタは、現在のスタック トップ ノードをスキップして、スタック トップ ノードの次のノードを指します。free(temp);
現在のスタックトップノードのメモリ空間を解放します。つまり、チェーンスタックから削除します。return 1;
1 が返されると、スタックが正常にポップされたことを示します。return 0;
リンク スタックが空の場合、0 が返されると、スタックが空でポップできないことを意味します。4.この関数の目的は、要素のスタック操作を実現することです。
5.まずスタックが空かどうかを判断し、空でない場合は、スタックの最上位ポインタをスタックの最上位ノードの次のノードにポイントし、現在のスタックの最上位ノードのメモリ空間を解放します。同時。
6.スタックが空の場合は、スタック操作を実行できないことを意味します。
7. 返回值 1 表示出栈成功,返回值 0 表示栈为空,无法进行出栈操作。
Ⅴ. 取栈顶元素
【示例代码】
// 获取栈顶元素 DataType GetTop(LkStk* LS) { if (!EmptyStack(LS)) // 判断栈是否为空 return LS->next->data; // 返回栈顶节点的数据域值 else return NULLData; // 如果栈为空,则返回空数据(NULLData) }
【代码详解】
1. 这段代码实现了一个获取栈顶元素的函数
GetTop
。2. 函数的参数是一个指向
LkStk
结构体的指针LS
,用于表示要操作的链式栈。3. 代码中的注释解释了每一行的作用:
if (!EmptyStack(LS))
判断栈是否为空,使用 EmptyStack()
函数来辅助判断。return LS->next->data;
如果栈不为空,返回栈顶节点的数据域值。return NULLData;
如果栈为空,返回空数据(NULLData
),即表示栈中没有元素。4. 该函数的目的是获取栈顶元素的值,而不对栈进行修改。
5. 首先判断栈是否为空,如果不为空,返回栈顶节点的数据域值;如果栈为空,返回空数据(
NULLData
)表示栈中没有元素。
(4)栈的应用和递归
① 栈的基本应用实例
【示例一】 设栈的输入序列依次为 1,2,3,4,则所得的输出序列哪个符合?
【题目解析】正确答案:d
- 栈是一种遵循 “后进先出”(Last In First Out, LIFO)原则的数据结构,所以当我们按顺序依次将元素 1、2、3、4 入栈,得到的输出序列只能是
d 3,4,2,1
。- 他のオプション ( a 1,2,3,4、b 4,2,3,1、c 1,3,2,4 ) をスタックの出力シーケンスにすることはできません。
- オプションd 3,4,2,1 は、スタックのプロパティに準拠する唯一の出力シーケンスであり、入力シーケンスが 1,2,3,4 の場合に対応します。
【詳しい回答】
- オプションa 1,2,3,4 : スタックは後入れ先出しの原則に従って要素を出力するため、出力シーケンスは 4,3,2,1 となり、指定されたオプションと一致しません。
- オプションb 4,2,3,1 : スタックはスタックにプッシュされた最後の要素を最初に出力するため、出力シーケンスは 4,3,2,1 となり、指定されたオプションと一致しません。
- オプションc 1,3,2,4 : スタックは要素 4 を出力する前に、最初に要素 3 を出力する必要があります。オプション c で指定されるシーケンスは 1,3 であり、スタックの特性と矛盾します。
- オプションd 3,4,2,1 : スタックは後入れ先出し (LIFO) データ構造であり、要素がスタックからポップアウトされる順序は、要素がプッシュされた順序と逆になります。スタック。タイトルの入力シーケンスは 1、2、3、4 であるため、スタックの特性に従って、最初に要素 4 がスタックにプッシュされ、次に要素 3、次に要素 2、最後に要素 1 がスタックにプッシュされます。したがって、スタック内の順序は 4、3、2、1 になります。オプション d に対応する出力シーケンス 3、4、2、1 もスタックの特性と一致しており、要素 3 が最初にポップされ、次に要素 4、要素 2 の順で、要素 1 が最後にポップされます。したがって、オプション d は、スタックのルールに準拠し、入力シーケンスと一貫性のある正しい出力シーケンスです。
【例2】シーケンシャルスタックの動作
【サンプルコード】
const int maxsize = 50; // 定义常量 maxsize,表示栈的最大大小 typedef struct seqstack // 定义结构体 seqstack,表示顺序栈 { char data[maxsize]; // 字符数组,用于存储栈中的元素 int top; // 整型变量,表示栈顶指针的位置 } seqstk; int main() { seqstk stk; // 声明一个 seqstk 类型的变量 stk int i; char ch; Initstack(&stk); // 初始化栈 stk for (ch = 'A'; ch <= 'A' + 10; ch++) // 将字符 'A' 到 'A' + 10 入栈 { Push(&stk, ch); // 将字符 ch 压入栈 stk printf("%c", ch); // 打印字符 ch } printf("\n"); while (!Emptystack(&stk)) // 当栈不为空时执行循环 { ch = GetTop(&stk); // 获取栈顶元素赋值给变量 ch Pop(&stk); // 弹出栈顶元素 printf("%c", ch); // 打印变量 ch 的值 } printf("\n"); return 0; }
【コード説明】
1.コードの機能は、順次スタックを作成して初期化し、文字「A」から「K」を順番にスタックにプッシュし、次にスタックにプッシュされた文字を出力し、最後にスタックをポップして出力することです。文字を順番にスタックから取り出します。
2.詳細は以下のとおりです。
const int maxsize = 50;
maxsize
スタックの最大サイズを表す 定数を定義します 。typedef struct seqstack
seqstk
順次スタックを表す 新しいデータ型を定義します 。char data[maxsize];
seqstk
構造体 に 文字配列を定義してdata
、要素をスタックに格納します。int top;
スタックの先頭ポインタの位置を示すために、seqstk
構造体内 に 整数変数を定義します。top
int main()
メイン関数のエントリ。seqstk stk;
順次スタックを表すseqstk
型の変数 を宣言します 。stk
Initstack(&stk);
関数を呼び出してInitstack()
スタックを初期化しますstk
。for (ch = 'A'; ch <= 'A' + 10; ch++)
ループし、文字「A」から「A」+ 10 を順番にスタックにプッシュします。Push(&stk, ch);
呼び出した関数はPush()
文字をch
スタックに プッシュしますstk
。printf("%c", ch);
文字を印刷しますch
。while (!Emptystack(&stk))
スタックが空でない間にループを実行します。ch = GetTop(&stk);
関数を呼び出してGetTop()
スタックの最上位要素を取得し、それを変数に割り当てますch
。Pop(&stk);
呼び出した関数はPop()
スタックの最上位要素をポップします。printf("%c", ch);
ch
変数の値 を出力します 。return 0;
main 関数が正常に実行されたことを示すフラグを返します。3.要約すると、このコードの機能は、順次スタックを作成し、一連の文字をスタックにプッシュして 1 つずつ出力し、次にスタック内の要素を 1 つずつポップアウトして出力することです。 、最終的な出力結果は「ABCDEFGHIJK」になります。
【結果】
- 実行中、文字「A」から「K」が順番にスタックにプッシュされるため、最初の行の出力は「ABCDEFGHIJK」になります。
- 次に、各ループでスタックの最上位要素がポップされ、スタックが空になるまで出力されます。
- したがって、出力の 2 行目は「KJIHGFEDCBA」となり、文字「K」から「A」の順序が逆になります。
ABCDEFGHIJK KJIHGFEDCBA
[例 3]下図に示す先頭ノードを先頭とする単連結リストをスタックを利用して反転するアルゴリズムを記述します。
【サンプルコード】
void ReverseList(LkStk *head) { Lkstk *S; // 定义指向链栈的指针变量 S DataType x; // 定义数据类型变量 x,用于临时存储链表节点的数据 InitStack(S); // 初始化链栈 S p = head->next; // p 指向链表的第一个节点 while (p != NULL) // 扫描链表 { Push(s, p->data); // 元素进栈:将链表节点的数据入栈 p = p->next; // p 指向下一个节点 } p = head->next; // 重新让 p 指向链表的第一个节点 while (!EmptyStack(S)) // 栈不为空时 { p->data = GetTop(S); // 元素填入单链表中:将栈顶元素赋值给链表节点的数据 Pop(S); // 出栈:弹出栈顶元素 p = p->next; // p 指向下一个节点 } }
【コード説明】
1.このコードの機能は、リンク リストの逆の操作を実現することであり、具体的な実装手順は次のとおりです。
- リンク スタックを指すポインタ変数を宣言します
S
。x
リンク リスト ノードのデータを格納する一時変数を宣言します 。- チェーンスタックを初期化します
S
。- ポインタを
p
リンク リストの最初のノードにポイントします。- リンクされたリストを走査し、ノードのデータをスタックにプッシュします。
p
ポインタをリンク リストの最初のノードに再ポイントします 。- スタックを走査し、スタックの最上位要素をリンク リスト ノードのデータに順番に割り当て、スタックの最上位要素をポップします。
- ポインタを
p
次のノードに向けます。つまり、リンクされたリストを逆方向にたどります。- リンクされたリストの逆の操作を完了します。
2.詳細は以下のとおりです。
void ReverseList(LkStk *head)
関数名は でReverseList
、パラメータはリンク リストの先頭ノードへのポインタでhead
、リンク リストが反転されていることを示します。Lkstk *S;
リンクスタックのポインタを表すLkstk
ポインタ変数の型 を宣言します 。S
DataType x;
リンクリストノードのデータを一時的に格納する データ型のDataType
変数 を宣言します。x
InitStack(S);
関数を呼び出してInitStack()
リンク スタック をS
初期化します。p = head->next;
ポインタをp
リンク リストの最初のノードにポイントします。while (p != NULL)
ポインタがp
null でない間にループを実行します。Push(s, p->data);
関数を呼び出してPush()
、リンク リスト ノードのデータをスタックにプッシュします。p = p->next;
ポインタをリンク リストの次のノードにポイントしますp
。つまり、リンク リストを逆方向に走査します。p = head->next;
p
ポインタをリンク リストの最初のノードに 再ポイントします 。while (!EmptyStack(S))
リンクスタックがS
空でない間にループを実行します。p->data = GetTop(S);
スタックの最上位要素をリンク リスト ノードのデータに割り当てます。Pop(S);
呼び出した関数はPop()
スタックの最上位要素をポップします。p = p->next;
ポインタをリンク リストの次のノードにポイントしますp
。つまり、リンク リストを逆方向に走査します。3.要約すると、この関数は、リンク スタックと一時変数を使用してリンク リストの反転を実現し、最初にリンク リスト ノードのデータをスタックにプッシュし、次にスタックからポップアウトします。
②再帰の定義
関数が完了する前にそれ自体を呼び出す場合、その関数は 再帰関数と呼ばれます。
[例]整数 n の階乗関数を求めます。
【サンプルコード】
int f(int n) { if (n == 0) // 如果 n 等于 0 return 1; // 返回 1 else // 否则 return n * f(n - 1); // 递归调用函数 f,传入 n-1,并将结果乘以 n 返回 }
【コード説明】
int f(int n)
関数名はf
、パラメータは整数でn
、階乗を計算する関数を示します。if (n == 0)
n が 0 に等しい場合、最も基本的なケースが再帰され、階乗の結果が 1 であることを意味します。return 1;
結果 1 を返します。else
n が 0 に等しくない場合は、階乗の再帰計算を続行する必要があることを意味します。return n * f(n - 1);
関数 f を再帰的に呼び出し、n-1 を渡し、n を乗算した結果を返します。再帰呼び出しの目的は、n-1 の階乗の n 倍に等しい n の階乗を計算することです。
③再帰の一般形式
void fname(参数表)
{
if (数据作为递归出口)
{
// 简单操作
}
else
{
// 简单操作
fname(参数表);
// 简单操作
// [fname(参数表); 简单操作;]
// 可能有多次递归调用
}
}
【詳細説明】上記は再帰関数の一般的な形式ですが、具体的に説明すると以下のようになります。
void fname(参数表)
: 関数名は でfname
、パラメータのセットが付いています。if (数据作为递归出口)
: 特定の条件が満たされると、データは再帰の出口として使用されます。つまり、再帰は終了します。// 简单操作
: 再帰終了時に実行される単純なアクション。else
:条件を満たさない場合は、以下の操作を行ってください。// 简单操作
: 再帰呼び出しの前後に実行される単純な操作。fname(参数表)
: 関数を再帰的に呼び出しfname
、パラメータ リストを渡します。// 可能有多次递归调用
: 再帰呼び出しの後、関数はfname
再帰のために呼び出され続ける可能性があり、複数の再帰呼び出しが存在する可能性があります。[注意]再帰関数は再帰の終了条件を満たしている必要があります。満たさない場合は無限再帰に入り、プログラムがクラッシュします。同時に、再帰関数は、再帰が正常に終了できるように、再帰呼び出しの順序と終了条件の設定を考慮する必要があります。
2、行列
スタックとキューの実装を記述するヘッダー ファイルは次のとおりです。
- Seqstack.h: シーケンシャル スタックの定義と実装。
- Lkstack.h: チェーン スタックの定義と実装。
- Sequeue.h: シーケンス キューの定義とその実装。
- Lkqueue.h: チェーンキューの定義と実装。
(1) キューの基本概念
① 定義
キュー: これも、操作が制限された線形テーブルです。
- キュー - テーブルの一端での挿入と、もう一端での削除のみを許可する線形テーブルです。
- 削除できる端をフロントと呼びます
- 挿入を許可するもう一方の端はキューの末尾(後部)と呼ばれます。
- キュー Q=(a1, a2, a3,...an)
② 模式図
【例】 並んで買い物
- オペレーティング システムのジョブ キューイング
- 最初にキューに入ったメンバーが常に最初にキューから出ます
- したがって、キューは先入れ先出し(先入れ先出し)線形テーブル、または 略してFIFO テーブルとも呼ばれます。
- キュー内に要素が存在しない場合、それは空のキューと呼ばれます。
- 要素 a 1 、 a 2 、... a n を 空のキューに順番に追加した後、a 1 はキューの先頭要素、a n はキューの末尾要素になります。
- 明らかに、キューから出る a 1 、 a 2 、... a nのみです。つまり、キューの変更は先入れ先出しの原則に従って実行されます。
③キューの特徴
先入れ先出し ( FIFO ) :
キューの目的 - 処理されるデータを一時的に保存するためによく使用されます。
④キューの基本動作
➢キュー の初期化 InitQueue(Q):
- 空のキューを設定します Q
➢空のキュー ( Q):
- キュー Q が空の場合、戻り値は 1 、それ以外の場合、戻り値は 0です。
➢エンキュー エンキュー (Q,x):
- データ要素 x を キューの最後からキューに挿入し、それをキューの新しい末尾要素にします
➢キュー アウトキュー (Q):
- キューの最初の要素を削除します
➢ キューの最初の要素を取得 GetHead(Q):
- キューの最初の要素の値を返します。
(2) キューの順次実装
①シーケンシャルキュー
[定義] シーケンシャルキュー - 配列の一方の端からのみ挿入でき、もう一方の端から削除できます。[説明] SeqQue sq ;
- オーバーフロー条件: sq.rear == maxsize-1 (キューがいっぱい)
- アンダーフロー条件: sq.rear == sq.front (キューが空)
[注意] False overflow : sq.rear == maxsize-1ですが 、キューの実際の容量が最大容量に達していません。
②循環列
[定義] キューの逐次実装 - 循環キュー
- キューの記憶構造として 1 次元配列を使用する
- キュー容量- キューに保存できる要素の最大数
【回路図】
【合意】
- 初期値: フロント=リア=0
- エンキュー: 後部が 1 増加し、末尾ポインタが指す位置に要素が挿入されます。
- デキュー: フロントを 1 増やし、ヘッド ポインタが指す位置の要素を取得します。
【イラスト】
- head ポインタの先頭 - 常に実際の head 要素の前の位置を指します。
- テールポインタ後部 - 常に実際のテール要素を指します
➢ キュー操作へ:
- 平方リア=平方リア+1;
- sq.data[sq.rear]=x;
➢デキュー 操作:
- sq.front=sq.front+1;
- 図 a は空のキュー、sq.rear=0、sq.front=0 です。
- 図 b は、20 がキューに入った後、sq.rear=1、sq.front=0 であることを示しています。
- 図 c は、30、40、および 50 が順番にキューに入れられた後、sq.rear=4、sq.front=0 であることを示しています。
- 図 d は、20、30、40、50 が順番にキューから出た後、sq.rear=4、sq.front=4 であることを示しています。
- 図 e は、60 がキューに入った後、sq.rear=5、sq.front=4 であることを示しています。
【サンプルコード】const int maxsize = 20; // 定义队列的最大容量为20 // 定义顺序队列结构体 typedef struct seqqueue { DataType data[maxsize]; // 存储队列中的元素 int front, rear; // 队头指针和队尾指针 } SeqQue; SeqQue sq; // 声明一个顺序队列变量sq
【コード説明】1.コードの各行のコメントの説明は次のとおりです。
const int maxsize = 20;
定数が定義されておりmaxsize
、キューの最大容量が 20 であることを示します。typedef struct seqqueue
という名前の構造体型を定義しますseqqueue
。DataType data[maxsize];
seqqueue
配列はdata
キュー内の要素を格納するために 構造内で定義されます。配列のサイズは ですmaxsize
。int front, rear;
2 つの整数変数および がseqqueue
構造体 で 定義されており、それぞれキューの先頭ポインタとキューの末尾ポインタを表します。front
rear
SeqQue sq;
シーケンシャル キュー変数はsq
、 型で 宣言されますSeqQue
。2.
SeqQue
このコードは、固定サイズの配列 とdata
キューの先頭と末尾の位置を示す 2 つのfront
ポインタを 含むシーケンシャル キュー データ構造を定義しますrear
。3.
sq
変数 を宣言すると 、SeqQue
構造体を使用してシーケンシャル キュー オブジェクトを作成できます。
Ⅰ. 循環キューの定義
[定義] 循環キュー - キューに記憶領域(配列表現)を割り当て、この記憶領域を先頭から末尾までつながったものとみなします。【回路図】【知らせ】
- ヘッド ポインターの 前方 — 実際のキューのヘッド要素の時計回り方向の 1 つ後ろの位置
- テールポインタ 後部- 実際のテール要素を指します
Ⅱ. 循環キューループの実装
➢ キューへの挿入の場合: キューの末尾のポインタが 1ずつインクリメントされます。Sq.rear=(sq.rear+1)%maxsize
➢削除後すぐにデキュー:キューの先頭のポインタが 1ずつインクリメントされます
Sq.front=(sq.front+1)%maxsize
Ⅲ. 循環キューの型定義
【サンプルコード】
typedef struct Cycqueue { DataType data[maxsize]; // 存储数据的数组 int front, rear; // 队头和队尾指针 } CycQue; // 定义循环队列类型 CycQue CQ; // 声明一个循环队列实例 CQ
【コード説明】
typedef struct Cycqueue
構造体の型の定義を示しますCycqueue
。{}
中括弧内で構造体のメンバーを定義します。DataType data[maxsize];
データを格納するために名前を付けた配列を宣言しますdata
。配列のサイズは ですmaxsize
。int front, rear;
循環キューの先頭ポインターと末尾ポインターをそれぞれ表す 2 つの整数変数front
と を宣言します。rear
} CycQue;
構造体の型定義の終わりを示し、名前を に変更しますCycQue
。CycQue CQ;
CQ
上で定義した構造タイプを使用して、 循環キュー インスタンスを宣言します 。
Ⅳ. 規定
循環キュー CQ
- アンダーフロー状態は、キューが空であることを意味します: CQ.front==CQ.rear
- オーバーフロー条件は、キューがいっぱいであることです。末尾ポインタが後ろから先頭ポインタに追いつきます。
つまり: (CQ.rear+1)%maxsize==CQ.front ( テールキャッチヘッド )
- スペースが無駄になります。チームがいっぱいの場合、実際のチーム容量 = maxsize-1
③循環キューの基本動作
Ⅰ. 初期化
【サンプルコード】
void InitQueue(CycQue CQ) { CQ.front = 0; // 初始化循环队列的队头指针为 0 CQ.rear = 0; // 初始化循环队列的队尾指针为 0 }
【コード説明】
1.この関数は、循環キューを初期化し、キューの先頭ポインタとキュー末尾のポインタを 0 に設定して、キューが空であることを示すために使用されます。
2.詳細な説明は次のとおりです。
void InitQueue(CycQue CQ)
関数が定義されておりInitQueue
、パラメータが循環キューであること を示しますCQ
。CQ.front = 0;
循環キューのヘッド ポインタをfront
0 に初期化し、キューが空であることを示します。CQ.rear = 0;
循環キューの末尾ポインタをrear
0 に初期化し、キューが空であることを示します。
Ⅱ. キューが空であると判断する
【サンプルコード】
int EmptyQueue(CycQue CQ) { if (CQ.rear == CQ.front) // 如果队尾指针等于队头指针,表示队列为空 return 1; // 返回 1,表示队列为空 else return 0; // 返回 0,表示队列不为空 }
【コード説明】
1.この関数は、循環キューが空かどうかを判断するために使用されます。
2.キュー末尾ポインタがキュー先頭ポインタと等しい場合は、キューが空であることを意味し、そうでない場合は、キューが空ではないことを意味します。
3. 戻り値 1 はキューが空であることを示し、戻り値 0 はキューが空ではないことを示します。
4.詳細な説明は次のとおりです。
int EmptyQueue(CycQue CQ)
関数が定義されておりEmptyQueue
、パラメータが循環キューであること を示しますCQ
。if (CQ.rear == CQ.front)
キュー末尾ポインタがrear
キュー先頭ポインタと等しい かどうかを確認しますfront
。return 1;
キュー末尾ポインタがキュー先頭ポインタと等しい場合、キューが空であることを示し、1 を返します。return 0;
キュー末尾ポインタがキュー先頭ポインタと等しくない場合は、キューが空ではないことを意味し、0 を返します。
Ⅲ. 列に並びます
[定義]チームに入る - チームの最後に新しい要素 xを挿入する
- 判定オーバーフロー?はい、オーバーフローが戻ります。
- それ以外の場合は、キュー末尾ポインターを変更し (1 を増やし)、新しい要素 x をキュー末尾に挿入します。
【サンプルコード】
// 在循环队列中插入元素 int EnQueue(CycQue CQ, DataType x) { // 判断队列是否已满 if ((CQ.rear + 1) % maxsize == CQ.front) { error("队列满"); // 队列满时报错 return 0; } else { CQ.rear = (CQ.rear + 1) % maxsize; // 队尾指针向后移动一个位置 CQ.data[CQ.rear] = x; // 将元素x插入队尾 return 1; } }
【コード説明】
1.このコードは、循環キューに要素を挿入する操作を実装します。
2.詳細な説明は次のとおりです。
CycQue
循環キューの構造タイプです。DataType
キュー内の要素のデータ型です。maxsize
キューの最大容量です。3.この関数では、まずキューがいっぱいかどうかを判断します。これは次の条件によって判断されます
(CQ.rear + 1) % maxsize == CQ.front
。条件が true の場合、キューがいっぱいで、エラー メッセージが出力され、0 が返されます。4.キューがいっぱいでない場合は、
else
次のステートメントが実行されます。まず、末尾ポインタを 1 つ前の位置に戻しますCQ.rear = (CQ.rear + 1) % maxsize
。x
次に、キューの最後に要素を挿入しますCQ.data[CQ.rear] = x
。最後に、関数は挿入が成功したことを示す 1 を返します。5.ここでの
CQ
パラメータは、関数内のパラメータを変更しても外部には影響 しないことCQ.rear
に注意してください。CQ.data
関数内の外部構造を変更する場合は、ポインターまたは参照を関数パラメーターとして使用する必要があります。
Ⅳ. デキュー
[定義]デキュー - キューの先頭の要素を削除して戻ります。
- アンダーフローと判断されるのでしょうか?はい、アンダーフローを返します。
- アンダーフローしない場合は、キュー ヘッド ポインターを変更し、キュー ヘッド要素を取得します。
【サンプルコード】
// 从循环队列中取出元素 int OutQueue(CycQue CQ) { // 判断队列是否为空 if (EmptyQueue(CQ)) { error("队列空"); // 队列空时报错 return 0; } else { CQ.front = (CQ.front + 1) % maxsize; // 队头指针向后移动一个位置 return 1; } }
【コード説明】
1.コードの機能は、循環キューから要素を取り出すことです。
2.詳細な説明は次のとおりです。
CycQue
循環キューの構造タイプです。maxsize
キューの最大容量です。3.関数では、まずキューが空かどうかを判断します。これは
EmptyQueue(CQ)
関数を呼び出すことで判断されます。キューが空の場合はエラーメッセージが出力され、0が返されます。4.キューが空でない場合は、
else
次の。まず、行頭ポインタを 1 位置戻しますCQ.front = (CQ.front + 1) % maxsize
。次に、関数は要素が正常に取得されたことを示す 1 を返します。5.前の
EnQueue()
関数と同様に、ここでのCQ
パラメータも 値によって渡されるため、関数の内部を変更して も外部には影響しないことCQ.front
関数内の外部構造を変更する場合は、ポインターまたは参照を関数パラメーターとして使用する必要があります。
Ⅴ. キューの最初の要素を取得する
【サンプルコード】
// 获取栈顶元素 DataType GetTop(LkStk* LS) { if (!EmptyStack(LS)) // 判断栈是否为空 return LS->next->data; // 返回栈顶节点的数据域值 else return NULLData; // 如果栈为空,则返回空数据(NULLData) }
【コード説明】
1.コードの機能は、循環キューの先頭要素を取得することです。
2.詳細な説明は次のとおりです。
DataType
キュー内の要素のデータ型です。CycQue
循環キューの構造タイプです。maxsize
キューの最大容量です。NULLData
空のデータを表す特定の値です。3.関数では、まずキューが空かどうかを判断します。これは
EmptyQueue(CQ)
関数を呼び出すことで判断されます。キューが空の場合は、空のデータを直接返しますNULLData
。4.キューが空でない場合は、
else
次のステートメントを実行します。ここで使用して(CQ.front + 1) % maxsize
、キューの先頭の次の位置を計算し、その位置の要素を返しますCQ.data[(CQ.front + 1) % maxsize]
。5.前の関数と同様に、
CQ
ここでのパラメータも値によって渡されるため、関数内部の変更はCQ.front
外部には影響しないことに注意してください。関数内の外部構造を変更する場合は、ポインターまたは参照を関数パラメーターとして使用する必要があります。なお、実際の状況に応じてコード内で定義や初期化を行ってくださいNULLData
。
(3) キューのリンク実装
①チェーンキューの定義
[定義] 連鎖キュー - 連結リストで表されるキュー。つまり、 リストの先頭の 削除 と末尾の挿入に制限された単一連結リストです。【回路図】[説明] 当然のことながら、単一リンク リストの先頭ポインタだけではリストの最後に挿入するのは不便なので、リンク リストの最後のノードを指す末尾ポインタを追加します。[注] 2 つのポインタが添付されています。
- ヘッド ポインター フロント - テーブルのヘッド ノードを指します。キューのヘッド要素ノードは、 front->nextです。
- 末尾ポインターRear - リンクされたリストの最後のノード (つまり、キューの末尾ノード) を指します。
② チェーンキュータイプの説明
[説明] キューヘッドとキューテールの2つのポインタをまとめてカプセル化し、チェーンキューの種類を構造体として定義できます。タイプ【サンプルコード】// 定义链式队列结点 typedef struct LinkQueueNode { DataType data; // 结点数据 struct LinkQueueNode* next; // 指向下一个结点的指针 } LkQueNode; // 定义链式队列 typedef struct LkQueue { LkQueue* front; // 队头指针 LkQueue* rear; // 队尾指针 } LkQue; LkQue LQ;
【コード説明】
1.このコードは、連鎖キューの構造タイプを定義し、キュー インスタンスを作成します。
2.コードの説明は次のとおりです。
DataType
キュー内の要素のデータ型です。3.チェーン キュー ノードの構造タイプを定義します
LinkQueueNode
。
data
ノードに保存されているデータです。next
次のノードへのポインタ。4.連鎖キューの構造タイプを定義します
LkQueue
。
front
キュー内の最初のノードを指すキュー ヘッド ポインターです。rear
キューの最後のノードを指すキュー末尾ポインターです。
5
LkQue LQ;
連鎖キューに関連する操作のために連鎖キューのインスタンスを 作成しましたLQ
。上記のコードでは、
typedef
新しい型名を定義しLkQueNode
、LkQue
その後使用するためにキーワードが使用されていることに注意してください。【知らせ】
- LQ.front - チェーンされたチームのヘッドポインタ
- LQ.rear - チェーンされたチームのテールポインタ
- チェーンされたチームのオーバーフロー:無視できます; (動的なアプリケーション空間のため)
- 連鎖チームのアンダーフロー:つまり、連鎖チームが空の場合、チームを離れることも必要です。
➢ 現時点では、リンクリストには実際のノードはありません。➢ 規定:
- チェーンキューが空の場合、リア ポインタもヘッダノードを指すようにします。
➢チェーン キューのアンダーフロー条件:
- LQ.front->next==NULL
- または:LQ. フロント==LQ。後方;
③チェーンキューの基本操作
Ⅰ. 初期化
【サンプルコード】
// 初始化链式队列 void initQueue(LkQue* LQ) { LkQueNode* temp; // 创建一个临时结点指针 // 分配内存以存储临时结点 temp = (LkQueNode*)malloc(sizeof(LkQueNode)); LQ->front = temp; // 将队头指针指向临时结点 LQ->rear = temp; // 将队尾指针指向临时结点 (LQ->front)->next = NULL; // 设置临时结点的下一个结点为空 }
【回路図】
【コード説明】
1.コードの機能は、チェーンされたキューを初期化することです。
2.コードの各行のコメントの説明は次のとおりです。
LkQue* LQ
: 関連するキューを操作するために使用される受信チェーン キュー ポインター パラメーター。LkQueNode* temp
: 一時的な連鎖キュー ノード ポインタを作成します。3.この関数では、最初に
malloc
関数を 使用してtemp
、チェーン キュー ノードを格納するために一時ノードにメモリを割り当てます。sizeof(LkQueNode)
割り当てるメモリのサイズを指定するために使用されます。次に、キュー ヘッド ポインタ
LQ->front
とキュー テール ポインタの 両方がLQ->rear
一時ノードを指すように設定されtemp
、最初はキュー内にこのノードのみが存在します。4.最後に、一時ノードの次ノード ポインタ を に
(LQ->front)->next
設定しますNULL
。これは、このノードに後継ノードがないことを示します。5.以上でキューの初期化操作が完了します。
Ⅱ. キューが空であると判断する
【サンプルコード】
// 判断链式队列是否为空 int EmptyQueue(LkQue LQ) { if (LQ.rear == LQ.front) // 如果队尾指针和队头指针相同,则队列为空 return 1; // 返回1表示队列为空 else return 0; // 返回0表示队列非空 }
【コード説明】
1.コードの機能は、チェーンされたキューが空かどうかを判断することです。
2.コードの各行のコメントの説明は次のとおりです。
int
: 関数の戻り値の型は整数で、キューが空かどうかを示します。LkQue LQ
: キューが空かどうかを判断するために使用される受信連鎖キュー構造パラメーター。3.この関数では、まず、キュー
LQ.rear
末尾ポインタがキュー先頭ポインタと同じかどうかを 判断することによって、キューがLQ.front
空かどうかを判断します。同じ場合、キューには要素がないため、キューは空です。4.キュー末尾ポインタとキュー先頭ポインタが異なる場合は、キューが空ではないことを意味します。
5.最後に、関数はキューが空であることを示す 1 を返し、キューが空でないことを示す 0 を返します。
以上でキューが空かどうかの判定が完了する。
Ⅲ. 列に並びます
[定義]チームに参加する - チームの最後、つまりリンクされたリストの最後に要素 xを挿入します。
新しいノード p を生成します (データ フィールドを x に、チェーン フィールドを NULL に設定します) 新しいノード p をテーブルの末尾に挿入し、新しいキューの終了ノードになります【回路図】
【サンプルコード】
// 入队操作 void EnQueue(LkQue* LQ, DataType x) { LkQueNode* temp; // 创建一个临时结点指针 // 分配内存以存储新结点 temp = (LkQueNode*)malloc(sizeof(LkQueNode)); temp->data = x; // 设置新结点的数据为x temp->next = NULL; // 设置新结点的下一个结点为空 (LQ->rear)->next = temp; // 将新结点添加到队尾结点的后面 LQ->rear = temp; // 更新队尾指针为新结点 }
【コード説明】
1.コードの機能は、チェーン キューのエンキュー操作を実現することです。
2.コードの各行のコメントの説明は次のとおりです。
LkQue* LQ
: 関連するキューを操作するために使用される受信チェーン キュー ポインター パラメーター。DataType x
: データをエンキューします。3.この関数では、まず
malloc
関数 を使用してtemp
、データを保存するために新しいノードにメモリを割り当てます。sizeof(LkQueNode)
割り当てるメモリのサイズを指定するために使用されます。4.次に、キューに新しく追加されたデータを表す新しいノードのデータを
temp
パラメータとして 設定します。x
次に、一時ノードの次ノード ポインタ を に
(LQ->rear)->next
設定しtemp
、末尾ノードの後に新しいノードを追加します。5.
LQ->rear
最後に、新しい末尾ノードを指すように 末尾ポインタを更新しますtemp
。6.このようにして、エンキュー操作が完了します。
Ⅳ. デキュー
[定義]チーム外 - チェーンされたチーム内の先頭要素を削除し、 e に送信します。
- アンダーフローを考慮しない
- アンダーフローしない場合:
- キューヘッドノードの温度を取得します。
- head 要素を x に送信します。
- チェーンされたチームからヘッド ノードを削除します。
- チェーンされたキューに要素が 1 つだけある場合、キューは削除後に空になるため、キューの末尾ポインタを変更する必要があります。
- ノードの温度がシステムに返されます。
【回路図】
【サンプルコード】
// 出队操作 int OutQueue(LkQue* LQ) { LkQueNode* temp; // 创建一个临时结点指针 if (EmptyQueue(*LQ)) // 判断队列是否为空 { error("队空"); // 输出错误提示信息 return 0; // 返回0表示出队失败 } else { temp = (LQ->front)->next; // 获取队头结点的下一个结点 (LQ->front)->next = temp->next; // 将队头指针指向下一个结点 if (temp->next == NULL) // 如果出队后队列为空 LQ->rear = LQ->front; // 更新队尾指针为队头指针 free(temp); // 释放出队的结点 return 1; // 返回1表示出队成功 } }
【コード説明】
1.コードの機能は、チェーン キューのデキュー操作を実現することです。
2.コードの各行のコメントの説明は次のとおりです。
LkQue* LQ
: 関連するキューを操作するために使用される受信チェーン キュー ポインター パラメーター。3.関数内では、まずキューが空かどうかを判定し、
EmptyQueue(*LQ)
関数を呼び出すことで判定します。空の場合は、エラー メッセージが出力され"队空"
、デキューに失敗したことを示す 0 が返されます。4.キューが空でない場合は、デキュー操作を実行します。
5.まず、一時ノード ポインタを
temp
キュー ヘッド ノードの次のノード、つまりデキューされるノードを指します。6.次に、キューヘッドノードの次ノードポインタ を に
(LQ->front)->next
設定しますtemp->next
。つまり、キューヘッドポインタがデキューノードの次ノードを指すようにします。7.次に、デキュー操作後にキューが空かどうかを確認し、空
temp->next
であるかどうかを 判断しますNULL
。「はい」の場合、キューを出た後にキューが空になり、キュー末尾ポインタをキューLQ->rear
先頭ポインタに 更新する必要があることを意味しますLQ->front
。8.最後に、
free
関数を使用してキューからドロップアウトされたノードのスペースを解放し、メモリ リークを防ぎます。9.この関数は、デキューが成功したことを示す 1 を返します。このようにして、デキュー操作が完了します。
Ⅴ. キューの最初の要素を取得する
【サンプルコード】
// 获取队头元素的值 DataType GetHead(LkQue LQ) { LkQueNode* temp; // 创建一个临时结点指针 if (EmptyQueue(LQ)) // 判断队列是否为空 return NULLData; // 如果队列为空,则返回空数据 else { temp = LQ.front->next; // 获取队头结点的下一个结点 return temp->data; // 返回队头结点的数据 } }
【コード説明】
1.コードの機能は、連鎖キューの先頭要素の値を取得することです。
2.コードの各行のコメントの説明は次のとおりです。
DataType GetHead(LkQue LQ)
: キューの先頭の要素の値を取得します。戻り値の型は ですDataType
。LkQue LQ
: キューの先頭要素を取得するために使用される受信チェーン キュー パラメーター。3.関数内では、まずキューが空かどうかを判定し、
EmptyQueue(LQ)
関数を呼び出すことで判定します。キューが空の場合は、空のデータを直接返しますNULLData
。4.キューが空でない場合は、キューの先頭の要素を取得する操作を実行します。
5.まず、一時ノード ポインタ
temp
をキュー ヘッド ノードの次のノード、つまり取得するキュー ヘッド要素を指します。6.次に、キューヘッドノードのデータを返します
temp->data
。以上で、先頭要素の値を取得する操作が完了する。
3. アレイ
配列:特殊な線形テーブルとみなすことができ、テーブル内の配列 要素。
(1) 配列の論理構造と基本動作
【定義】配列 — 線形テーブルの拡張であり、その各要素は値と添字のセットで構成され、添字の数は配列の次元と呼ばれます。
- 配列は最もよく知られたデータ型であり、初期の高級言語では、配列が利用可能な唯一のデータ型でした。
- 配列内の各要素は均一な型を持ち、配列要素の添字には通常、上限と下限が固定されているため、配列の処理は他の複雑な構造よりも単純です。
- 多次元配列は線形リストを一般化したものです。
[注意] 配列を一度定義すると、その次元および次元境界は変更されません。したがって 、構造の初期化と破棄を除けば、配列には通常 2 つの基本操作しかありません。
- read - 一連の添字を指定して、対応するデータ要素を読み取ります
- 書き込み - 一連の添字を指定して、対応するデータ要素を変更します
[二次元配列]二次元配列:二次元配列 A mn は、m 個の行ベクトルから構成されるベクトル、または n 個の列ベクトルから構成されるベクトルとみなすことができます 。
【回路図】
(2) 配列の記憶構造
①ストレージ 構造 - シーケンシャルストレージ構造
- コンピュータのメモリ構造は 1 次元であるため、1 次元メモリで多次元配列を表現するには、配列の要素を特定の順序でシーケンスに配置し、この線形シーケンスをメモリに格納する必要があります。 。
- また、配列は通常、挿入や削除が行われないため、つまり、配列が一度確立されると、構造内の要素の数や要素間の関係は変わりません。したがって、配列を表現するには、一般にシーケンシャルストレージの方法が使用されます。
② アドレス式(主に行単位で格納)
2 次元配列 Amn は、 各要素が k 個のストレージ ユニットを占有すると 仮定して、「行優先順」でメモリに格納されます 。
- 要素 a ij の記憶アドレスは、配列のベース アドレスにa ij の前の要素が占有するセルの数を加えたものでなければなりません。
- ij は i 行 j 列にあるため、前 i 行には i×n 個の要素があり、 i 行目のij の前にはj 個の要素があるため、 i 個の要素が存在します。その前に ×n+j 個の要素があるため、ij のアドレス計算関数は次のようになります。
LOC(aij)=LOC(a00)+(i*n+j)*k
(3) 行列の圧縮保存
記憶領域を節約する ために、このタイプの行列を圧縮して保存 できます 。つまり、複数の同一の、ゼロ要素には領域は割り当てられません。
①特殊マトリックス
特殊な行列 — つまり、 非ゼロ要素またはゼロ要素の 分布 に特定の規則がある行列です。
いくつかの特別な行列の圧縮ストレージ:
- 対称行列
- 三角行列
- スパース行列
②特殊行列:対称行列
Ⅰ. 定義
[定義] n次正方行列 A において、各要素が次の性質を満たすとき、A を対称行列と呼びます。
- aij = アジ
- 0≤i
- j≤n-1
[例]次の図は次数 5 の対称行列です。
Ⅱ. 物理的な保管
- 対称行列の要素は主対角線に関して対称であるため、行列の上三角または下三角の要素が保存されている限り、2 つの対称要素ごとに保存スペースを共有することになり、保存スペースのほぼ半分が使用されます。保存することができます。
- 一般性を失うことなく、主対角線より下の要素 (対角線を含む) を「行優先順」で格納します。その格納形式を次の図に示します。
- この下三角行列では、行 i には正確に i 個の要素があり、要素の総数は次のとおりです。
∑(i)=n(n+1)/2
- したがって、これらの要素を、図の矢印で示された順序で 1 次元配列 S[0..n(n+1)/2-1]に格納できます 。
- 対称行列 A の要素へのアクセスを容易にするために、aij と S[k] の間の対応関係を見つける必要があります。
添字変換式: (下の)
- i≥jの場合 、 aij は下の三角形にあります。aij の前の行 i (行 0 から行 i-1 まで) には 1+2+…+i=i(i+1)/2 要素があり、オンラインです。i、aij の前にちょうど j 個の要素 (つまり、 ai0 、 ai1 、 ai2 、 ai3 、…、ai j-1) があるため、次のようになります。
- i<jの場合 、aij は上三角行列内にあります。 aij=aji であるため、上記の対応関係で i と j を交換するだけで次のようになります。
- 上記の添字交換関係により、任意の添字セット (i, j) について、行列要素 aij は S[k] で見つかります。それ以外の場合は、すべての k=0,1,2,3 ,...n について行列要素 aij を見つけることができます。 (n+1)/2-1 により、行列内の S[k] の要素の位置 (i, j) を決定できます。したがって、次の図に示すように、S[n(n+1)/2] は対称行列 A の圧縮ストレージと呼ばれます。
- たとえば、a31 と a13 は両方とも sa[7] に格納されます。その理由は次のとおりです。
k=i*(i+1)/2+j=3*(3+1)/2+1=7
③特殊マトリックス:三角マトリックス
Ⅰ. 定義
[定義] 三角行列を主対角線で割ると、上三角行列と下三角行列の 2 種類があります。
- 図には上三角行列が示されており、その下三角の要素(主対角を除く)はすべて定数です。
- 下三角行列はその逆で、図に示すように、主対角線上に定数があります。
- ほとんどの場合、三角行列定数は 0 です。
【例】
【イラスト】
三角行列の繰り返し要素 c は記憶領域を共有でき、残りの要素は正確に n(n+1)/2を持ちます 。したがって、三角行列を圧縮してベクトル s[0..n(n +1)/2 ]、c はベクトルの最後のコンポーネントに格納されます。
Ⅱ. 上三角行列
【解説】 上三角行列 では、主対角線 上 の p 行目 (0≦p<n) にはちょうど np 個の要素があり、 上 三角行列の要素 a ij を行先順に格納する と、行 i には(np)=i(2n-i+1)/2要素があり、行 i の ij の前にちょうど ji 要素があります: a ij 、a ij +1 、… a ij-1。[式]したがって、s[k]とa ij の対応関係は次のようになります。
Ⅲ. 下三角行列
[式]下三角行列の格納は対称行列の格納と同様であり、s[k] と aij の対応関係は次のとおりです。
④特殊行列:疎行列
Ⅰ. 定義
スパース行列:行列 A に s 個の非ゼロ要素があると仮定します。sが行列要素の総数よりはるかに小さい場合、 A はスパース行列と呼ばれます。
- スパース行列の圧縮ストレージ 。 つまり、スパース行列内の非ゼロ要素のみが格納されます。
- 目的:保管スペースを節約します。
Ⅱ. 三つ子
- 非ゼロ要素の分布は一般に不規則であるため、非ゼロ要素を格納する際には、非ゼロ要素が配置されている行と列の位置 (i, j) も記録する必要があります。
- 逆に、トリプレット (i,j,a ij )は行列 A の非ゼロのエントリを一意に識別します。
- したがって、スパース行列は、非ゼロ要素とその行数と列数を表すトリプルによって一意に識別できます。
Ⅲ.トリプルテーブル
次のトリプレット テーブル:
( (0,1,12)、(0,2,9)、(2,0,- 3)、(2,5,14)、(3,2,24)、(4,1,18)、 (5,0,15),(5,3,-7) )
- 行と列の値のペア (5,6) を加算することは、次の行列 M の別の記述として使用できます。
- 前述のトリプル テーブルのさまざまな表現方法は、スパース行列のさまざまな圧縮保存方法につながる可能性があります。
Ⅳ. トリプルシーケンスリスト
[表現] 疎行列の三重数列表表現—— 行列の非ゼロ要素を三重に変換し、行の非降順(列の行の昇順)でメモリに格納します。 。[三重テーブル構造] 三重テーブル構造: 三重テーブルが逐次記憶構造で表現されると仮定すると、疎モーメントが得られます。配列の圧縮保存方法 - トリプル シーケンス テーブル。[コード例] スパース行列トリプレットテーブルの格納タイプ:const int maxnum = 10; // 定义非零元素的最大个数为10 typedef struct node { int i, j; // 非零元的行下标和列下标 DataType v; // 非零元素的值 } NODE; // 定义三元组结构体 // 定义稀疏矩阵结构体 typedef struct spmatrix { NODE data[maxnum]; // 非零元三元组表 int mu, nu, tu; // 矩阵的行数、列数和非零元个数 } SpMtx;
[概略図] SpMtrx M ; とすると、下図に示す疎行列のトリプレットの表現は次のようになります。
【コード説明】1.コードの各行のコメントの説明は次のとおりです。
const int maxnum = 10;
定数が定義されておりmaxnum
、非ゼロ要素の最大数が 10 であることを示します。typedef struct node
という名前の構造体型を定義しますnode
。int i, j;
2 つの整数変数と がnode
構造体 で定義されており 、 それぞれ非ゼロ要素の行添字と列添字を表します。i
j
DataType v;
type の変数がnode
構造体で定義され 、 ゼロ以外の要素の値を表します。DataType
v
NODE;
タイプは定義されておりNODE
、トリプレット構造です。typedef struct spmatrix
という名前の構造体型を定義しますspmatrix
。NODE data[maxnum];
spmatrix
配列は構造体 で 定義されdata
、非ゼロ要素のトリプル テーブルを格納するために使用されます。配列のサイズは ですmaxnum
。これは、ゼロ以外の要素の最大数です。int mu, nu, tu;
3 つの整数変数、 および が 構造spmatrix
体 で定義されており 、 それぞれ行列の行、列、および非ゼロ要素の数を表します。mu
nu
tu
SpMtx;
タイプは定義されておりSpMtx
、スパース行列構造です。2.
SpMtx
このコードは、トリプレットの配列data
と 3 つの整数変数mu
、nu
および から構成されるスパース行列データ構造 を定義しますtu
。3.配列
data
は、疎行列の非ゼロ要素のトリプレット テーブルを格納するために使用され、 および はmu
それぞれnu
行列tu
の行、列、および非ゼロ要素の数を表します。4.
maxnum
ゼロ以外の要素の最大数を制限します。