【データリベレーション02】スタック

スタックは、後入れ先出し (LIFO、後入れ先出し) 原則に基づいたデータ構造です。これは、皿の積み重ねや本の山に似た、現実の積み重ねをシミュレートします。

スタックには、プッシュとポップという 2 つの基本操作があります。

  1. プッシュ: 新しい要素をスタックの先頭に追加します。新しい要素が現在のスタックの最上位要素になります。
  2. Pop: スタックの最上位から最上位の要素を削除し、その要素の値を返します。

これら 2 つの基本操作に加えて、スタックは次のような他の一般的な操作もサポートできます。

  • Top: スタックの最上位要素を削除せずに取得します。
  • 空テスト (isEmpty): スタックが空かどうかを確認します。
  • サイズの取得: スタック内の要素の数を取得します。

実際、スタックは配列またはリンク リストとして実装できます。

配列を使用して実装されたスタックは、配列ベースのスタックと呼ばれます。シーケンシャル スタックでは、配列の末尾がスタックの先頭として使用され、各プッシュ操作では要素が配列の末尾に配置され、ポップ操作では配列の末尾から要素が削除されます。

リンクリストを使用して実装されたスタックをリンクスタックと呼びます。連鎖スタックでは、各ノードに要素と次のノードへの参照が含まれます。プッシュ操作ではリンク リストの先頭に新しいノードが挿入され、ポップ操作ではリンク リストの先頭からノードが削除されます。

スタックは、コンピューター サイエンスにおいて幅広い用途に使用できます。たとえば、プログラミングでは、関数呼び出しのプロセスでスタックがよく使用されます。関数が呼び出されるたびに、その関連情報 (パラメーター、ローカル変数など) がスタックにプッシュされます。関数が実行されると、 、この情報はスタックからポップされます。このアプローチにより、プログラムは関数へのネストされた呼び出しを追跡し、実行状態を正しく復元できるようになります。

スタックは、ブラケット マッチング、式の評価、深さ優先検索アルゴリズム、バックトラッキング アルゴリズムなど、他の多くの問題を解決するためにも使用されます。そのシンプルさと効率性により、スタックは重要なデータ構造になります。

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

スタックの抽象データの説明

スタックは、後入れ先出し (LIFO、後入れ先出し) 特性を持つデータ構造を記述するために使用される抽象データ型 (ADT) です。次の操作を定義します。

  1. 初期化: 空のスタックを作成します。
  2. プッシュ: 新しい要素をスタックの先頭に追加します。
  3. Pop: スタックの最上位から最上位の要素を削除し、その要素の値を返します。
  4. Top: スタックの最上位要素を削除せずに取得します。
  5. 空のテスト (IsEmpty): スタックが空かどうかを確認します。
  6. サイズの取得 (Size): スタック内の要素の数を取得します。

これらの操作は、スタックの基本的な動作と特性を定義します。これらの操作を使用すると、配列またはリンク リスト ベースの実装など、さまざまな具体的なスタック実装を実装できます。

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します
[例] 3 文字を ABC の順でスタックにプッシュした場合
• ABC のすべての順列が可能
シーケンスですかそれはスタックからポップされたものですか?
• CAB のようなシーケンスを生成することは可能ですか?

再帰中、スタックと元の文字シーケンスへのポインタを維持します。スタックの現在の最上位要素がポインタが指す要素と同じである場合は、その要素をスタックからポップできますが、そうでない場合は、ポインタが指す要素をスタックにプッシュする必要があります。元の文字シーケンス内のすべての要素がスタックにプッシュされたら、要素をスタックから徐々にポップして、可能なポップ シーケンスを取得できます。

上記の方法を使用すると、考えられるすべてのポップ シーケンスを取得できます。 CAB で始まるシーケンスが含まれている場合、CAB がポップ シーケンスの可能性があることを意味します。そうしないと、CAB などのシーケンスを生成できません。

概要: スタックを ABC の順にプッシュします。ポップ シーケンスとしては、ABC、ACB、BAC、BCA、CBA、CAB の 6 つがあります。したがって、CAB はポップ シーケンスの可能性があります。

スタックのシーケンシャルストレージ実装

ここに画像の説明を挿入します

ここに画像の説明を挿入します

#define MAXSIZE 100 // 定义栈的最大容量

typedef struct {
    
    
    ElementType data[MAXSIZE]; // 用数组存储栈元素
    int top; // 栈顶指针,指向当前栈顶元素的位置
} Stack;

