目次
3. スタックの使用 (Leetcode20. 有効な括弧 +225)
4. 2 つのキューを使用してスタックを実装する (225. キューを使用してスタックを実装する - Leetcode)
5. 2 つのスタックでキューを実現 (232. スタックでキューを実現 - Leetcode)
1. データ構造 - スタック
1. スタックの基本的な紹介
スタックは、データが先入れ先出し、後出し、後入れ先出しのデータ構造です。
- スタックは通常、配列で実装されます(単一のリンクされたリストで実装されたスタックには明らかな欠陥があります。リストの最後にある前のデータのアドレスを取得する必要があるため、データ ポッピング操作の時間の複雑さは O(N) です。見つかります)
2. スタックの実装
全体図:
スタック ヘッダー ファイル:
#pragma once #include <stdio.h> #include <assert.h> #include <stdbool.h> #include <stdlib.h> typedef int STDataType; typedef struct Stack { STDataType * arry; //栈的堆区空间指针 int top; //维护栈顶下标,同时记录栈中的元素个数 int capacity; //记录栈的容量 }ST; void StackInit(ST* Stack); //栈对象的初始化 STDataType StackTop(ST*Stack); //返回栈顶元素 void StackPush(ST* Stack,STDataType val); //元素入栈 void StackPop(ST* Stack); //元素出栈 int StackSize(ST* Stack); //返回栈对的元素个数的接口; bool StackEmpty(ST* Stack); //判断栈是否为空的接口 void StackDestroy(ST* Stack); //销毁栈
スタック初期化インターフェース:
void StackInit(ST* Stack) { assert(Stack); Stack->arry = NULL; Stack->capacity = 0; Stack->top = 0; }
スタックの最上位要素のインターフェイスを返します。
STDataType StackTop(ST* Stack) { assert(Stack); assert(Stack->top > 0); return Stack->arry[Stack->top - 1]; }
スタック データの数を返すインターフェイス:
int StackSize(ST* Stack) { assert(Stack); return Stack->top; }
スタックが空かどうかを判断するためのインターフェース:
bool StackEmpty(ST* Stack) { assert(Stack); return (Stack->top == 0); }
データプッシュ操作インターフェース:
void StackPush(ST* Stack, STDataType val) { assert(Stack); if (Stack->capacity == Stack->top) //检查容量是否足够 { int newcapacity = (0 == Stack->capacity) ? 4 : 2 * Stack->capacity; //如果容量为零,则将容量设置为4,其他情况下按照两倍扩容方式增容 STDataType* tem = (STDataType*)realloc(Stack->arry, newcapacity*sizeof(STDataType)); if (NULL == tem)//判断realloc是否成功 { perror("realloc failed:"); exit(-1); } Stack->capacity = newcapacity; Stack->arry = tem; } //元素入栈 Stack->arry[Stack->top] = val; Stack->top++; }
データスタック操作インターフェース:
void StackPop(ST* Stack) { assert(Stack); assert(Stack->top > 0); Stack->top--; }
スタックを破棄するインターフェース:
void StackDestroy(ST* Stack) { assert(Stack); if (Stack->arry) { free(Stack->arry); } Stack->arry = NULL; Stack->capacity = 0; Stack->top = 0; }
2. データ構造 - キュー
1. キューの基本的な紹介
キューは、データが先入れ先出し、後入れ後出しのデータ構造です。
- データはキューの最後からのみ入り、キューの先頭から出ること ができます
- キューは通常、単連結リストで実装されます。これは、キューがデータの先頭削除操作を含み、単連結リストのデータ先頭削除操作の時間計算量が O(1) であるためです。
2.キューの実装
キューの全体図:
キュー ヘッダー ファイル:
#pragma once #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <assert.h> typedef int QDataType; typedef struct QueueNode //单链表结构体 { struct QueueNode* next; QDataType data; }QNode; typedef struct Queue //维护队列的结构体 { QNode* head; QNode* tail; }Queue; void QueueInit(Queue* queue); //队列的初始化接口 void QueueDestroy(Queue* queue); //销毁队列的接口 void QueuePush(Queue* queue,QDataType val); //数据入队接口 void QueuePop(Queue* queue); //数据出队接口 QDataType QueueFront(Queue* queue); //返回队头数据接口 QDataType QueueBawck(Queue* queue); //返回队尾数据接口 int QueueSize(Queue* queue); //返回队列数据个数的接口 bool QueueEmpty(Queue* queue); //判断队列是否为空的接口
キューの初期化インターフェース:
void QueueInit(Queue* queue) //队列初始化 { assert(queue); queue->head = NULL; queue->tail = NULL; }
キュー ヘッド データを返すインターフェイス:
QDataType QueueFront(Queue* queue) //返回队列头部数据 { assert(queue); assert(queue->head); return queue->head->data; }
キューの最後にあるデータを返すインターフェイス:
QDataType QueueBawck(Queue* queue) //返回队列尾部数据 { assert(queue); assert(queue->tail); return queue->tail->data; }
キュー データの数を返すインターフェイス:
int QueueSize(Queue* queue) //返回队列节点个数 { assert(queue); int count = 0; QNode* tem = queue->head; while(tem) { count++; tem = tem->next; } return count; }
キューが空かどうかを判断するためのインターフェース:
bool QueueEmpty(Queue* queue) //判断队列是否为空 { assert(queue); return (NULL == queue->head); }
データ エンキュー インターフェイス:
void QueuePush(Queue* queue,QDataType val) //链表尾插数据(数据从队列尾入队) { assert(queue); QNode* NewNode = (QNode*)malloc(sizeof(QNode));//申请堆区链表节点 if (NULL == NewNode) { perror("malloc failed:"); exit(-1); } NewNode->data = val; NewNode->next = NULL; if (NULL == queue->tail) //链表为空时的数据插入操作 { assert(NULL == queue->head); queue->tail = NewNode; queue->head = NewNode; } else //链表非空时的数据插入操作 { queue->tail->next = NewNode; queue->tail = NewNode; } }
データ デキュー インターフェイス:
void QueuePop(Queue* queue) //删去链表头节点(数据从队列头部出队) { assert(queue); assert(queue->head && queue->tail); QNode* Next = queue->head->next; free(queue->head); queue->head = Next; if (NULL == queue->head) //若删去的是链表的最后一个元素,则要将尾指针也置空 { queue->tail = NULL; } }
キューを破棄するためのインターフェース:
void QueueDestroy(Queue* queue) //销毁链表(队列) { assert(queue); QNode* tem = queue->head; while (tem) { QNode* Next = tem->next; free(tem); tem = Next; } queue->head = NULL; queue->head = NULL; }
3. スタックの使用 (Leetcode20. 有効な括弧 +225)
1. 問題の説明
のみを含む文字列が 与えられた場合 、その文字列が有効かどうかを判断します。
'(' ,
')' ,
'{' ,
'}' ,
'[' ,
']'
s
有効な文字列は次を満たす必要があります。
- 左括弧は、同じタイプの右括弧で閉じる必要があります。
- 左括弧は正しい順序で閉じる必要があります。
- 各閉じ括弧には、対応する同じタイプの開き括弧があります。
例:
入力: s = "()[]{}" 出力: true
C ソリューション インターフェイス:
bool isValid(char * s) { }
2. 問題分析
文字列を左からときのブラケット マッチングのアイデア:
- 文字列 をトラバースするときに、現在の左ブラケットがどの右ブラケットと一致するかを判断できないため、一致の考え方は、最初の右ブラケットを最初に見つけてから、対応する左ブラケットを探すことです。一致が失敗した場合、 false を返し、一致が成功した場合は、次の閉じ括弧を探し続け、最後まで上記のプロセスを繰り返します。
- 暴力的なマッチング アルゴリズムのアニメーション分析:
- 上記の分析によると、暴力的なマッチング方法の時間計算量は O(N^2) であることがわかります。
しかし、この問題が補助スタックを使用して解決されれば、時間効率のためにスペースを交換するという目的を達成することができます。
基本的な考え方は次のとおりです。
- ポインターを使用して文字列をトラバースし、開始括弧に遭遇するたびに、それをスタックにプッシュします
- 右括弧に出会うたびに, スタックの一番上にある括弧と照合します. 照合に失敗した場合, 文字列が条件を満たしていないことを意味し, インターフェイスは false を返します.照合が成功した場合,スタックの一番上の要素がポップされ、引き続き文字列をトラバースして上記の手順を繰り返します インターフェイスは「\0」が見つかるまで true を返します
- アルゴリズムのアニメーション図:
実装されたスタック (この号の最初のセクション) を使用して問題を解決できます (ちなみに、スタックの実装にバグがないことを確認することもできます)。
ソリューション コード:
ソリューションのスタック:
typedef char STDataType; typedef struct Stack { STDataType * arry; //栈的堆区空间指针 int top; //维护栈顶下标,同时记录栈中的元素个数 int capacity; //记录栈的容量 }ST; void StackInit(ST* Stack) { assert(Stack); Stack->arry = NULL; Stack->capacity = 0; Stack->top = 0; } STDataType StackTop(ST* Stack) { assert(Stack); assert(Stack->top > 0); return Stack->arry[Stack->top - 1]; } void StackPush(ST* Stack, STDataType val) { assert(Stack); if (Stack->capacity == Stack->top) //检查容量是否足够 { int newcapacity = (0 == Stack->capacity) ? 4 : 2 * Stack->capacity; STDataType* tem = (STDataType*)realloc(Stack->arry, newcapacity*sizeof(STDataType)); if (NULL == tem) { perror("realloc failed:"); exit(-1); } Stack->capacity = newcapacity; Stack->arry = tem; } Stack->arry[Stack->top] = val; Stack->top++; } void StackPop(ST* Stack) { assert(Stack); assert(Stack->top > 0); Stack->top--; } int StackSize(ST* Stack) { assert(Stack); return Stack->top; } bool StackEmpty(ST* Stack) { assert(Stack); return (Stack->top == 0); } void StackDestroy(ST* Stack) { assert(Stack); if (Stack->arry) { free(Stack->arry); } Stack->arry = NULL; Stack->capacity = 0; Stack->top = 0; }
2 つの補助判定インターフェイス コード:
//判断指针指向的括号是否为右括号的接口 bool CheckRightQutoe(const char quote) //左括号返回true,有括号返回false { if('{' == quote || '(' == quote || '[' == quote) { return true; } else { return false; } }
//判断两个括号是否匹配的接口 bool JudgeMatch(const char right,const char left) { if(('}' == right && '{' == left) || (']' == right && '[' == left) || (')' == right && '(' == left) ) { return true; } return false; }
ソリューション インターフェイス コード:
bool isValid(char * s) { ST ans; StackInit(&ans); //创建一个栈 int lenth = strlen(s); //字符串有效字符个数为奇数直接返回false if(lenth % 2 == 1) { return false; } char * tem = s; //tem用于遍历字符串 while('\0' != *tem) { if(CheckRightQutoe(*tem)) //遇到右括号则入栈 { StackPush(&ans,*tem); } else //遇到左括号则与栈顶右括号匹配 { if(StackEmpty(&ans) || !JudgeMatch(*tem,StackTop(&ans))) { return false; //匹配前注意判断栈是否为空 } StackPop(&ans); //匹配完后弹出栈顶括号 } tem ++; } if(!StackEmpty(&ans)) //栈为空说明全部括号都完成了匹配 { return false; } return true; }
- この方法は、スタックデータの後入れ先出しの特性を最大限に活用します。
- アルゴリズムの時間計算量は O(N)
4. 2 つのキューを使用してスタックを実装する ( 225. キューを使用してスタックを実装する - Leetcode )
この問題は実用的な意味を持たず、スタックとキューという 2 つのデータ構造について理解を深めることが目的です。
- 2 つのキューを使用してデータの先入れ後出しを実現する
ソリューション インターフェイス:
typedef struct { } MyStack; //创建MyStack结构体 MyStack* myStackCreate() { } //向MyStack两个队列成员中的非空队列中插入数据 //(若两个队列都为空则向任意一个队列插入数据都可以) void myStackPush(MyStack* obj, int x) { } //删除栈顶元素(同时返回栈顶元素的值) int myStackPop(MyStack* obj) { } //栈顶的数据其实就是非空队列尾的数据 int myStackTop(MyStack* obj) { } //返回栈顶的数据 bool myStackEmpty(MyStack* obj) { } //销毁栈 void myStackFree(MyStack* obj) { }
自分でルールを設定できます。キューによって実装された「スタック」のインターフェースでは、キューの標準操作インターフェースのみを使用して、「スタック」内の要素の入口と出口を操作できます。
- この質問では、この問題の 2 番目のセクションで実装されたキューを使用して、スタックを実装します。
2 つのキューを持つスタックのデータ構造の全体図:
2 つのキューは、スタック構造の定義を実装します。
typedef struct { Queue queue1; Queue queue2; } MyStack;
「スタック」インターフェースへのデータ:
void myStackPush(MyStack* obj, int x)
- MyStack の 2 つのキュー メンバーの空でないキューにデータを挿入する
- (両方のキューが空の場合は、任意のキューにデータを挿入できます)
//向MyStack两个队列成员中的非空队列中插入数据 //(若两个队列都为空则向任意一个队列插入数据都可以) void myStackPush(MyStack* obj, int x) { if(!QueueEmpty(&(obj->queue1))) { QueuePush(&(obj->queue1),x); } else { QueuePush(&(obj->queue2),x); } }
データ ポップアップ インターフェイス:
int myStackPop(MyStack* obj)
データ ポッピング プロセスのアニメーション イラスト:
//删除栈顶元素(同时返回栈顶元素的值) int myStackPop(MyStack* obj) { int ret = 0; //将非空队列里面的(除了队尾的元素)移到另外一个空队列中 if(!QueueEmpty(&(obj->queue1)) && QueueEmpty(&(obj->queue2))) { while(obj->queue1.head != obj->queue1.tail) //转移数据 { QueuePush(&(obj->queue2),(obj->queue1).head->data); QueuePop(&(obj->queue1)); } ret = QueueFront(&(obj->queue1)); //记录要弹出的数据 QueuePop(&(obj->queue1)); //弹出数据 } else { while(obj->queue2.head != obj->queue2.tail) //转移数据 { QueuePush(&(obj->queue1),(obj->queue2).head->data); QueuePop(&(obj->queue2)); } ret = QueueFront(&(obj->queue2)); //记录要弹出的数据 QueuePop(&(obj->queue2)); //弹出数据 } return ret; //返回被弹出的数据 }
- 2 つのキューの相互作用により、データの先入れ後出しが完了しました。
ソリューション コード:
問題解決の待ち行列:
typedef int QDataType; typedef struct QueueNode //队列链表节点结构体 { struct QueueNode* next; QDataType data; }QNode; typedef struct Queue //维护队列的结构体 { QNode* head; QNode* tail; }Queue; void QueueInit(Queue* queue); //队列初始化 void QueueDestroy(Queue* queue); //销毁链表(队列) void QueuePush(Queue* queue,QDataType val); //链表尾插数据(数据从队列尾入队) void QueuePop(Queue* queue); //删去链表头节点(数据从队列头部出队) QDataType QueueFront(Queue* queue); //返回队列头部数据 QDataType QueueBawck(Queue* queue); //返回队列尾部数据 int QueueSize(Queue* queue); //返回队列节点个数 bool QueueEmpty(Queue* queue); //判断队列是否为空 void QueueInit(Queue* queue) //队列初始化 { assert(queue); queue->head = NULL; queue->tail = NULL; } void QueueDestroy(Queue* queue) //销毁链表(队列) { assert(queue); QNode* tem = queue->head; while (tem) //逐个节点释放链表 { QNode* Next = tem->next; free(tem); tem = Next; } queue->head = NULL; queue->head = NULL; } void QueuePush(Queue* queue,QDataType val) //链表尾插数据(数据从队列尾入队) { assert(queue); QNode* NewNode = (QNode*)malloc(sizeof(QNode)); if (NULL == NewNode) //在堆区申请节点空间 { perror("malloc failed:"); exit(-1); } NewNode->data = val; NewNode->next = NULL; if (NULL == queue->tail) //注意判断队列是否为空队列并分类讨论完成元素插入 { assert(NULL == queue->head); queue->tail = NewNode; //队列为空时tail和head指向同一个节点 queue->head = NewNode; } else { queue->tail->next = NewNode; //队列非空时令tail指向队尾数据 queue->tail = NewNode; } } void QueuePop(Queue* queue) //删去链表头节点(数据从队列头部出队) { assert(queue); assert(queue->head && queue->tail); QNode* Next = queue->head->next; free(queue->head); queue->head = Next; if (NULL == queue->head) //注意判断完成删除元素后队列是否变为空队列 { queue->tail = NULL; } } QDataType QueueFront(Queue* queue) //返回队列头部数据 { assert(queue); assert(queue->head); return queue->head->data; } QDataType QueueBawck(Queue* queue) //返回队列尾部数据 { assert(queue); assert(queue->tail); return queue->tail->data; } int QueueSize(Queue* queue) //返回队列节点个数 { assert(queue); int count = 0; QNode* tem = queue->head; while(tem) //计算节点个数 { count++; tem = tem->next; } return count; } bool QueueEmpty(Queue* queue) //判断队列是否为空 { assert(queue); return (NULL == queue->head); }
ソリューション スタック:
typedef struct { Queue queue1; Queue queue2; } MyStack; //创建MyStack结构体 MyStack* myStackCreate() { MyStack * tem = (MyStack *)malloc(sizeof(MyStack)); if(NULL == tem) { perror("malloc failed:"); exit(-1); } QueueInit(&(tem->queue1)); QueueInit(&(tem->queue2)); return tem; } //向MyStack两个队列成员中的非空队列中插入数据 //(若两个队列都为空则向任意一个队列插入数据都可以) void myStackPush(MyStack* obj, int x) { if(!QueueEmpty(&(obj->queue1))) { QueuePush(&(obj->queue1),x); } else { QueuePush(&(obj->queue2),x); } } //删除栈顶元素(同时返回栈顶元素的值) int myStackPop(MyStack* obj) { int ret = 0; //将非空队列里面的(除了队尾的元素)移到另外一个空队列中 if(!QueueEmpty(&(obj->queue1)) && QueueEmpty(&(obj->queue2))) { while(obj->queue1.head != obj->queue1.tail) //转移数据 { QueuePush(&(obj->queue2),(obj->queue1).head->data); QueuePop(&(obj->queue1)); } ret = QueueFront(&(obj->queue1)); //记录要弹出的数据 QueuePop(&(obj->queue1)); //弹出数据 } else { while(obj->queue2.head != obj->queue2.tail) //转移数据 { QueuePush(&(obj->queue1),(obj->queue2).head->data); QueuePop(&(obj->queue2)); } ret = QueueFront(&(obj->queue2)); //记录要弹出的数据 QueuePop(&(obj->queue2)); //弹出数据 } return ret; //返回被弹出的数据 } //栈顶的数据其实就是非空队列尾的数据 int myStackTop(MyStack* obj) { if(!QueueEmpty(&(obj->queue1))) { return obj->queue1.tail->data; } else { return obj->queue2.tail->data; } } //返回栈顶的数据 bool myStackEmpty(MyStack* obj) { return QueueEmpty(&(obj->queue1)) && QueueEmpty(&(obj->queue2)); } //销毁栈 void myStackFree(MyStack* obj) { QueueDestroy(&(obj->queue1)); QueueDestroy(&(obj->queue2)); free(obj); obj = NULL; }
5. 2 つのスタックを使用してキューを実装する ( 232. スタックを使用してキューを実装する - Leetcode )
アイデア図:
ソリューション コード:
typedef struct { ST PushStack; ST PopStack; } MyQueue; MyQueue* myQueueCreate() //队列初始化接口 { MyQueue* tem = (MyQueue *)malloc(sizeof(MyQueue)); if(NULL == tem) { perror("malloc failed:"); exit(-1); } StackInit(&(tem->PushStack)); StackInit(&(tem->PopStack)); return tem; } void myQueuePush(MyQueue* obj, int x) { StackPush(&(obj->PushStack),x); //数据入队 } int myQueuePop(MyQueue* obj) { assert(!StackEmpty(&(obj->PushStack)) || !StackEmpty(&(obj->PopStack))); //数据出队前保证队列不为空 if(StackEmpty(&(obj->PopStack))) //PopStack为空则要将PushStack数据转移进来 { while(!StackEmpty(&(obj->PushStack))) { StackPush(&(obj->PopStack),StackTop(&(obj->PushStack))); //将PushStack栈顶数据压入PopStack栈中 StackPop(&(obj->PushStack)); //完成数据转移后弹出PushStack的栈顶数据 } } int ret = StackTop(&(obj->PopStack)); //记录队头元素 StackPop(&(obj->PopStack)); //元素出队 return ret; //返回队头元素 } int myQueuePeek(MyQueue* obj) { assert(!StackEmpty(&(obj->PushStack)) || !StackEmpty(&(obj->PopStack))); //保证队列不为空 if(StackEmpty(&(obj->PopStack))) //PopStack为空则要将PushStack数据转移进来 { while(!StackEmpty(&(obj->PushStack))) { StackPush(&(obj->PopStack),StackTop(&(obj->PushStack))); //将PushStack栈顶数据压入PopStack栈中 StackPop(&(obj->PushStack)); //完成数据转移后弹出PushStack的栈顶数据 } } return StackTop(&(obj->PopStack)); //返回PopStack栈顶元素作为队列队头元素 } bool myQueueEmpty(MyQueue* obj) { return StackEmpty(&(obj->PopStack)) && StackEmpty(&(obj->PushStack)); //判断队列是否为空 } void myQueueFree(MyQueue* obj) { StackDestroy(&(obj->PopStack)); StackDestroy(&(obj->PushStack)); free(obj); obj = NULL; }
- 問題の解決策では、この問題の最初のセクションで実装されたスタックを使用します