**小菜鸡的实验三 栈的应用**

实验三 栈的应用
116132019116 XZR
一、实验目的:

  1. 掌握顺序栈和队列的存储结构和实现方法;
  2. 应用栈解决数制转换、括号匹配、行编辑和迷宫求解;
  3. 识记循环队列能够解决普通队列的假溢出问题;
  4. 熟练掌握对于循环队列出队、入队,以及计算队列元素个数时对于队列的头尾指针分别执行的操作。
    二、实验内容:
  5. 栈的应用部分:我完成了栈的结构以及用栈实现求解数制转换、括号匹配、行编辑和迷宫求解四种问题。
    三、实验步骤与实验算法设计:
    写在前面:
    栈的ADT设计:
ADT SqStack{
     数据元素:D = {
     (Stack[stacksize],top,base)|Stack[i]∈DataType;top,base∈DataType*}
数据关系:R = {
     <data i,data i+1>|data i,data i+1∈D}
基本操作:
InitStack(SqStack& stack)
初始条件:stack已存在操作结果:将栈初始化,stack.base = stack.Stack;再使stack.top = stack.base 

PushStack(SqStack& stack,DataTyPe data)
初始条件:stack已存在操作结果:data进栈,位于stack栈顶,stack.top++ 

PopStack(SqStack& stack, DataTyPe& data)
初始条件:stack已存在且不为空栈操作结果:栈顶元素出栈,并将栈顶元素存于data 

ClearStack(SqStack& stack,DataType& data)
初始条件:stack已存在操作结果:与初始化类似,stack.base = stack.Stack;再使stack.top = stack.base 
}