// 初始化栈
void InitStack(Stack *S) {
    
    
    S->top = -1; // 初始化栈顶指针为-1,表示空栈
}

// 判断栈是否为空
int IsEmpty(Stack *S) {
    
    
    return (S->top == -1);
}

// 判断栈是否已满
int IsFull(Stack *S) {
    
    
    return (S->top == MAXSIZE - 1);
}

// 入栈操作
void Push(Stack *S, ElementType item) {
    
    
    if (IsFull(S)) {
    
    
        printf("Stack is full. Cannot push element %d.\n", item);
    } else {
    
    
        S->data[++(S->top)] = item;
    }
}

// 出栈操作
ElementType Pop(Stack *S) {
    
    
    if (IsEmpty(S)) {
    
    
        printf("Stack is empty. Cannot pop element.\n");
        return ERROR; // ERROR可以是一个预定义的错误值
    } else {
    
    
        return S->data[(S->top)--];
    }
}

// 获取栈顶元素
ElementType GetTop(Stack *S) {
    
    
    if (IsEmpty(S)) {
    
    
        printf("Stack is empty. No top element.\n");
        return ERROR;
    } else {
    
    
        return S->data[S->top];
    }
}

ここに画像の説明を挿入します
ここに画像の説明を挿入します

スタックのチェーンストレージ実装

ここに画像の説明を挿入します
ここに画像の説明を挿入します

typedef struct StackNode {
    
    
    ElementType data; // 数据域
    struct StackNode *next; // 指针域,指向下一个节点
} StackNode;

typedef struct {
    
    
    StackNode *top; // 栈顶指针,指向当前栈顶元素
} LinkedStack;

// 初始化栈
void InitStack(LinkedStack *S) {
    
    
    S->top = NULL; // 初始化栈顶指针为空,表示空栈
}

// 判断栈是否为空
int IsEmpty(LinkedStack *S) {
    
    
    return (S->top == NULL);
}

// 入栈操作
void Push(LinkedStack *S, ElementType item) {
    
    
    StackNode *newNode = (StackNode *)malloc(sizeof(StackNode)); // 创建新节点
    newNode->data = item; // 设置新节点的数据域为要入栈的元素
    newNode->next = S->top; // 将新节点插入到栈顶
    S->top = newNode; // 更新栈顶指针
}

// 出栈操作
ElementType Pop(LinkedStack *S) {
    
    
    if (IsEmpty(S)) {
    
    
        printf("Stack is empty. Cannot pop element.\n");
        return ERROR; // ERROR可以是一个预定义的错误值
    } else {
    
    
        StackNode *temp = S->top; // 保存当前栈顶节点
        ElementType item = temp->data; // 获取栈顶元素的值
        S->top = temp->next; // 更新栈顶指针
        free(temp); // 释放原栈顶节点
        return item;
    }
}

// 获取栈顶元素
ElementType GetTop(LinkedStack *S) {
    
    
    if (IsEmpty(S)) {
    
    
        printf("Stack is empty. No top element.\n");
        return ERROR;
    } else {
    
    
        return S->top->data;
    }
}

スタック アプリ: 式の評価

式の評価に関しては、スタックの使用を検討できます。以下は、スタックを使用して中置式を評価する方法を示す、より複雑な例です。

解決したい式が中置式であると仮定します: (3 + 4) * 5 - 6 / 2

  1. 空のスタックと演算子の優先順位辞書を作成します。
  2. 中置式の各要素を左から右に繰り返します。
  3. 現在の要素が数値の場合、整数に変換され、スタックに直接プッシュされます。
  4. 現在の要素が演算子の場合は、次の手順を実行します。
    • スタックが空であるか、スタックの最上位要素が左括弧 "(" である場合、現在の演算子がスタックにプッシュされます。
    • スタックが空でなく、現在のオペレーターの優先順位がスタックの一番上のオペレーターの優先順位より大きい場合、現在のオペレーターはスタックにプッシュされます。
    • スタックが空でなく、現在の演算子の優先順位がスタックの先頭の演算子の優先順位以下の場合、スタックの先頭の演算子がポップされて計算され、計算結果がプッシュされます。スタックに。条件が満たされるまでこの手順を繰り返し、条件が満たされたら現在の演算子をスタックにプッシュします。
    • 現在の要素が右括弧 ")" の場合、スタックの一番上の演算子がポップされ、左括弧 "(" に遭遇するまで計算されます。左括弧 "(" はスタックからポップされますが、計算は行われません。実行されました。
  5. 中置式を走査した後、スタック内の残りの演算子がポップされて計算されます。
  6. スタックには式の結果である要素が 1 つだけ残っています。

