[C数据结构与算法]栈&队列

顺序栈

顺序栈与顺序表都使用一段连续的内存空间存储数据,但二者存储,删除数据时的顺序不同. 栈具有后进先出的特点, 虽然顺序栈上的数据同样可以用下标直接访问任意元素,但是这样做便使顺序表与顺序栈无异, 顺序栈的出栈,入栈操作应当永远对于栈顶,也就是存储在栈的地址最高处的有效值.

一个顺序栈应当包括一段连续内存与一个存储栈顶位置的变量或指针

int base[10],top= -1;	//base指向大小为10*4字节大小的连续内存,top用于存储当前栈的内存应用情况

当空栈时形如下图:

在这里插入图片描述

入栈一个元素,top++

在这里插入图片描述

  • 代码实现

    //Function for sequence stack
    int pushSS(int* base,int top,int elm){		
    	base[++top]= elm;	//Void stack: top(-1) to top(0)
    	
    	return top;
    }
    
    int popSS(int* base,int top){
    	//Check if a void stack
    	if(top==-1) return top;
    	
    	base[top--]= 0;	//Stack with only one element: top(0) to top(-1)
    	return top;
    }
    

    以上代码实现了顺序栈的入栈与出栈, 在pushSS()方法中作者没有对栈是否溢出进行判断,请读者自行根据需求实现自己的入栈操作.

  • 例题

    1. 进制装换

      题目叙述十分简单: 将任意进制 A A A(2-36进制)的 X X X转换成 B B B进制的 Y Y Y

      例如: 输入一个十六进制数 A 180 A180 A180 要求转换成一个四进制数 22012000 22012000 22012000

      • 思路: 由于输入存在大于十进制的进制,因此我们将输入的进制数以字符储存,并使用ASCII将对应字符转换成数字, 具体公式为

        1. 若字符 ′ 0 ′ ≤ c ≤ ′ 9 ′ '0'\leq c\leq '9' 0c9: 转换结果为 c − 48 c-48 c48
        2. 若字符 ′ A ′ ≤ c ≤ ′ Z ′ 'A'\leq c\leq 'Z' AcZ: 转换结果为 c − 55 c-55 c55

        将字符转换后, 我们将结构按权展开,将其转换成十进制,如题:
        A 180 = 10 × 1 6 3 + 1 × 1 6 2 + 8 × 1 6 1 + 0 × 1 6 0 = 41344 A180= 10\times 16^{3}+1\times 16^{2}+8\times 16^{1}+0\times 16^{0}=41344 A180=10×163+1×162+8×161+0×160=41344
        最后我们对转换的十进制数使用目标进制取余,将其转换成目标进制数
        41344 / 4 = 10336 ⋯ 0 10334 / 4 = 2584 ⋯ 0 2584 / 4 = 646 ⋯ 0 646 / 4 = 161 ⋯ 2 161 / 4 = 40 ⋯ 1 40 / 4 = 10 ⋯ 0 10 / 4 = 2 ⋯ 2 2 / 4 = 0 ⋯ 2 41344/4= 10336\cdots0\\ 10334/4= 2584\cdots 0\\ 2584/4= 646 \cdots0\\ 646/4= 161\cdots 2\\ 161/4= 40 \cdots 1\\ 40/4=10\cdots 0\\ 10/4=2\cdots 2\\ 2/4=0\cdots 2 41344/4=10336010334/4=258402584/4=6460646/4=1612161/4=40140/4=10010/4=222/4=02
        其结果等于余数的倒序输出即 R e v ( 00021022 ) = 22012000 Rev(00021022)=22012000 Rev(00021022)=22012000, 若读者尚未清楚如何在任意进制转换,这里有一篇文章可供参考: 十进制与任意进制的转换_十进制转任意进制_熊仙森的博客-CSDN博客

        #include <stdio.h>
        #include <string.h>
        #include <math.h>
        
        #define MAXSIZE 100
        
        int push(char* base,int top,int elm){
        	base[++top]= elm;
        	
        	return top;
        }
        
        void main(){
        	int A,B;
        	char X[MAXSIZE],Y[MAXSIZE];
        	
        	printf("Please input X A B:");
        	scanf("%s %d %d",&X,&A,&B);
        	
        	int decimal= 0;
        	//将X按权展开,转换为十进制
        	for(int idx=0;idx<strlen(X);idx++)
        		decimal+= (X[idx]>='0' && X[idx]<='9') ? (X[idx]-48)*pow(A,strlen(X)-idx-1): (X[idx]-55)*pow(A,strlen(X)-idx-1);
        		
        	int top=-1;
        	//用B取余得到十进制数
        	while(decimal!=0){		
        		top= (decimal%B <10) ? push(Y,top,decimal%B+48) : push(Y,top,decimal%B+55);
        		
        		decimal= decimal/B;
        	}
        	
        	//打印栈 
        	for(int idx= top;idx!=-1;idx--)
        		printf("%c",Y[idx]);
        }
        

