一、棧
1.棧的概念及結構
棧:一種特殊的線性表,其隻允許在固定的一端進行插入和删除元素操作。進行資料插入和删除操作的一端稱為棧頂,另一端稱為棧底。棧中的資料元素遵守後進先出LIFO(Last In First Out)的原則。
壓棧:棧的插入操作叫做進棧/壓棧/入棧,入資料在棧頂。
出棧:棧的删除操作叫做出棧。出資料也在棧頂。
棧的實作一般可以使用數組或者連結清單實作,相對而言數組的結構實作更優一些。因為數組在尾上插入資料的代價比較小。
數組棧
鍊式棧
對于第一種頭作為棧頂,插入删除資料為頭插和頭删,這樣很容易找到頭進行操作,如果用頭做棧頂,頭插頭删,就可以設計成單連結清單。
第二種尾作為棧頂,插入删除資料為尾插和尾删,這種結構設計成雙向連結清單比較好,否則删除資料效率低。
其次數組棧緩存使用率高,因為數組是一段連續的空間,記憶體通路數組的第一個數的時候,後面的數字已經預緩存在cache中,而鍊式棧的空間不是連續的,是用連結清單将不連續的空間連接配接起來,在找到連結清單的第一個數字後,如果找不到第二個空間,假設不命中,一次加載20byte到緩存(具體加載多大取決硬體體系)。鍊式棧的緩存命中率低。
對于鍊式棧唯一的好處就是可以随時開辟空間,不浪費空間,數組棧隻能在空間不夠的時候開辟一定的空間,存在空間的浪費,但是鍊式棧僅限于這個好處。除非要在一個不能浪費空間的地方使用,比如嵌入式,我們用鍊式棧。
是以對于棧,兩種都可以,非要選一種,我們最好用數組棧。2.棧的實作
對于各個函數的聲明和頭檔案的包括,我們寫在Stack.h中#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//數組指針
int top;//棧頂
int capacity;//最大容量
}ST;
//初始化數組
void StackInit(ST ps);
//銷毀數組
void StackDestroy(ST ps);
void StackPush(ST ps, STDataType x);
void STackPop(ST ps);
//找到棧頂元素
STDataType StackTop(ST ps);
//棧的大小
int StackSize(ST ps);
//判斷棧是否為空
bool StackEmpty(ST* ps);//引用頭檔案
#### 棧的初始化
将指針置空,棧中最大個數為0,棧的下标為0或-1
```c
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;//指向棧頂的下一個,指向棧頂後,+1指向下一個
//ps->top=-1;//指向棧頂,先+1再指向棧頂
ps->capacity = 0;
}
插入資料
棧的插入資料隻有一種,在棧頂插入,将資料插入棧頂,top下标也要增加一個,當插入到棧滿的時候,我們需要擴容。
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
銷毀資料
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);//釋放置空
ps->a = NULL;
ps->capacity = ps->top = 0;//置為0
}
删除資料
删除非常簡單,隻需要将top減減,但在test.c中測試,删除資料最後top會小于0,變成-1,-2...,是以我們直接斷言一下,在top大于0時,才能删除
void STackPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
取棧頂資料
因為前面初始化棧頂top為0,最後指向棧頂元素的下一個,是以傳回top-1.當棧為空時,top-1為野指針,傳回随機值,是以斷言一下top>0
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return(ps->a[ps->top-1]);
}
棧的大小
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
判斷棧是否為空
bool StackEmpty(ST* ps)
{
/*if (ps->top == 0)//檢視下标top是否為0
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//如果為0傳回ture,其他情況傳回false
}
棧的實作時非常簡單的
那麼如何實作棧的周遊列印呢
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
每次列印棧頂,并删除棧頂即可,最後棧為空跳出循環,列印結束。
3.OJ練習題--有效的括号
我們來做一道OJ題小試牛刀
給定一個隻包括 '(',')','{','}','[',']' 的字元串 s ,判斷字元串是否有效。
有效字元串需滿足:
左括号必須用相同類型的右括号閉合。
左括号必須以正确的順序閉合。
來源:力扣(LeetCode)
連結:https://leetcode-cn.com/problems/valid-parentheses
這道題我們運用上面寫好的接口,因為用的是C語言來寫,不像C++有STL庫刷起來那麼友善,需要自己實作接口,這是C刷題的一個弊端。
思路:當遇到左括号入棧,當遇到右括号左括号出棧跟右括号比對示例5中,遇到左括号入棧,當遇到有括号的時候棧頂的左括号出棧與之比對,比對上了,指針向下走,這時棧頂的第一個左括号已經被删除,當遇到第二個右括号時,棧裡的左括号與之比對,比對上了,指針向下走,當指針指向空時,傳回真,其他情況傳回false.
傳回資料的時候一定要記得銷毀棧,避免記憶體洩漏,因為這個棧是我們realloc出來的。
當遇到隻有一個左括号的時候,左括号入棧,但是指針在向下走的時候,直接跳出循環,得到傳回值,無法得到true結果,這時我們要判斷是否是空,如果是空傳回true,否則為false。
當隻有一個右括号的時候,棧中沒有元素,棧為空,取不到棧頂,我們在上面斷言了棧不能為空,是以要判斷一下棧是否為空,然後傳回false,這個詳情看下面代碼。
typedef char STDataType;
typedef struct Stack
{
STDataType* a;//數組指針
int top;//棧頂
int capacity;//最大容量
}ST;
//初始化數組
void StackInit(ST* ps);
//銷毀數組
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
//找到棧頂元素
STDataType StackTop(ST* ps);
//棧的大小
int StackSize(ST* ps);
//判斷棧是否為空
bool StackEmpty(ST* ps);//引用頭檔案
void StackInit(ST* ps)
{
assert(ps);
ps->a=NULL;
ps->top=ps->capacity=0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a=NULL;
ps->top=ps->capacity=0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if(ps->top==ps->capacity)
{
int newcapacity=ps->capacity==0?4:ps->capacity*2;
STDataType* tmp=(STDataType*)realloc(ps->a,sizeof(STDataType)*newcapacity);
if(tmp==NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a=tmp;
ps->capacity=newcapacity;
}
ps->a[ps->top]=x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(ps->top>0);
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top>0);
return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
return ps->top==0;
}
bool isValid(char * s){
ST st;
StackInit(&st);
while(*s)
{
if(*s=='(' || *s=='[' || *s=='{')
{
//入棧
StackPush(&st,*s);
++s;
}
else
{
//遇到右括号,但是棧裡沒有資料,沒有左括号了,傳回false
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
STDataType top=StackTop(&st);
StackPop(&st);
if((*s==')' && top !='(')||
(*s==']' && top !='[')||
(*s=='}' && top !='{'))
{
StackDestroy(&st);
return false;
}
else
{
++s;
}
}
}
//如果棧不是空,棧中還有左括号未出,沒有比對,傳回的時false
// bool ret=StackEmpty(&st);
// return ret;
if(StackEmpty(&st))
{
return true;
}
else
{
return false;
}
StackDestroy(&st);
}
二、隊列
1.隊列的概念及結構
隊列:隻允許在一端進行插入資料操作,在另一端進行删除資料操作的特殊線性表,隊列具有先進先出FIFO(First In First Out)
入隊列:進行插入操作的一端稱為隊尾
出隊列:進行删除操作的一端稱為隊頭
2.隊列的實作
隊列裡面我們定義一個指針
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
因為隊列是先進先出,存放資料為尾插,删除資料為頭删,是以為了友善,我們定義兩個指針,一個指向頭一個指向尾,然後放在一個結構體中,這樣我們測試的時候可以不用二級指針,直接通過結構體來改變裡面的資料。如果不定義這樣的一個結構體,通過二級指針傳入指針位址,然後通過一級指針找到資料來改變資料。
typedef struct QueueList
{
QueueNode* head;
QueueNode* tail;
}QList;
棧的初始化和銷毀
void QueueInit(QList* ps)
{
assert(ps);
ps->head = NULL;
ps->tail = NULL;
}
void QueueDestroy(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;//定義指針周遊連結清單
QueueNode* next = cur->next;//存放目前指針的下一個
while (cur)
{
free(cur);
cur = next;
}
ps->head = ps->tail = NULL;
}
判斷隊列是否為空
bool QueueEmpty(QList* ps)
{
/*if (ps->head == NULL)
{
return true;
}
else
{
return false;
}*/
return ps->head == NULL;
}
尾插和頭删
隻實作尾插和頭删是由于隊列的性質寫出的
**尾插**
void QueuePush(QList* ps, QDataType x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (ps->tail == NULL)
{
ps->tail = ps->head = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = newnode;
}
}
頭删
在頭删的同時,頭删到NULL,tail指針并沒有被釋放,它還指向最後一個節點,head釋放掉之後,tail成為野指針,傳回随機值。是以head釋放掉變為NULL的同時,也将tail置為空
void QueuePop(QList* ps)
{
assert(ps);//判斷結構體不為空
assert(!QueueEmpty(ps));//判斷隊列不為空
QueueNode* next = ps->head->next;
free(ps->head);
ps->head = next;
if (ps->head == NULL)
{
ps->tail = NULL;
}
}
傳回隊尾和隊頭資料
QDataType QueueFront(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->head->data;
}
QDataType QueueBack(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->tail->data;
}
計算隊列大小
int QueueSize(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
int count = 0;
while (cur)
{
++count;
cur = cur->next;
}
return count;
}
測試一下
test.c
void TestQueue1()
{
QList q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
QDataType front = QueueFront(&q);
printf("%d ", front);
QueuePop(&q);
}
printf("\n");
}
int main()
{
TestQueue1();
return 0;
}
隊列OJ題
讓我們來做幾道OJ題
1.用隊列實作棧
請你僅使用兩個隊列實作一個後入先出(LIFO)的棧,并支援普通棧的全部四種操作(push、top、pop 和 empty)。
實作 MyStack 類:
void push(int x) 将元素 x 壓入棧頂。
int pop() 移除并傳回棧頂元素。
int top() 傳回棧頂元素。
boolean empty() 如果棧是空的,傳回 true ;否則,傳回false 。
OJ連結
思路:這道題用兩個隊列實作棧,将資料放進一個隊列中,再将資料删除導入到另一個隊列,直到删除到最後一個數字,直接将數字删除,實作後進先出
這道題用C語言,是以需要自己實作一些隊列接口。前面我們寫好直接用
這道題的函數接口中,将兩個隊列放在一個結構體中,myStackCreate中調用接口初始化,傳回結構體指針,與我們上面寫的有所差別,我們上面是直接在test.c中建立一個結構體,把結構體的指針傳過去。
typedef struct {
} MyStack;
MyStack* myStackCreate() {
}
![圖檔.png](https://s2.51cto.com/images/20211105/1636111112941209.png?x-oss-process=image/watermark,size_14,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=)
兩個地方要區分開來。
但是在我們建立結構體傳回指針時,我們不能這樣建立
```c
MyStack* myStackCreate() {
Mystack st;
return &st;
}
這是我們在棧裡面定義的局部變量,除了作用域就會被銷毀,我可以定義全局變量,但是會出問題,是以我們用malloc在堆裡面定義,這樣出了作用域也不會被銷毀。
typedef struct {
QList q1;
QList q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
在釋放函數中
```c
void myStackFree(MyStack* obj) {
}
因為我們的結構是這樣的
需要先釋放兩個隊列,再釋放隊列的結構體
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
實作代碼
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct QueueList
{
QueueNode* head;
QueueNode* tail;
}QList;
void QueueInit(QList* ps);
void QueueDestroy(QList* ps);
void QueuePush(QList* ps, QDataType x);
void QueuePop(QList* ps);
QDataType QueueFront(QList* ps);
QDataType QueueBack(QList* ps);
int QueueSize(QList* ps);
bool QueueEmpty(QList* ps);
void QueueInit(QList* ps)
{
assert(ps);
ps->head = NULL;
ps->tail = NULL;
}
void QueueDestroy(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
ps->head = ps->tail = NULL;
}
void QueuePush(QList* ps, QDataType x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
newnode->data = x;
newnode->next = NULL;
if (ps->tail == NULL)
{
ps->tail = ps->head = newnode;
}
else
{
ps->tail->next = newnode;
ps->tail = newnode;
}
}
void QueuePop(QList* ps)
{
assert(ps);//判斷結構體不為空
assert(!QueueEmpty(ps));//判斷隊列不為空
QueueNode* next = ps->head->next;
free(ps->head);
ps->head = next;
if (ps->head == NULL)
{
ps->tail = NULL;
}
}
QDataType QueueFront(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->head->data;
}
QDataType QueueBack(QList* ps)
{
assert(ps);
assert(!QueueEmpty(ps));
return ps->tail->data;
}
int QueueSize(QList* ps)
{
assert(ps);
QueueNode* cur = ps->head;
int count = 0;
while (cur)
{
++count;
cur = cur->next;
}
return count;
}
bool QueueEmpty(QList* ps)
{
assert(ps);
return ps->head == NULL;
}
typedef struct {
QList q1;
QList q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
//如果隊列不為空,則放資料
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
//當不為空的隊列資料個數大于1,将資料導入空隊列,并删除
int myStackPop(MyStack* obj) {
QList* emptyQ=&obj->q1;
QList* noneemptyQ=&obj->q2;
if(!QueueEmpty(&obj->q1))
{
emptyQ=&obj->q2;
noneemptyQ=&obj->q1;
}
while(QueueSize(noneemptyQ)>1)
{
QueuePush(emptyQ,QueueFront(noneemptyQ));//放入隊列頭的資料
QueuePop(noneemptyQ);
}
QDataType top=QueueFront(noneemptyQ);
QueuePop(noneemptyQ);
return top;//帶傳回值,傳回不為空隊列最後一個資料
}
//誰不是空的,傳回誰的隊尾
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
//兩個都是空才是空
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
3.用棧實作隊列
請你僅使用兩個棧實作先入先出隊列。隊列應當支援一般隊列支援的所有操作(push、pop、peek、empty):
實作 MyQueue 類:
void push(int x) 将元素 x 推到隊列的末尾
int pop() 從隊列的開頭移除并傳回元素
int peek() 傳回隊列開頭的元素
boolean empty() 如果隊列為空,傳回 true ;否則,傳回 false
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return(ps->a[ps->top-1]);
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)
{
return ps->top == 0;
}
typedef struct {
ST pushST;
ST popST;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q=(MyQueue*)malloc(sizeof(MyQueue));
StackInit(&q->pushST);
StackInit(&q->popST);
return q;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST,x);
}
//當popST不為空,如果pushST不為空,将資料導入popST再進行删除
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
STDataType top=StackTop(&obj->popST);
StackPop(&obj->popST);
return top;
}
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popST))
{
while(!StackEmpty(&obj->pushST))
{
StackPush(&obj->popST,StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
return StackTop(&obj->popST);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->popST)&&StackEmpty(&obj->pushST);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushST);
StackDestroy(&obj->popST);
free(obj);
}
3.設計循環隊列
typedef struct {
int* a;
int front;
int tail;
int k;
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
cq->a=(int*)malloc(sizeof(int)*(k+1));
cq->tail=cq->front=0;
cq->k=k;
return cq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
//滿了不能添加,傳回flase
if(myCircularQueueIsFull(obj))
return false;
obj->a[obj->tail]=value;
++obj->tail;
obj->tail%=(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
//空了不能删除,傳回false
if(myCircularQueueIsEmpty(obj))
return false;
++obj->front;
obj->front%=(obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
return -1;
if(obj->tail%(obj->k+1)==0)
{
return obj->a[obj->k];
}
else
{
return obj->a[obj->tail-1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->k+1)==obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}