以下は、中置式 (3 + 4) * 5 - 6 / 2 を評価するための具体的な手順です。

  1. 空のスタックと演算子の優先順位辞書を作成します (加算と減算は優先順位 1、乗算と除算は優先順位 2)。
  2. 「(」までトラバースしてスタックにプッシュします。
  3. 3 までトラバースし、整数に変換してスタックにプッシュします。
  4. 「+」までトラバースし、スタックの最上位は「(」です。「+」をスタックにプッシュします。
  5. 4 までトラバースし、整数に変換してスタックにプッシュします。
  6. ")" までトラバースし、演算子 "+" をスタックの一番上にポップアップし、3+4=7 を得る計算を実行し、計算結果 7 をスタックにプッシュします。
  7. は「」に移動します。スタックの最上位要素は 7 で、優先度は「」より大きく、「*」はスタックにプッシュされました。
  8. 5 までトラバースし、整数に変換してスタックにプッシュします。
  9. 「-」までトラバースします。スタックの最上位要素は「」です。優先度は「-」以下です。スタックの最上位演算子をポップします。」 " そして計算を実行し、7*5=35 を取得し、計算結果 35 をスタックにプッシュします。
  10. 6 までトラバースし、整数に変換してスタックにプッシュします。
  11. 「/」までトラバースし、スタックの最上位要素は 6、優先度は「/」以下、最上位演算子「/」をポップアップして計算を実行し、6/2=3 を取得し、計算をプッシュします。結果 3 をスタックに置きます。
  12. 中置式を走査した後、スタックには要素 35-3 が 1 つだけ残ります。これは式の計算結果です。

したがって、中置式 (3 + 4) * 5 - 6 / 2 の値は 32 になります。

中置式を後置式に変換する方法

中置式を後置式に変換する一般的な方法は、スタックを使用することです。

変換プロセスの手順は次のとおりです。

  1. 後置式を格納するために空のスタックと空のリストを作成します。
  2. 中置式の各要素を左から右に繰り返します。
  3. 現在の要素が数字または文字の場合、接尾辞式リストの最後に直接追加されます。
  4. 現在の要素が左括弧 "(" の場合、それをスタックにプッシュします。
  5. 現在の要素が右括弧 ")" の場合、左括弧 "(" が見つかるまで、スタック内の要素がポップされて接尾辞式リストに追加されます。その後、左括弧はスタックからポップされますが、追加されません。を後置式リストに追加します。
  6. 現在の要素が演算子 (「+」、「-」、「*」、「/」など) の場合、次の操作が実行されます。
    • スタックが空の場合は、現在のオペレーターをスタックにプッシュします。
    • スタックが空でなく、スタックの最上位要素が左括弧 "(" である場合、現在の演算子がスタックにプッシュされます。
    • スタックが空でなく、現在の演算子の優先順位がスタックの先頭にある演算子の優先順位以下である場合は、演算子をスタックの先頭にポップし、後置式リストに追加します。を繰り返します。条件が満たされるまでこのステップを実行し、その後現在の演算子をスタックから削除し、スタックにプッシュします。
    • スタックが空でなく、現在のオペレーターの優先順位がスタックの一番上のオペレーターの優先順位より大きい場合、現在のオペレーターはスタックにプッシュされます。
  7. 中置式を走査した後、残りの演算子をスタックからポップし、後置式リストに追加します。
  8. 後置式リストは、変換された後置式です。

以下に例を示します。

中置式: 2 + 3 * 4

変換プロセス:

  1. 2 まで移動し、接尾辞式リストに直接追加します。
  2. + にトラバースすると、スタックは空になります。スタックにプッシュします。
  3. 3 まで移動し、接尾辞式リストに直接追加します。
  4. * までトラバースします。スタックは空ではありません。スタックの先頭は + です。* をスタックにプッシュします。
  5. 4 まで移動し、接尾辞式リストに直接追加します。
  6. 中置式を調べた後、スタック内の残りの演算子 + と * をポップし、後置式リストに追加します。

変換された後置式: 2 3 4 * +

したがって、中置式 2 + 3 * 4 は後置式 2 3 4 * + に変換できます。

ここに画像の説明を挿入します
ここに画像の説明を挿入します
ここに画像の説明を挿入します

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/shaozheng0503/article/details/134894246