数据结构与算法 - 线性结构: 堆栈与队列

200 阅读7分钟

数据结构与算法 - 线性结构: 堆栈

栈: 是限定仅在表尾进行插入和删除操作的线性表. (后进先出的线性表)

栈顶(top): 允许插入和删除的一端称为栈顶.

栈底(bottom): 不允许插入和删除的一端称为栈底.

空栈: 不包含任何数据元素的栈.

栈的插入操作, 叫做 进栈, 也称为 压栈, 入栈

栈的删除操作, 叫做 出栈, 也称为 弹栈

栈的定义和操作

定义

#define MAXSIZE 10  
  
typedef int SElemType;  
  
typedef struct {  
    SElemType data[MAXSIZE];  
    /*  
    * 用于栈顶指针  
    */  
    int top;  
} SqStack;

操作

/**  
* 初始化栈  
* @param sqStack  
*/  
void initStack(SqStack *sqStack) {  
    sqStack->top = -1;  
}  
  
/**  
* 判断栈是否为空栈  
* @param stack  
* @return  
*/  
bool IsEmpty(SqStack *stack) {  
    return stack->top == -1;  
}  
  
/**  
* 判断是否满栈  
* @param stack  
* @return  
*/  
bool IsFull(SqStack *stack) {  
    return stack->top == MAXSIZE - 1;  
}  
  
/**  
* 入栈  
* @param stack  
* @param item  
* @return  
*/  
bool Push(SqStack *stack, SElemType item) {  
    if (IsFull(stack)) {  
        printf("Stack is full. Cannot push element.\n");  
    return false;  
    }  
    stack->data[++stack->top] = item;  
    return true;  
}  
  
/**  
* 出栈  
* @param stack  
* @param item  
* @return  
*/  
bool Pop(SqStack *stack, SElemType *item) {  
    if (IsEmpty(stack)) {  
        printf("Stack is empty. Cannot pop element.\n");  
        return false;  
    }  
    *item = stack->data[stack->top--];  
    return true;  
}  
  
/**  
* 获取栈顶元素  
* @param stack  
* @param item  
* @return  
*/  
bool GetTop(SqStack *stack, SElemType *item) {  
    if (IsEmpty(stack)) {  
        printf("Stack is empty. No top element.\n");  
        return false;  
    }  
    *item = stack->data[stack->top];  
    return true;  
}  
  
/**  
* 清空栈  
* @param stack  
*/  
void ClearStack(SqStack *stack) {  
    stack->top = -1;  
}  
  
/**  
* 打印栈中元素  
* @param stack  
*/  
void PrintStack(SqStack *stack) {  
    if (IsEmpty(stack)) {  
        printf("Stack is empty.\n");  
        return;  
    }  
    printf("Stack elements: ");  
    for (int i = stack->top; i >= 0; i--) {  
        printf("%d ", stack->data[i]);  
    }  
    printf("\n");  
}

栈的相关例题

最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

示例

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

算法实现

typedef struct stack{
    int val;
    struct stack *next;
    struct stack *min;
} MinStack;

/** initialize your data structure here. */

MinStack* minStackCreate() {
    MinStack *myStack=(MinStack*)malloc(sizeof(MinStack));
    myStack->val=0;
    myStack->next=NULL;
    myStack->min=NULL;
    return myStack;
}

void minStackPush(MinStack* obj, int val) {
    MinStack *node=(MinStack*)malloc(sizeof(MinStack));
    node->val=val;
    if(obj->val==0){
        node->next=NULL;
        node->min=node;
        obj->next=node;
        
    }else{
        node->min= val<obj->next->min->val ? node : obj->next->min;
        node->next=obj->next;
        obj->next=node;
    }
    obj->val++;
}

void minStackPop(MinStack* obj) {
    MinStack *p=obj->next;
    obj->next=p->next;
    free(p);
    obj->val--;
}

int minStackTop(MinStack* obj) {
    return obj->next->val;
}

int minStackGetMin(MinStack* obj) {
    return obj->next->min->val;
}

void minStackFree(MinStack* obj) {
    if(obj->val==0)
        return;
    else if(obj->next->next==NULL){
        free(obj->next);   
        obj->next=NULL;
        return;
    }
    MinStack *p1, *p2;
    p1=obj->next;
    p2=p1->next;
    while(p2){
        free(p1);
        p1=p2;
        p2=p2->next;
    }
    free(p1);
    obj->val=0;
}

/**
 * Your MinStack struct will be instantiated and called as such:
 * MinStack* obj = minStackCreate();
 * minStackPush(obj, val);
 
 * minStackPop(obj);
 
 * int param_3 = minStackTop(obj);
 
 * int param_4 = minStackGetMin(obj);
 
 * minStackFree(obj);
*/

有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1

输入: s = "()"
输出: true

示例 2

输入: s = "()[]{}"
输出: true

示例 3

输入: s = "(]"
输出: false

算法实现

bool isValid(char * s){
    char leftBrackets(char c);
    int length=0;
    while(s[length++]!='\0');
    length--;
    char stack[length];
    int top=-1;
    for(int i=0; i<length; i++){
        if(s[i]=='(' || s[i]=='[' || s[i]=='{')
            stack[++top]=s[i];
        else{
            if(top!=-1 && stack[top]==leftBrackets(s[i]))
                top--;
            else
                return false;
        }
    }
    if(top>-1)
        return false;
    return true;
}
char leftBrackets(char c){
    if(c==')')
        return '(';
    else if(c==']')
        return '[';
    else
        return '{';
}

数据结构与算法 - 线性结构: 队列

image.png

在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。

如上图所示,队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。

队列的定义与操作

结构定义