链栈

链栈与链表一样,在非连续的内存中动态存储数据. 与顺序表不同的是, 链栈的存储是相反的, 每次入栈,出栈操作因该对应头指针指向的节点,而非链栈的最后一个节点. 形如下图:

在这里插入图片描述

因此,链栈一般情况下不包括头节点(以便直接对首元节点操作),头指针指向的是首元节点. 其节点定义如下:

typedef struct LinkedStackNode{
	struct LinkedStackNode* next;
	int elm;
}LSNode;
  • 代码实现

    //Function for linked stack
    LSNode* pushLS(LSNode* head,int pushElm){	
    	//Create a new node 
    	LSNode* insNode= (LSNode*)malloc(sizeof(LSNode));
    	if(!insNode) return NULL;	//Memory overflow
    	insNode->elm= pushElm;
    	
    	insNode->next= head;	//Connect insNode with first node
    	head= insNode;	//Let head point the insNode
    	
    	return head;
    }
    
    LSNode* popLS(LSNode* head){
    	//Check if a void stack
    	if(head==NULL) return NULL;
    	
    	LSNode* delNode= head;	//Save the delNode
    	
    	head= head->next;	//Skip the delNode
    	
    	delNode->next= NULL;	//Cut off delNode with stack list
    	
    	free(delNode);
    	return head;
    }
    

    以上代码实现了链栈的入栈,出栈操作, 由于链式存储结构不需要提前分配内存所以不需要对链栈是否溢出进行判断. 为了优化内存的使用, 实现出栈时注意养成销毁出栈节点内存的习惯.

  • 总结

    无论是顺序栈还是链栈,"栈"字代表了他们**"后进先出"的存储顺序**,而"顺序"和"链"代表了他们的存储方式即线性的数据结构. 只要读者记住一点即可,对于栈的处理永远是对于栈顶元素/节点的.

顺序队列

看这个数据结构的名字,读者就应该明白,顺序队列的数据应该存储在一段连续内存,这里不再多讲. 队列与栈不同的是队列是**“先进先出”,拥有队头,队尾, 因此需要两个 变量/指针 存储内存的使用情况**.

