后台开发学习笔记(四、栈、队列)

栈和队列比较简单,就简单的描述描述,上一节链表讲的有点多了,这次简单讲解一下栈和对列。

4.1 栈

4.1.1 栈的结构

栈的结构,在其他博客也说的很多了,这里就简单说一说,栈是一种线性结构,栈的元素只能先进后出(Frist In Last Out简称 FILO),最早进入的元素存储到栈底,最后进入的元素叫栈顶,栈的操作只能在栈顶操作。
栈的结构可以用数组或者链表来实现
下面只要用数组来实现的:

typedef struct Stack
{
	int 		top;				//栈顶指针
	int			size;				//栈的大小
	Elemtype 	*data;				//栈的元素
}_Stack;

4.1.2 栈的操作

  1. 入栈
    入栈操作(push)就是把一个新的元素添加到栈顶的位置,然后这个新元素就是栈顶了。
/**
    * @brief  入栈,内部支持扩容
    * @param  
    * @retval 
    */ 
    int stack_push(struct Stack *s, Elemtype data)
    {
		if(s == NULL)
			return -1;

		if(s->top >= s->size)   //这个是要等于,因为有0
		{
			int new_len = (s->size>>1) + s->size;
			//printf("len = %d %d %d\n", s->size>>1, s->size, new_len);
			s->data = realloc(s->data, new_len*sizeof(Elemtype));   	//扩容1.5倍,realloc可以复制之前数据到新的内容区
			if(s == NULL)
				return -2;

			s->size = new_len;
		}

		s->data[s->top] = data;
		s->top++;
		
        return 0;
    }
  1. 出栈
    出栈操作(pop)就是把元素从栈里取出,不过只能从栈顶的元素取出,出栈元素的前一个元素将会称为新的栈顶
/**
	* @brief  出栈,
	* @param  
	* @retval 
	*/ 
	int stack_pop(struct Stack *s, Elemtype *data)
	{
		if(s == NULL || data == NULL)
			return -1;

		//可以做减少容量的操作

		if(s->top == 0)
			return -2;

		*data = s->data[--s->top];    //这个是指向栈顶的,需要先减
		return 0;
	}

4.1.3 附加:栈的面试题

1.有效括号
leetcode 20题:
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。

这道题是在leetcode做的第一道题,不过感觉c语言刷leetcode有点累,啥都需要封装,但是目前涉及到这些基本的数据结构,还是用c写,在leetcode可以用c++,直接调用c++封装好的栈。之前我们也封装好了一个栈,可以现在用来试试。

这道题目就是利用了栈的功能,先进后出,我们把每进来的属于左括号的都进栈,当来了右括号的,就把获取栈顶元素,判断一下是否和刚进来的右括号匹配,如果不匹配,就返回失败,如果匹配的话,出栈,然后继续下一轮。

代码:

/**
	* @brief  有效括号
	* @param  s: 字符串
	* @retval 
	*/ 
	int stack_ValidParentheses(char *s)
	{
		//1.创建一个栈
		struct Stack st;
		stack_creat(&st, 10);

		//2.分解字符串,c语言只能用指针遍历
		char *c = s;
		while(*c != '\0')
		{
			//3.判断是否是左括号,是的话就入栈,否则取出栈顶元素做判断
			if(*c == '(' || *c == '[' || *c == '{')
			{
				stack_push(&st, *c);
			}
			else
			{
				int cc;
				//先做检测,如果栈为空,返回失败
				if(stack_len(&st) == 0)
					return -1;
				stack_pop(&st, &cc);
				
				//判断是否跟输入的一致
				if(*c == ')' && cc != '(' ||
					*c == ']' && cc != '[' ||
					*c == '}' && cc != '{' )
				{
					return -1;
				}
			}
			c++;
		}

		//4.如果栈为空,就说明匹配成功,反则失败
		if(stack_len(&st) == 0)
			return 0;
		return -1;
	}

不同语言的实现,只是语法有区别,具体思想都差不多的。

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

push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。

当初第一次听这道题还是在面试的时候,当初很懵逼,想不出来,结果很明显面试被刷了,再次遇到是在程序员小灰的公众号上看到答案,感觉设计的真好,算法还是要好好刷刷。

不好理解就画图,画了图就好理解了:

  • 首先申请两个栈,一个是存储正常存储元素的栈,另一个是备胎栈,存储最小值的
    在这里插入图片描述
  • 入栈
    这个最小栈的入栈设计和我们之前的入栈有点区别,首先第一个元素4入栈,它会入两个栈,因为最小栈现在为空,所以第一个值就是最小值。
    在这里插入图片描述
    仅接着第二个元素9入栈,在入栈之前要判断一下栈B的栈顶元素,如果9比栈顶元素小,入栈B,现在结果9>4,所以不入栈B。
    在这里插入图片描述
    第三个元素是3,再进行比较,这时候3<4所以入栈B。
    在这里插入图片描述
  • 出栈
    竟然入栈都要考虑栈B了,出栈的时候也需要考虑到了。在出栈的时候会判断一下栈B的栈顶元素,如果值相等,栈B也出栈,这样栈B的栈顶元素就是目前栈的最小值,符合获取栈的最小值的复杂度为O(1)
    在这里插入图片描述
  • 获取最小值
    直接获取栈B的栈顶元素即可。

思想已经描述清楚了,那代码就不难写了:

/**
	* @brief  最小栈创建
	* @param  
	* @retval 
	*/ 
	static struct Stack gs_stA;
	static struct Stack gs_stB;
	int stackMin_init(void)
	{
		//1.创建一个栈A
		stack_creat(&gs_stA, 10);
		stack_creat(&gs_stB, 10);
		
		return 0;
	}

	int stackMin_push(Elemtype data)
	{
		//1.入栈
		Elemtype temp;
		
		//判断栈B是否为空,为空直接进栈
		if(stack_len(&gs_stB) == 0)
		{
			stack_push(&gs_stB, data);
		}
		else
		{
			//跟栈顶元素判断,如果小于栈顶元素进栈
			stack_top(&gs_stB, &temp);
			if(data <= temp)
			{
				stack_push(&gs_stB, data);
			}
		}

		//栈A都需要进栈
		stack_push(&gs_stA, data);
		
		return 0;
	}

	Elemtype stackMin_pop(void)
	{
		//1.出栈
		Elemtype tempA, tempB;

		//栈A先出栈
		stack_pop(&gs_stA, &tempA);


		//看看栈B的栈顶元素
		if(stack_len(&gs_stB) != 0)
		{
			stack_top(&gs_stB, &tempB);
		
	        if(tempA == tempB)
				stack_pop(&gs_stB, &tempB);	
		}
		
		return 0;
	}

	Elemtype stackMin_top(void)
	{
		//1.出栈
		Elemtype tempA;

		//栈A先出栈
		stack_top(&gs_stA, &tempA);
		
		return tempA;
	}

	Elemtype stackMin_getMin(void)
	{
		//最小值
		Elemtype temp;
		
		//判断栈B是否为空,不为空就获取最小值
		if(stack_len(&gs_stB) != 0)
		{
			stack_top(&gs_stB, &temp);
			return temp;
		}
		
		return -1;
	}

4.2 队列

4.2.1 队列的结构

队列(queue)是一种线性数据结构,不同于栈先入后出,队列中的元素只能先入先出(first IN First Out,简称FIFO)。队列的出口端叫做队头,队列的入口端叫队尾。入队只能从队尾入,出队只能从队头出。
队列的实现可以用数组也可以用链表。我这里使用数组来实现

typedef struct Queue
{
	int			front;				//对头指针
	int 		rear;				//队尾指针
	int 		size;				//队列的大小
	Elemtype	*data;				//队列的元素
}_Queue;

4.2.2 循环队列

为什么直接说循环队列,是我们用的基本都是循环队列,基本的队列没什么用,所以直接使用循环队列。
循环队列就是利用已经出队元素留下的空间,让队尾的指针指回到数组的首位,这样这个对列就循环起来了。
判断队列满的条件:(队尾下标+1)%数组长度 = 队头下标。

4.2.3 循环队列的操作

1.入队
入队(enqueue)就是把新元素放入到队列中,只允许在队尾位置放入元素,新元素的下一个位置会成为新的队尾。新添加了扩容操作。

/**
	* @brief  入队
	* @param  
	* @retval 
	*/ 
	//旧代码,保留做对比
	int queue_enter(struct Queue *q, Elemtype data)
	{
		assert(q);

		//判断队列是否满
		if((q->rear+1)%q->size == q->front)
			return -1;

		q->data[q->rear] = data;
		q->rear = (q->rear+1)%q->size;
		
		return 0;
	}

	//新添加了扩容操作
    int queue_enter(struct Queue *q, Elemtype data)
	{
		assert(q);

		//判断队列是否满
		if((q->rear+1)%q->size == q->front)
		{
			//申请新的内存扩容
			int new_len = (q->size>>1)+q->size;
			q->data = realloc(q->data, sizeof(Elemtype)*new_len);
			assert(q->data);
			//如果front<rear就没有问题,扩容后的数据也是直接添加到后面
			//如果front>rear,在旧的数组空间,就被分为两截,所以要把0到rear的一截复制到q->size后面,来完成扩容,最后修改rear指针
			if(q->rear < q->front)
			{
				int i;
				int old_len = q->size;
				for(i=0; i<q->rear; i++)
				{
					q->data[old_len++] = q->data[i]; 
				}
				q->rear = old_len;
			}

			q->size = new_len;
		}
			
		q->data[q->rear] = data;
		q->rear = (q->rear+1)%q->size;
		
		return 0;
	}

2.出队
出队操作(dequeue)就是把元素移出队列,只允许从队头移除,出队元素下一个元素是新的队头。

/**
	* @brief  出队
	* @param  
	* @retval 
	*/ 
	int queue_delete(struct Queue *q, Elemtype *data)
	{
		assert(q);
		assert(data);

		//判断队列是否为空
		if(q->rear == q->front)
			return -1;

		*data = q->data[q->front];
		q->front = (q->front+1)%q->size;
		
		return 0;
	}

3.遍历
循环队列遍历还是有点意思的,所以这里实现了一个

/**
		* @brief  遍历栈
		* @param  
		* @retval 
		*/ 
		int queue_traversal(struct Queue *q)
		{
			if(q == NULL)
				return -1;
	
			int i = 0;
			int head = (q->front)%q->size;
			int tail = (q->rear)%q->size;
			printf("traversal %d %d\n", head, tail);
			for(i=head; i != tail; i=(i+1)%q->size)   //这样遍历
			{		
				printf("%d ", q->data[i]);
			}
			printf("\n");
			
			return 0;
		}

4.2.4 附加:对列的面试题

暂时没有,以后可以添加

4.3 哈希表

暂时没有,以后添加

队列暂时没有面试题,不过leetcode上面队列的题也不少,可以去刷刷,我这里先不刷了,之后会去刷的,现在连哈希表都没实现,不过哈希表实现重点还是哈希函数,以后仔细研究过再做笔记把。

发布了32 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/C1033177205/article/details/103329713
今日推荐