栈的应用部分:
(一)利用栈实现数制转换,包括十转二、十转八。

  1. 需要用到的基本操作:入栈、出栈,栈的逆序输出等
    ①栈的初始化:`
Status InitStack(SqStack& stack) {
     	
stack.base = stack.Stack;	
stack.top = stack.Stack;	
stack.length = 0;	
return OK;}

②入栈:

Status PushStack(SqStack& stack,DataType data) {
     	
//cout << "输入要进栈的数据元素:";	
//cin >> data;	if (stack.length <= StackSize) {		*(stack.top++) = data;		
stack.length++;		
cout << data << "进栈成功!" << endl;		
return OK;	
}	
else {
     		
cout << "进栈失败!栈已满!" << endl;		
return ERROR;	}}

③出栈:

Status PopStack(SqStack& stack, DataType& data) {
     	
if (IsEmptyStack) {
     		
cout << "无可退栈的数据元素!" << endl;		
return ERROR;	
	}	
else {
     		
data = *(stack.top);		
stack.top--;		
stack.length--;		
cout << data << "退栈成功!" << endl;		
return OK;	
	}
}

④栈的输出操作符重载(从栈底往栈顶进行输出):

ostream& operator << (ostream& out, SqStack& stack) {
      
out << "这个栈为:(";
 for (int i = 0; i < stack.length; i++) {
     
   out << stack.Stack[i] << ","; 
   }
    out << "\b)" << endl; 
   return out;
 }

⑤栈的逆序输出(为数制转换量身定做的输出方式):

void ReversePrint(SqStack& stack) {
     
	DataType* TempTop = stack.top;//保存栈顶指针
	while (stack.top != stack.base) {
     
		cout << *(--stack.top);	
		}
		stack.top = TempTop;//恢复栈顶指针
	}

⑥栈的清空:

Status ClearStack(SqStack& stack) {
      
if (IsEmptyStack(stack)) {
     
  cout << "无须清空!" << endl;
    return ERROR; 
    } 
    else {
      
     stack.top = stack.base; 
      cout << "清空栈成功!" << endl; 
       return OK; 
      	 }
       }

⑦空栈的判断:

bool IsEmptyStack(SqStack& stack) {
     
	if (stack.base == stack.top) {
     
		cout << "是空栈!" ;
		return 1;	
		}	
		else {
     		
		cout << "不是空栈!" ;
		return 0;	
		}
		}
  1. 数制转换算法:

本实验中,仅完成了十进制数转二进制数和十进制数转八进制数,其实,只要是从十进制数转出去,不管转成几进制数都是一样的算法。我采用的是逐次取余的算法。
eg:比如十进制数9转二进制数,那么第一次9与2取余,余数为1,则1入栈;然后将9除以2取整,得到4,再对2取余,余数为0,则0入栈;第三次,将4除以2得到2,再对2取余,余数为0,则0入栈,然后将2除以2得到1,再对2取余,余数为1,则1入栈。最后,得到的栈从栈底到栈顶的数据元素可以表示为:(1,0,0,1),最后将这个栈从栈顶往栈底输出,可以得到十进制数9对应的二进制数就为:1001十进制转八进制的算法也是相同的。

现在根据这个思路写出数制转换函数的伪代码:

int number;//输入的想要转换的数字
int TwoOrEight;/*选择想要转换成的数制,可以是2或8,其实这里也可以是2~9中的其他任何数。 */
用户输入TwoOrEight;
 当number >= TwoOrEight的时候{
     
  将得到的余数入栈; 
  number = number / TwoOrEight; 
  对number取整;
  }
  对最后一次特殊处理,number对TwoOrEight取余将得到的余数入栈; 

现在根据伪代码打出代码:

Status ConverseNumber(SqStack& stack, int number) {
     
 int TwoOrEight; 
 cout << "输入想要转换的十进制数:";
  cin >> number; 
  cout << "输入想要转换成的数制(仅支持2或8):"; 
  cin >> TwoOrEight; 
  while (number  >= TwoOrEight) {
       
  if (number % TwoOrEight) {
      
    PushStack(stack, number % TwoOrEight); 
      number = (int)(number / TwoOrEight) ; 
       } 
  else {
       
   PushStack(stack, 0); 
     number /= TwoOrEight; 
      } 
      } 
      if (number % TwoOrEight ) {
      
       PushStack(stack, 1); 
       } 
      else {
       
      PushStack(stack, number%TwoOrEight);
        } 
      cout << "这个数字的"<<TwoOrEight<<"进制数为:";
      ReversePrint(stack); 
      cout << endl;
      return OK;
      } 

(二)利用栈实现括号匹配,包括[] {} () <>这4种括号
1.需要用到的基本操作:出入栈等,与数制转换的基本操作 无异,但是将操作细节的提示注释掉了,比如cout<<”入栈成功!”<

这里需要指出的不同点是:我把DataType 定义成了char类型,以便输入括号,而不是数制转换中的int类型。

2.行编辑算法设计:将用户输入的所有合法括号依次入栈,并判断此次即将入栈的括号是否为当前栈顶的目标匹配括号,如果是,则可以:
①此括号入栈后对栈执行两次出栈
②此括号不入栈,栈顶元素出栈一次如果当前输入的合法括号不是当前栈顶括号的目标匹配括号,那么此括号正常入栈,等待匹配。当用户输入完毕后,所有能够匹配的括号对都已经出栈了,也就是说,如果用户输入的这个括号群能够全部匹配,那么最后栈是一个空栈,若这个括号群不能够全部匹配,则最后的栈将不是一个空栈,里面的数据元素将是无法被匹配的括号群。

其实,这个程序并不局限于括号匹配,甚至可以延申到引号匹配,只需要多加两个if条件判断句即可。
现在根据算法写出代码:

bool MatchBrackets(SqStack& stack) {
     
 cout << "输入一堆括号,来匹配一波" << endl;
 int i = 0; DataType data; 
 DataType* brackets = new DataType[BracketSize]; 
 //while (scanf_s("%c", &data) != '\n') { 
 // brackets[i] = data; // cout << brackets[i++]; 
 //} 
 cin >> brackets; 
 while (*brackets != '\0') {
       
 //cout << "111" << endl;  
 PushStack(stack,*brackets );//将用户输入的括号存入栈  
 if (stack.length != 0) {
        
 if (*brackets == ')' && stack.Stack[stack.length - 2] == '(') {
         PopStack(stack, data);    
PopStack(stack, data);   
}   
if (*brackets == '>' && stack.Stack[stack.length - 2] == '<') {
         PopStack(stack, data);    
PopStack(stack, data);   
}   
if (*brackets == '}' && stack.Stack[stack.length - 2] == '{') {
         PopStack(stack, data);    
PopStack(stack, data);   
}   
if (*brackets == ']' && stack.Stack[stack.length - 2] == '[') {
         PopStack(stack, data);    
PopStack(stack, data);   
}   
if (*brackets == '》' && stack.Stack[stack.length - 2] == '《') {
         PopStack(stack, data);    
PopStack(stack, data);   
}   
if (*brackets == '】' && stack.Stack[stack.length - 2] == '【') {
         PopStack(stack, data);    
PopStack(stack, data);   
}  
}  
//cout << stack;  
brackets++; 
} 
cout << stack; 
if (IsEmptyStack(stack))  
return 1;//括号匹配,返回1 
else return 0;//括号不匹配,返回0
} 

(三)利用栈实现行编辑

  1. 需要用到的基本操作与括号匹配几乎完全一样,不再赘述
    这里需要指出的不同点是:我对ClearStack(SqStack& stack)函数进行了改正和优化。之前的清空操作只是把栈顶指针指向栈底,在一定程度上可以对栈的元素进行重新赋值,但这种清空是存在bug的伪清空,如果要真正清空,需要将当前栈的每个数据元素都赋值为’\0’,才能实现真正意义上清空。
    2.行编辑算法设计:
    我们假定遇到用户输入’#’时表示想退格1格,输入’@’时表示想清空当前行的所有内容,重新输入当前行,那么,我们只需要对用户每次输入的入栈元素进行判断。
 if(data == ‘#’) 
 PopStack(stack); 
 else if(data == ‘@’) 
 ClearStack(stack);

``现在根据算法写出行编辑函数的代码:

Status LineEdit(SqStack& stack) {
      
//行编译执行函数,遇到'#'时退格一次,遇到‘@’时清除整个行 
DataType data; 
int count; 
cout << "输入想要行编译的行数:"; 
cin >> count; 
while (count--) {
       
DataType* line = new DataType[LineSize];  
cout << "输入想要编辑的字符串行:";  
cin >> line;  
while (*line != '\0') {
        
PushStack(stack, *line);   
if (stack.length != 0) {
         
if (*(stack.top - 1) == '#') {
         
 PopStack(stack, data);     
 PopStack(stack, data);    
	 }    
 else if (*(stack.top - 1) == '@') {
          
 ClearStack(stack);    
				 }   
 			}   
 line++;  
 		}  
 cout << stack; 
 ClearStack(stack); 
 	} 
 return OK;
 } 

(四)利用栈实现迷宫求解问题

  1. 迷宫的C/C++架构:
    我采用一个含m×n个有效点位数据元素二位数组来存放迷宫,其中每个元素均为0或1,0表示此处可通,1表示此处为死路不通。
    为了更好地解决可能出现的数组越界问题,我在原有迷宫的基础上又增加了一个围绕迷宫四周的边界1,以便当走到数组边界时因“此路不同”而放弃,而不是因数组越界而发生一系列异常。
    所以如果这个迷宫有m×n个有效点位,那么我就要申请一个(m+2)×(n+2)大的二位数组。下面给出设计的范例迷宫:
    **小菜鸡的实验三 栈的应用**_第1张图片

注:红底色表示非迷宫的数据元素,只是用来更好地防止操作时数组越界,同时也避免了数组存储下标和逻辑下标不一致给写代码带来的繁琐的到底该加一还是减一的问题。蓝色直线标明了所有可行的路线。
其中我把每个点的可行方向定义为左上、正上、右上、正右、右下、正下、左下、正左8个方向。代码如下:

 #define m 6
 #define n 8
 int maze[m + 2][n + 2] = {
       
 {
     1,1,1,1,1,1,1,1,1,1},      
 {
     1,0,1,1,1,0,1,1,1,1},       
 {
     1,1,0,1,0,1,1,1,1,1},       
 {
     1,0,1,0,0,0,0,0,1,1},       
 {
     1,0,1,1,1,0,1,1,1,1},       
 {
     1,1,0,0,1,1,0,0,0,1},       
 {
     1,0,1,1,0,0,1,1,0,1},       
 {
     1,1,1,1,1,1,1,1,1,1} };//迷宫的设计
  1. 迷宫中各点的定义:包含他的坐标x,y,由于对边界作了特殊处理,导致坐标x,y恰好分别与存储迷宫中的每个数据元素的对应下标相同,这极大地优化了程序的可读性。
typedef struct {
      int x, y;}Point;//点的设计 

3.点的移动函数的设计:

 int directions[8] = {
      0,1,2,3,4,5,6,7 };//分别代表左上、正上、右上、正右、          //右下、正下、左下、正左8个方向。 
 bool MovePoint(Point& p, int maze[m + 2][n + 2], int direction) {
     
  bool flag = 0; 
  switch (direction) {
      
  case 0://往左上移动  
  	if (maze[p.x - 1][p.y - 1] == 1) {
        
  	cout << "往左上方行走不通!" << endl;  
  	}  
  	else if (maze[p.x - 1][p.y - 1] == 0) {
       
  	 cout << "往左上方行走通了!" << endl;   
  	 p.x -= 1;   p.y -= 1;   
  	 flag = 1;  
  	 }  break; 
  	 case 1://往正上方移动  if (maze[p.x][p.y - 1] == 1) {
        
  	 cout << "往左上行走不通!" << endl;   
  	 return 0;  
  	 }  
  	 else if (maze[p.x][p.y - 1] == 0) {
        
  	 cout << "往正上方行走通了!" << endl;   
  	 p.y -= 1;   
  	 flag = 1;  
  	 }  
  	 break; 
  	 case 2://往右上方移动  
  	 if (maze[p.x + 1][p.y - 1] == 1) {
     
  	        cout << "往右上方行走不通!" << endl;
  	   }  
  	   else if (maze[p.x + 1][p.y - 1] == 0) {
        
  	   p.x++;   p.y--;   
  	   cout << "往右上方行走通了!" << endl;   
  	   flag = 1;  
  	   }  
  	   break; 
  	   case 3://往正右方移动  
  	   if (maze[p.x + 1][p.y] == 1) {
        
  	   cout << "往正右方行走不通!" << endl;  
  	   }  
  	   else if (maze[p.x + 1][p.y] == 0) {
        
  	   p.x++;   
  	   cout << "往正右方行走通了!" << endl;   
  	   flag = 1;  
  	   } 
  	   case 4://往右下移动  
  	   if (maze[p.x + 1][p.y + 1] == 1) {
        
  	   cout << "往右下方行走不通!" << endl;  
  	   }  
  	   else if (maze[p.x + 1][p.y + 1] == 0) {
        
  	   p.x++;   p.y++;   
  	   cout << "往右下方行走通了!" << endl;   
  	   flag = 1;  
  	   }  
  	   break; 
  	   case 5://往正下移动  
  	   if (maze[p.x][p.y + 1] == 1) {
        
  	   cout << "往正下方行走不通!" << endl;  
  	   }  
  	   else if (maze[p.x][p.y + 1] == 0) {
        
  	   p.y++;   
  	   cout << "往正下方行走通了!" << endl;   
  	   flag = 1;  
  	   }  
  	   break; 
  	   case 6://往左下移动  
  	   if (maze[p.x - 1][p.y - 1] == 1) {
        
  	   cout << "往左下方行走不通!" << endl;  
  	   }  
  	   else if (maze[p.x - 1][p.y - 1] == 0) {
        
  	   p.x--;   p.y--;   
  	   cout << "往左下方行走通了!" << endl;   
  	   flag = 1;  
  	   } 
  	   case 7://往正左移动  
  	   if (maze[p.x - 1][p.y] == 1) {
        
  	   cout << "往正左方行走不通!" << endl;  
  	   }  
  	   else if (maze[p.x - 1][p.y] == 0) {
        
  	   p.x--;   cout << "往正左方行走通了!" << endl;   
  	   flag = 1;  
  	   }  
  	   break; 
  	   } 
  	   return flag;
  	   }
  1. 点的移动方向优先级确定:使用深度优先搜索(Depth First Search),即先搜索左下、正下、右下,若无可行路线再搜索左、右,若还无可行路线再搜索左上、正上、右上。如果确定此路不通,则确定死路的最后一个可行点,在此死胡同点将不再进行深度优先搜索,而执行广度优先搜索(Breadth First Search)。
    ①深度优先搜索的实现:
bool DepthFirstSearch(Point& p, int maze[m + 2][n + 2],bool& flag) {
     
 flag = 0;
 for (int i = 5; i >= 3; i--) {
     
  if (MovePoint(p, maze, i)) {
     
   flag = 1;
   return 1;
  }
 }
 if (flag == 0) {
     //深度优先搜索无果时,执行广度搜索
  if (MovePoint(p, maze, 2)) {
     
   flag = 1;
   return 1;
  }
  MovePoint(p, maze, 6);
  if (MovePoint(p, maze, 2)) {
     
   flag = 1;
   return 1;
  }
 }
 if (flag == 0) {
     //深度搜索和广度搜索都无果时,考虑向上返回原路径。同时函数返回0以作为此路不通的信号。
  //然后对返回后的点执行广度优先搜索。
  if (MovePoint(p, maze, 7)) {
     
   flag = 1;
   return 0;
  }
  if (MovePoint(p, maze, 0)) {
     
   flag = 1;
   return 0;
  }
  if (MovePoint(p, maze, 1)) {
     
   flag = 1;
   return 0;
  }
 }
}

②广度优先搜索的实现:


bool BreadthFirstSearch(Point& p, int maze[m + 2][n + 2],bool& flag) {
     
 flag = 0;
 if (flag == 0) {
     //执行广度优先搜索
  if (MovePoint(p, maze, 2)) {
     
   flag = 1;
   return 1;
  }
  MovePoint(p, maze, 6);
  if (MovePoint(p, maze, 2)) {
     
   flag = 1;
   return 1;
  }
 }
 for (int i = 5; i >= 3; i--) {
     //执行深度搜索
  if (MovePoint(p, maze, i)) {
     
   flag = 1;
   return 1;
  }
 }
 if (flag == 0) {
     //广度搜索和深度搜索都无果时,考虑向上返回原路径。同时函数返回0以作为两点之间没有通路的信号
  if (MovePoint(p, maze, 7)) {
     
   flag = 1;
   return 0;
  }
  if (MovePoint(p, maze, 0)) {
     
   flag = 1;
   return 0;
  }
  if (MovePoint(p, maze, 1)) {
     
   flag = 1;
   return 0;
  }
 }
}
  1. 最后一步:在主函数中寻求一条点(1,1)到点(6,8)的一条可行的路径,通过深度优先搜索实现。程序的全部代码和实验结果截图都在附录。

附:
全部程序:
链接:https://pan.baidu.com/s/1f5Yik6rE-qjBFIyTEKuKFQ
提取码:drvr
复制这段内容后打开百度网盘手机App,操作更方便哦

你可能感兴趣的:(栈,c++,栈)