以數制轉換問題講解棧數據結構的基本概念及其在計算機中的應用
一、棧的概念介紹
在我們的生活中,總有這麼一些例子,①食堂在堆放餐盤的時候,總是從下往上,在取餐盤的時候,又是從上往下;②最先放入廂式貨車的貨物,最後才能取出;③普通手槍的子彈夾,先裝進彈夾的子彈,最後纔會被打出來。類似於這樣的場景還有很多,這樣的存取順序,我們稱之爲先進後出(LIFO)。這種存取方式在解決某些計算機問題的時候非常高效,因此,在計算機科學中抽象出了一種數據結構,專門用於解決後進先出這類科學問題,在計算機中,這種後進先出的數據結構,我們稱之爲 棧。棧這種數據結構被抽想出來以後,就被廣泛的應用於計算機軟硬件系統中,在編譯系統、操作系統等系統軟件和各類應用軟件中經常使用棧來高效的完成特定的算法設計。在許多程序語言設計中,函數的調用,就會使用棧來保存形參以及函數的返回值。在硬件層面,有專門的棧寄存器來配合棧數據結構的高效訪問。(更好的閱讀體驗,請訪問程序員在旅途)
棧的邏輯結構是一種線性結構,和線性表相同,元素之間是一對一的關係,由於需要保證元素的後進先出規則,因此需要對線性表的運算操作進行一定的限制,即只允許在線性表的一端進行插入和刪除來滿足棧的要求。因此可以認爲棧是限制在表的一端進行插入和刪除的線性表。在線性表中允許插入、刪除的這一端稱爲棧頂,棧頂的位置是動態變化的;不允許操作的這一端稱爲棧底,棧底是固定不變的。當表中沒有元素時稱爲空棧。
對於棧而言,常用的操作有:①棧的初始化; ②判斷棧是否爲空;③入棧;④出棧;⑤取棧頂元素;⑥銷燬棧。這些基本的操作是組成複雜程序的基礎。棧的示意圖如下:
棧的物理實現有兩種方式,分別爲順序存儲方式 和 鏈式存儲方式。採用順序存儲的棧我們稱之爲順序棧,採用鏈式存儲結構的棧稱之爲鏈式棧。
二、順序棧的操作實現
和順序表的定義實現一樣,順序棧的實現,要分配一塊連續的存儲空間存放棧中的內容,可以用一維數組來實現,同時定義一個top變量指明當前棧頂的位置。類型描述如下:
#define MAXSIZE 50
typedef struct{
ElementType data[MAXSIZE];
int top;
}SeqStack,*PSeqStack;
定義一個指向棧的指針:
PSeqStack p = (PSeqStack)malloc(sizeof(SeqStack));
由於順序棧的數據域是靜態分配的存儲空間,而棧的操作又是一個動態的過程,因此,在入棧的時候,可能出現棧中元素的個數超過棧的最大空間大小,這時候就產生棧的溢出現象,這稱之爲上溢。在出棧的時候,可能全部元素都出去了,再也沒有元素可以出棧了,這也是棧的溢出現象,稱之爲下溢。在棧的操作實現過程中,要注意溢出的檢測。
2.1初始化順序棧
順序棧的初始化,就是構造一個空棧,然後返回一個指向順序表的指針。做法是使用malloc()分配棧這種結構體的存儲空間,然後,將棧中top置爲-1,標識空棧。代碼如下:
PSeqStack init_seqstack(){
//給棧分配內存空間
PSeqStack p = (PSeqStack)malloc(sizeof(SeqStack));
if(p){
p->top = -1; //top=-1代表空棧;
}
return p;
}
2.2判斷棧是否爲空
判斷棧中是否有元素,只要判斷top是否等於-1即可。(要注意判斷p是爲非NULL)
int empty_seqstack(PSeqStack p){
if(p){
if(p->top == -1){
return 1; //1代表空棧
}else{
return 0; //0代表非空棧
}
}else{
return 1; //1代表空棧
}
}
2.3入棧
入棧是在棧的頂部插入元素,由於是逐漸遞加的,因此,不需要移動元素。首先判斷棧是否已滿,若滿了,則退出;由於棧的top指向棧頂,只要將入棧元素賦到top+1的位置。同時執行top++即可。
int push_seqstack(PSeqStack p, ElementType x){
//棧滿則退出
if(p->top == MAXSIZE - 1){
return 0
}else{
//給棧頂元素賦值
p->top ++;
p->data[p->top] = x;
return 1;
}
}
2.4出棧
出棧也是在棧的頂部進行元素的刪除操作,也是無需移動元素,只要改變top的指向即可,然後把出棧的那個元素返回。思路是:首先判斷棧是否爲空,若空則退出;否則由於棧的top指向棧頂,只要修改top爲top-1即可。
int pop_seqstack(PSeqStack p, ElementType *x){//傳進來的是一個指針變量,這個起到保存出棧的數據的目的。
if(empty_seqstack(p)){ //棧爲空,則無法出棧
return 0;
}
*x = p->data[p->top]; //將棧頂元素賦給*x;
p->top --;
return 1;
}
2.5取棧頂元素
取棧頂元素就是取棧的top變量指向的元素。思路是:首先判斷棧是否爲空,若空則退出;否則由於棧的top指向棧頂,返回top所指單元的值即可,棧本身不發生變化。
int getTop_seqstack(PSeqStack p, ElementType *x){
if(empty_seqstack(p)){ //棧爲空,則無法取值
return 0;
}
*x = p->data[p->top];
return 1;
}
2.6銷燬棧
棧被初始化之後就在內存中分配了一塊連續的存儲空間,因此,如果在使用完之後,必須要將其銷燬,釋放其佔用的空間。
void destory_seqstack(PSeqStack *p){ //注意,這裏傳入的是*p;是爲了釋放p指向的地址空間。
if(*p){
free(*p);
}
*p = NULL;
return ;
}
三、鏈式棧的操作實現
棧的鏈式存儲結構一般用單鏈表表示,節點結構與單鏈表的結構相同,由於棧只是棧頂在做插入和刪除操作,所以棧頂應該放在單鏈表的頭部。由於不需要爲了各個節點的統一操作增加一個頭節點,所以棧使用的鏈表是沒有頭結點的。鏈表的節點結構如下:
typedef struct node{
ElementType data;
struct node *next;
}StackNode,*PStackNode;
放了方便操作,同時強調棧頂是棧的一個屬性,我們可以定義如下的棧的結構:
typedef struct{
PStackNode top;
}LinkStack,*PLinkStack;
定義一個指向棧的指針:
PLinkStack p = (PLinkStack)malloc(sizeof(LinkStack));
要注意的是:對於鏈棧來說,基本不存在棧滿的情況,除非內存已經沒有使用空間了。對於空棧來說,鏈表原來的定義是頭指針指向空,那麼鏈棧的空其實就是top=NULL。
3.1初始化空棧
棧在使用之前,要進行初始化,申請棧頂節點需要的內存空間。注意,這裏申請的是棧結構LinkStack需要的內存空間,而不是節點結構StackNode的節點空間。入棧的時候,申請的是StackNode節點空間。top的值類型是StackNode。
PLinkStack init_linkstack(){
PLinkStack p = (PLinkStack)malloc(sizeof(LinkStack)); //初始化鏈棧,申請top節點的內存空間
if(p){
p->top = NULL;
}
return p;
}
3.2判斷空棧
在出棧,取棧頂元素之前,首先得要判斷棧是否爲空,如爲空,則不取。
//判斷空棧;1表示空棧,0表示非空棧
int empty_linkstack(PLinkStack p){
if(p){
return (p->top == NULL);
}
return 1;
}
3.3入棧
和順序棧一樣,入棧的概念都是在棧頂插入元素。申請StackNode節點空間,賦給top。
//入棧。1入棧成功,0入棧失敗
int push_linkstack(PLinkStack p, ElementType x){
PStackNode temp_stacknode = (PStackNode)malloc(sizeof(StackNode));
if(temp_stacknode){
//這裏是入棧的核心操作,將最新申請的節點賦給top。
temp_stacknode->data = x;
temp_stacknode->next = p->top;
p->top = temp_stacknode;
return 1;
}else{
printf("內存空間不足,申請失敗\n");
return 0;
}
}
3.4出棧
出棧就是彈出棧頂元素,然後修改top的值。
//出棧。0出棧失敗1出棧成功
int pop_linkstack(PLinkStack p, ElementType *x){
if(empty_linkstack(p)){
printf("棧空,不能出棧");
return 0;
}{
//取棧頂元素,然後將這個top指向的節點free掉。
*x = p->top->data;
PStackNode x = p->top;
p->top = p->top->next;
free(x);
return 1;
}
}
3.5取棧頂元素
//取棧頂元素
int getTop_linkstack(PLinkStack p, ElementType *x){
if(empty_linkstack(p)){
printf("棧空");
return 0;
}{
//取棧頂元素,然後將這個top指向的節點free掉。
*x = p->top->data;
return 1;
}
}
3.6銷燬棧
和順序棧一樣,棧結構使用完之後,都要進行銷燬,以釋放其佔用的內存空間。
//銷燬棧
void destory_linkstack(PLinkStack *p){
if(*p){
PStackNode x,s;
x = (*p)->top;
//遍歷棧,然後將其每一個節點都給釋放掉。
while(x){
s = x;
x = x->next;
free(s);
}
free(*p);
}
*p = NULL;
}
四、棧的典型應用實例 — 數制轉換問題。
在學習計算機組成原理的時候,會經常的處理各種數制之間進行轉換的問題,而輾轉相除法就是我們最經常用的一種方法。輾轉相除法即是歐幾里得算法,主要用於計算兩個正整數a,b的最大公約數,應用領域有數學和計算機兩個方面,拓展應用領域很廣泛,輾轉相除法在處理數制轉換問題上非常有效,算法效率也很高。
將十進制數N轉換成r進制的數,採用輾轉相除法示意圖如下:以N= 1234,r=8爲例進行轉換 。
從上圖看,餘數由低位向高位依次產生,而八進制數則是從高位到低位輸出,具備後進先出的特性,因此恰好可以使用棧來保存這個結果。思路就是將N%r的結果依次入棧,待到N/r=0的時候算法結束,依次出棧即可。程序如下:
4.1順序棧算法
int numerical_conversition(int n, int r){
PSeqStack p;
ElementType x;
if(!r){
printf("基數不能爲0\n");
return 0;
}
p = init_seqstack();
if(!p){
printf("棧初始化失敗\n");
return 0;
}
while(n){
//餘數依次入棧
push_seqstack(p,n%r);
n = n / r;
}
while(!empty_seqstack(p)){
//依次出棧
pop_seqstack(p,&x);
printf("%d ",x);
}
printf("\n");
destory_seqstack(&p);
return 1;
}
4.2 鏈式棧算法
int numerical_conversition(int n, int r){
PLinkStack p;
ElementType x;
if(!r){
printf("基數不能爲0\n");
return 0;
}
p = init_linkstack();
if(!p){
printf("棧初始化失敗\n");
return 0;
}
while(n){
//餘數依次入棧
push_linkstack(p,n%r);
n = n / r;
}
while(!empty_linkstack(p)){
//依次出棧
pop_linkstack(p,&x);
printf("%d ",x);
}
printf("\n");
destory_linkstack(&p);
return 1;
}
完整程序如下:
①鏈式棧
#include<stdio.h>
#include<stdlib.h>
typedef int ElementType;
typedef struct node{
ElementType data;
struct node *next;
}StackNode,*PStackNode;
typedef struct{
PStackNode top;
}LinkStack,*PLinkStack;
//初始化空棧
PLinkStack init_linkstack(){
PLinkStack p = (PLinkStack)malloc(sizeof(LinkStack)); //初始化鏈棧,申請top節點的內存空間
if(p){
p->top = NULL;
}
return p;
}
//判斷空棧;1表示空棧,0表示非空棧
int empty_linkstack(PLinkStack p){
if(p){
return (p->top == NULL);
}
return 1;
}
//入棧。1入站成功,0入棧失敗
int push_linkstack(PLinkStack p, ElementType x){
PStackNode temp_stacknode = (PStackNode)malloc(sizeof(StackNode));
if(temp_stacknode){
//這裏是入棧的核心操作,將最新申請的節點賦給top。
temp_stacknode->data = x;
temp_stacknode->next = p->top;
p->top = temp_stacknode;
return 1;
}else{
printf("內存空間不足,申請失敗\n");
return 0;
}
}
//出棧.0出棧失敗1出棧成功
int pop_linkstack(PLinkStack p, ElementType *x){
if(empty_linkstack(p)){
printf("棧空,不能出棧");
return 0;
}{
//取棧頂元素,然後將這個top指向的節點free掉。
*x = p->top->data;
PStackNode x = p->top;
p->top = p->top->next;
free(x);
return 1;
}
}
//取棧頂元素
int getTop_linkstack(PLinkStack p, ElementType *x){
if(empty_linkstack(p)){
printf("棧空");
return 0;
}{
//取棧頂元素,然後將這個top指向的節點free掉。
*x = p->top->data;
return 1;
}
}
//銷燬棧
void destory_linkstack(PLinkStack *p){
if(*p){
PStackNode x,s;
x = (*p)->top;
//遍歷棧,然後將其每一個節點都給釋放掉。
while(x){
s = x;
x = x->next;
free(s);
}
free(*p);
}
*p = NULL;
}
int numerical_conversition(int n, int r){
PLinkStack p;
ElementType x;
if(!r){
printf("基數不能爲0\n");
return 0;
}
p = init_linkstack();
if(!p){
printf("棧初始化失敗\n");
return 0;
}
while(n){
//餘數依次入棧
push_linkstack(p,n%r);
n = n / r;
}
while(!empty_linkstack(p)){
//依次出棧
pop_linkstack(p,&x);
printf("%d ",x);
}
printf("\n");
destory_linkstack(&p);
return 1;
}
int main(){
numerical_conversition(1234,8);
return 0;
}
②順序棧
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
typedef int ElementType;
typedef struct{
ElementType data[MAXSIZE];
int top;
}SeqStack,*PSeqStack;
//初始化棧
PSeqStack init_seqstack(){
//給棧分配內存空間
PSeqStack p = (PSeqStack)malloc(sizeof(SeqStack));
if(p){
p->top = -1;
}
return p;
}
int empty_seqstack(PSeqStack p){
if(p){
if(p->top == -1){
return 1; //1代表空棧
}else{
return 0; //0代表非空棧
}
}else{
return 1; //1代表空棧
}
}
//入棧
int push_seqstack(PSeqStack p, ElementType x){
//棧滿則退出
if(p->top == MAXSIZE - 1){
return 0;
}else{
//給棧頂元素賦值
p->top ++;
p->data[p->top] = x;
return 1;
}
}
//彈出棧
int pop_seqstack(PSeqStack p, ElementType *x){//傳進來的是一個指針變量,這個起到保存出棧的數據的目的。
if(empty_seqstack(p)){ //棧爲空,則無法出棧
return 0;
}
*x = p->data[p->top]; //將棧頂元素賦給*x;
p->top --;
return 1;
}
//獲取棧頂元素
int getTop_seqstack(PSeqStack p, ElementType *x){
if(empty_seqstack(p)){ //棧爲空,則無法取值
return 0;
}
*x = p->data[p->top];
return 1;
}
//銷燬棧
void destory_seqstack(PSeqStack *p){ //注意,這裏傳入的是*p;是爲了釋放p指向的地址空間。
if(*p){
free(*p);
}
*p = NULL;
return ;
}
int numerical_conversition(int n, int r){
PSeqStack p;
ElementType x;
if(!r){
printf("基數不能爲0\n");
return 0;
}
p = init_seqstack();
if(!p){
printf("棧初始化失敗\n");
return 0;
}
while(n){
//餘數依次入棧
push_seqstack(p,n%r);
n = n / r;
}
while(!empty_seqstack(p)){
//依次出棧
pop_seqstack(p,&x);
printf("%d ",x);
}
printf("\n");
destory_seqstack(&p);
return 1;
}
int main(){
numerical_conversition(1234,8);
return 0;
}