#define MAXSIZE 10  
typedef int QElemType;  
typedef struct {  
    QElemType data[MAXSIZE];  
    int front; // 队头指针  
    int rear; // 队尾指针  
} SqQueue;

操作

/**  
* 初始化队列  
* @param sqQueue  
*/  
void initQueue(SqQueue *sqQueue) {  
    sqQueue->front = 0;  
    sqQueue->rear = 0;  
}  
  
/**  
* 判断队列是否为空  
* @param sqQueue  
* @return  
*/  
bool IsEmpty(SqQueue *sqQueue) {  
    return sqQueue->front == sqQueue->rear;  
}  
  
/**  
* 判断队列是否已满  
* @param sqQueue  
* @return  
*/  
bool IsFull(SqQueue *sqQueue) {  
    return (sqQueue->rear + 1) % MAXSIZE == sqQueue->front;  
}  
  
/**  
* 入队  
* @param queue  
* @param item  
* @return  
*/  
bool EnQueue(SqQueue *queue, QElemType item) {  
    if (IsFull(queue)) {  
        printf("Queue is full. Cannot enqueue element.\n");  
        return false;  
    }  
    queue->data[queue->rear] = item;  
    queue->rear = (queue->rear + 1) % MAXSIZE;  
    return true;  
}  
  
/**  
* 出队  
* @param queue  
* @param item  
* @return  
*/  
bool DeQueue(SqQueue *queue, QElemType *item) {  
    if (IsEmpty(queue)) {  
        printf("Queue is empty. Cannot dequeue element.\n");  
        return false;  
    }  
    *item = queue->data[queue->front];  
    queue->front = (queue->front + 1) % MAXSIZE;  
    return true;  
}  
  
/**  
* 获取队头元素  
* @param queue  
* @param item  
* @return  
*/  
bool GetFront(SqQueue *queue, QElemType *item) {  
    if (IsEmpty(queue)) {  
        printf("Queue is empty. No front element.\n");  
        return false;  
    }  
  
    *item = queue->data[queue->front];  
    return true;  
}  
  
/**  
* 清空队列  
* @param queue  
*/  
void ClearQueue(SqQueue *queue) {  
    queue->front = 0;  
    queue->rear = 0;  广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。
}   
/**  
* 打印队列中元素  
* @param queue  
*/  
void PrintQueue(SqQueue *queue) {  
    if (IsEmpty(queue)) {  
        printf("Queue is empty.\n");  
        return;  
    }  
    printf("Queue elements: ");  
    int i = queue->front;  
    while (i != queue->rear) {  
        printf("%d ", queue->data[i]);  
        i = (i + 1) % MAXSIZE;  
    }  
    printf("\n");  
}

队列和广度优先搜索

广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径。

例题

岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

算法实现

int numIslands(char** grid, int gridSize, int* gridColSize) {
    // 边界条件判断
    if (grid == NULL || gridSize == 0)
        return 0;
    // 统计岛屿的个数
    int count = 0;
    // 两个循环遍历每一个格子
    for (int i = 0; i < gridSize; i++) {
        for (int j = 0; j < *gridColSize; j++) {
            // 只有当前格子是 '1' 才开始计算
            if (grid[i][j] == '1') {
                // 如果当前格子是 '1',岛屿的数量加1
                count++;
                // 然后通过dfs把当前格子的上下左右4个位置为 '1' 的都要置为 '0',
                // 因为它们是连着一起的算一个岛屿
                dfs(grid, i, j, gridSize, gridColSize);
            }
        }
    }

    // 最后返回岛屿的数量
    return count;
}

void dfs(char** grid, int i, int j, int gridSize, int* gridColSize) {
    // 边界条件判断,不能越界
    if (i < 0 || i >= gridSize || j < 0 || j >= *gridColSize || grid[i][j] == '0')
        return;

    // 把当前格子置为 '0',然后再从它的上下左右 4 个方向继续遍历
    grid[i][j] = '0';
    dfs(grid, i - 1, j, gridSize, gridColSize);  // 上
    dfs(grid, i + 1, j, gridSize, gridColSize);  // 下
    dfs(grid, i, j + 1, gridSize, gridColSize);  // 左
    dfs(grid, i, j - 1, gridSize, gridColSize);  // 右
}

完全平方数

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入: n = 12
输出: 3 
解释: 12 = 4 + 4 + 4

示例 2:

输入: n = 13
输出: 2
解释: 13 = 4 + 9

算法实现


bool is_square(int n) {
    int sqrt_n = (int) sqrt(n);
    return sqrt_n * sqrt_n == n;
}

int numSquares(int n) {
    // 一,先判断由1个平方数组成的
    // 如果 n 是平方数,直接返回1即可,表示 n 由 1 个平方数组成
    if (is_square(n))
        return 1;

    // 如果 n 是 4 的倍数,就除以 4,因为 4 是 2 的平方,
    // 如果 n 可以由 m 个完全平方数组成,那么 4n 也可以由 m 个完全平方数组成
    while ((n & 3) == 0)
        n >>= 2;

    // 二,在判断由4个平方数组成的
    // 如果 n 是 4 的倍数,在上面代码的执行中就会一直除以 4,
    // 直到不是 4 的倍数为止,所以这里只需要判断 n=(8b+7) 即可
    if ((n & 7) == 7)
        return 4;

    int sqrt_n = (int) sqrt(n);

    // 三,接着判断由2个平方数组成的
    // 下面判断是否能由 2 个平方数组成
    for (int i = 1; i <= sqrt_n; i++) {
        if (is_square(n - i * i)) {
            return 2;
        }
    }

    // 四,剩下的只能由 3 个平方数组成了
    // 如果上面都不成立,根据拉格朗日四平方和定理,只能由 3 个平方数组成了
    return 3;
}