顺序栈
顺序栈与顺序表都使用一段连续的内存空间存储数据,但二者存储,删除数据时的顺序不同. 栈具有后进先出的特点, 虽然顺序栈上的数据同样可以用下标直接访问任意元素,但是这样做便使顺序表与顺序栈无异, 顺序栈的出栈,入栈操作应当永远对于栈顶,也就是存储在栈的地址最高处的有效值.
一个顺序栈应当包括一段连续内存与一个存储栈顶位置的变量或指针
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()
方法中作者没有对栈是否溢出进行判断,请读者自行根据需求实现自己的入栈操作. -
例题
-
进制装换
题目叙述十分简单: 将任意进制 A A A(2-36进制)的 X X X转换成 B B B进制的 Y Y Y
例如: 输入一个十六进制数 A 180 A180 A180 要求转换成一个四进制数 22012000 22012000 22012000
-
思路: 由于输入存在大于十进制的进制,因此我们将输入的进制数以字符储存,并使用
ASCII
将对应字符转换成数字, 具体公式为- 若字符 ′ 0 ′ ≤ c ≤ ′ 9 ′ '0'\leq c\leq '9' ′0′≤c≤′9′: 转换结果为 c − 48 c-48 c−48
- 若字符 ′ A ′ ≤ c ≤ ′ Z ′ 'A'\leq c\leq 'Z' ′A′≤c≤′Z′: 转换结果为 c − 55 c-55 c−55
将字符转换后, 我们将结构按权展开,将其转换成十进制,如题:
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=10336⋯010334/4=2584⋯02584/4=646⋯0646/4=161⋯2161/4=40⋯140/4=10⋯010/4=2⋯22/4=0⋯2
其结果等于余数的倒序输出即 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;
- 创建并初始化节点
insNode
rear
指向的节点的next
指针指向insNode
- 移动
rear
到insNode
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;
}
出队时:
- 判断是否空队列,保存
top
指向节点的下一个节点,即要删除的节点 - 跳过要删除的节点
top->next=top->next->next
, 若rear
指向了要删除的节点,需要移动rear
指针到头节点 - 释放要删除节点的内存
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