入队top++, 出队rear++, 数据段整体向高位地址移动

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 代码实现

    #define MAXSIZE 100
    
    int enQueueSQ(int* base,int rear,int elm){
    	base[rear++]= elm;
    	
    	return rear;
    }
    
    int deQueueSQ(int* base,int top,int rear){
    	if(top==rear) return top;	//Cheack if a void queue
    	
    	base[top++]= 0;
    	
    	return top;
    }
    
  • 例题

    初始甲乙方各有 n n n张牌,甲乙轮流出牌(甲先乙后),每次从牌堆顶摸牌出牌一张.

    将出的牌垂直排列在桌子上, 若出的牌的点数与桌上任何牌的点数相同,则出牌者获得这两张以及中间所有的牌.

    若一方没有手牌游戏结束, 有手牌的一方获胜.

    现输入 n n n 与双方牌的顺序(甲先乙后,顺序由牌堆顶到底), 要求判断获胜的一方

    例如: 输入 n = 3 n=3 n=3 368 368 368 135 135 135

    甲: 3 乙: 1 场上牌堆: 31

    甲: 6 乙: 3 场上排队: 3163, 乙获得 3163四张手牌

    甲: 8 由于甲没有手牌,乙获胜

    • 分析

      对于桌子上的牌,可以视为栈,每次出牌时遍历寻找入栈元素是否在栈中存在相同元素. 而玩家的牌堆可以视为队列, 按照输入的顺序入队,出牌时从牌堆顶摸牌, 出队输入的第一个元素, 满足"先进先出"的特点.

      我们在循环中模拟出牌:

      在这里插入图片描述

    • 代码实现
      #include <stdio.h>
      
      #define MAXSIZE 1000
      
      int isCardInDesk(int* base,int top){
      	for(int idx=top-1;idx!=-1;idx--)
      		if(base[idx]==base[top]) return idx;
      		
      	return -1;
      }
      
      void main(){
      	int cardNum; 
      	printf("Please input crad number: ");
      	scanf("%d",&cardNum);
      	
      	int stackA[MAXSIZE],stackB[MAXSIZE];
      	int topA= 0,rearA= 0,rearB= 0,topB= 0;
      	
      	int desk[MAXSIZE], topDesk=-1;
      	
      	printf("\nPlease input stackA: ");
      	for(int idx=1;idx<=cardNum;idx++)
      		scanf("%d",&(stackA[rearA++]));
      	
      	printf("\nPlease input stackB ");
      	for(int idx=1;idx<=cardNum;idx++)
      		scanf("%d",&(stackB[rearB++]));
      		
      	while(1){
      		printf("\nStack A has card: ");
      		for(int idx=topA;idx<rearA;idx++)
      			printf("%d ",stackA[idx]);
      			
      		//A出队
      		int cardA= stackA[topA++];
      		printf("\nA show card %d",cardA);
      		
      		//Desk入栈 
      		desk[++topDesk]= cardA;
      		
      		//CardA 是否在 Desk中 
      		if(isCardInDesk(desk,topDesk)!=-1){
      			int tmp= isCardInDesk(desk,topDesk); 
      			printf("\nFind card same as card A showed, A gained %d cards",topDesk-tmp+1);
      			
      			for(int idx=topDesk;idx>=tmp;idx--){
      				//手牌入队A
      				stackA[rearA++]= desk[idx];	
      				//手牌出栈Desk 
      				desk[topDesk--]= 0;
      			}		
      		}
      			
      		printf("\nStack B has card: ");
      		for(int idx=topB;idx<rearB;idx++)
      			printf("%d ",stackB[idx]);
      			
      		//B出队
      		int cardB= stackB[topB++];
      		printf("\nB show card %d",cardB);
      		
      		//Desk入栈 
      		desk[++topDesk]= cardB;
      		
      		//CardB 是否在 Desk中 
      		if(isCardInDesk(desk,topDesk)!=-1){
      			int tmp= isCardInDesk(desk,topDesk); 
      			printf("\nFind card same as card B showed, B gained %d cards",topDesk-tmp);
      			
      			for(int idx=topDesk;idx>=tmp;idx--){
      				//手牌入队B
      				stackB[rearB++]= desk[idx];	
      				//手牌出栈Desk 
      				desk[topDesk--]= 0;
      			}		
      		}
      		
      		if(topA==rearA || topB==rearB){
      			topA==rearA? printf("\nB wins") : printf("\nA wins");
      			break;
      		}
      	}
      }
      

链式队列

同样,链式的队列也将数据存储在节点中,节点分布在非连续的内存中.

创建的链式队列一般存在头节点, 同样包括两个rear,top指针.

在这里插入图片描述

因此将节点定义成:

typedef struct LinkedQueueNode{
	struct LinkedQueueNode* next;
	int elm;
}LQNode;
  • 代码实现

    入队时:

在这里插入图片描述

  1. 创建并初始化节点insNode
  2. rear指向的节点的next指针指向insNode
  3. 移动rearinsNode
LQNode* enQueueLQ(LQNode* head,LQNode* rear,int insElm){
	//Create insNode
	LQNode* insNode= (LQNode*)malloc(sizeof(LQNode));
	insNode->next= NULL;
	insNode->elm= insElm;
	
	//Insert insNode to end of queue
	rear->next= insNode;
	//Move rear
	rear= insNode;
	
	return rear;
}

出队时:

在这里插入图片描述

  1. 判断是否空队列,保存top指向节点的下一个节点,即要删除的节点
  2. 跳过要删除的节点 top->next=top->next->next, 若rear指向了要删除的节点,需要移动rear指针到头节点
  3. 释放要删除节点的内存
LQNode* deQueueLQ(LQNode* head,LQNode* top,LQNode* rear){
	//Check if a void queue
	if(top==rear) return ;
	
	//Save delNode
	LQNode* delNode= top->next;
	
	//Skip delNdoe
	top->next= top->next->next;
	delNode->next= NULL;
	
	//Check if queue has noly one node
	if(rear==delNode) rear= top;
	
	free(delNode); 
	return rear;
}

Reference

关于栈,队列的演示图来自 C语言中文网 C语言中文网:C语言程序设计门户网站(入门教程、编程软件) (biancheng.net)

流程图制作来自 ProcessOn https://www.processon.com/

文章封面来自 Pixiv画师ぢせhttps://www.pixiv.net/artworks/67329067

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值