《算法笔记》编程笔记——第七章与第六章部分 栈、队列、链表

《算法笔记》编程笔记——第七章与第六章部分 栈和递归、队列、链表

一、栈

  • 栈的使用:#include + using namespace std;

  • 栈的定义:stack st;

  • 栈内元素的访问:st.top()。只有这个方法进行元素的访问

  • 常用函数:

    • st.push(x);//压入数值
      st.pop();//弹出栈顶元素
      st.top();//获取栈顶元素
      st.empty();//栈判空,如果为空,返回true,否则返回false
      st.size();//返回栈内元素个数
      //上述操作的时间复杂度均为O(1);
      
    • 栈的清空:重新定义一个栈:

  • 栈的常见用途:

    • 通常用栈来模拟递归,防止出现程序崩溃的情况
  • 题目

    • 题目描述:

      • 读入一个只包含+, -, *, / 的非负整数计算表达式,计算该表达式的值。(结果精确到小数点后2位)

      • 思路:

        ①中缀表达式转后缀表达式

        ②计算后缀表达式

        步骤一:中缀表达式转为后缀表达式

        1)设立一个操作符栈,用来临时存放操作符;设立一个数组或者队列,用来存放后缀表达式

        2)从左至右扫描中缀表达式,如果碰到操作数(注意:操作数可能不止一位,因此需要一位一位读入然后合并到一起,具体实现见代码),就把操作数加入到后缀表达式中。

        3)如果碰到操作符op,就将其优先级与操作符栈栈顶的操作符优先级进行比较。 如果op优先级高于栈顶操作符,就将op压入操作符栈中; 如果op优先级低于或等于栈顶操作符优先级,则将操作符栈的操作符不断弹出到后缀表达式中,直到op的优先级高于栈顶操作符的优先级。

        4)不断重复上述操作,直到中缀表达式扫描完毕,之后若操作符栈中仍有元素,就将它们依次弹出到后缀表达式中。

        步骤二:计算后缀表达式

        从左到右扫描后缀表达式,如果是操作数,就压入栈中;如果是操作符,就连续弹出两个操作数(注意:后弹出的是第一操作数,先弹出的是第二操作数),然后进行操作符的操作计算,生成的新操作数再压入栈中。反复到后缀表达式扫描完毕,这时栈中仅存的最后一个数就是答案。

      • 代码实现

        /*
        测试数据:
        ①1 + 3 * 5 / 4 * 8 / 9 * 6 * 2 / 3 / 7 + 3 * 8 / 2 = 14.90
        ②2 / 3 * 4 = 2.67
        ③5 + 2 * 3 / 49 - 4 / 13 = 4.81
        */ 
        #include<bits/stdc++.h>
        using namespace std; 
        struct node{
        	double num;
        	char op;
        	bool flag; //判断是操作符还是操作数 
        };
        string str;
        stack<node> s; //操作符栈
        queue<node> q; //后缀表达式
        map<char, int> op;
        //将中缀表达式转为后缀表达式 
        void Change(){
        	node temp;
            //注意for循环括号里没有"i++”
        	for(int i = 0; i < str.length(); ){
        		if(str[i] >= '0' && str[i] <= '9'){
        			temp.flag = 1; //表明是操作数
        			//计算操作数,注意:操作数可能不止1位!!! 
        			temp.num = str[i++] -'0';
        			while(i < str.length() && str[i] >= '0' && str[i] <= '9'){
        				temp.num = temp.num * 10 + (str[i] - '0');
        				i++;
        			}
        			q.push(temp);//将操作数压入到后缀表达式队列中 
        		}else{//如果是操作符 
        			temp.flag = 0; //标记是操作符
        			//如果op优先级低于等于栈顶元素优先级,将栈顶元素弹出到后缀表达式队列中
        			while(!s.empty() && op[str[i]] <= op[s.top().op]){
        				q.push(s.top());
        				s.pop();
        			} 
        			temp.op = str[i];
        			s.push(temp);
        			i++;
        		} 
        	}
        	//中缀表达式扫描完毕,但栈中仍有操作符,将其放入到后缀表达式队列中
        	while(!s.empty()){
        		q.push(s.top());
        		s.pop();
        	} 
        } 
        //计算后缀表达式
        double Cal(){
        	double temp1, temp2;
        	node cur, temp;
        	while(!q.empty()){ //后缀表达式队列非空 
        		cur = q.front();
        		q.pop();
        		if(cur.flag == true)s.push(cur);//是操作数直接压入到栈中
        		else{
        			temp2 = s.top().num;//弹出第二操作数
        			s.pop();
        			temp1 = s.top().num;//弹出第一操作数
        			s.pop();
        			temp.flag = true; //临时记录操作数
        			if(cur.op == '+')temp.num = temp1 + temp2;
        			else if(cur.op == '-')temp.num = temp1 - temp2;
        			else if(cur.op == '*')temp.num = temp1 * temp2;
        			else temp.num = temp1 / temp2;
        			s.push(temp);//把计算得到的新操作数压入栈中 
        		} 
        	}
        	return s.top().num; //栈中剩下的最后一个数就是答案 
        } 
        int main(){
        	op['+'] = op['-'] = 1; //设定操作符的优先级
        	op['*'] = op['/'] = 2;
        	//输入一行字符串,包括有空格的也输入;当一行中只有0时结束循环 
        	while(getline(cin, str), str != "0"){
        		for(string::iterator it = str.end(); it != str.begin(); it--){
        			if(*it == ' ')str.erase(it);//把表达式中的空格去掉;这个代码模板很值得学习 
        		}
        		while(!s.empty())s.pop();//初始化栈
        		Change();//将中缀表达式转为后缀表达式
        		printf("%.2f\n", Cal());//计算后缀表达式 
        	} 
        	return 0;	
        }
        

二、1队列

  • 队列的使用:#include + using namespace std;

  • 队列的定义:queue q;

  • 队列中元素的访问:只有两种——q.front();访问队列首元素;q.back()访问队列尾元素

  • 队列的函数:

    • q.push(x);//压入数值
      q.front();q.back();//访问队首和队尾
      q.pop();//队首元素出队
      q.empty();//判空,若为空,返回true,否则返回false
      q.size();//返回队列中的元素个数
      //上述操作的时间复杂度均为O(1)
      
    • 队列的清空:一种为while循环,逐个pop(),一种为重新定义一个队列。

  • 队列的常见用途:

    • 当需要实现广度优先遍历的时候,可以用queue,而不用自己去编写队列

二、2优先队列

  • 优先队列:priority_queue,在优先队列中,队首元素一定是优先级别最高的一个。使用为:#include+using namespace std;【此处写法和上面队列一致】

  • 优先队列的定义:priority_queue q;

  • 优先队列的元素访问:与队列不同的是,只有一个q.top();对队首元素进行访问,类似于栈

  • 优先队列的常用函数:

    • q.push(x);//时间复杂度为O(logn)
      q.top();
      q.pop();//令堆顶元素出队,时间复杂度为O(logn)。
      q.empty();//true为空,否则为非空
      q.size();//返回个数
      
  • 优先队列中元素优先级的设置:

    • 基本数据类型与结构体:

      //基本数据类型
      priority_queue<int,vector<int>,less<int> > q;//vector<int>是来填写元素的参数类型的,如int、char等,less<int>表示数字大的优先级高
      priority_queue<char,vector<char>,greater<char> > q;//数字小的优先级别高
      
      //结构体类型
      struct fruit{
          string name;
          int price;
      }f1,f2,f3;
      struct cmp{//优先级函数
          bool operator () (fruit f1,fruit f2){
              return f1.price>f2.price;
          }
      }
      //上述优先级函数中,bool operator (),是一个定式。f1.price>f2.price表示价格低的优先级高。因为此函数本身是重载小于符号,也就是本身定义的是数值高的优先级高,如果需要定义数值小的优先级别高,则需要用大于符号。
      priority_queue<fruit,vector<fruit>,cmp> q;
      //如果传入的数据较为庞大,如字符串或者数组类型,可以加const和&【引用】
      bool operator()(const fruit &f1,const fruit &f2){
          return f1.price>f2.price;
      }
      
    • 优先级设置与sort中cmp函数的符号相反。因为sort中return的符号就是本身的意思;而此处由于本身是数值大的优先级别高,返回的是<号,所以当数值小的优先级别高时,返回的就是>号

  • 优先队列的常见用途:

    • 解决一些贪心问题【前后联系!!】,也可以对Dijkstra算法进行优化。

三、递归、快速幂

  • 递归最重要的是递归边界和递归式,以实现将问题规模减小。

  • 递归题型:全排列【可以结合STL中的next_permutation()函数】、斐波那契数列。

  • 递归的几个片段

    • x + ( x − 1 ) + … … + y x + (x-1) + …… + y x+(x1)++y

      int f(int x, int y){
          if(x == y) return x;
          return x + f(x-1,y);
      }
      
    • x ∗ ( x − 1 ) ∗ ( x − 2 ) ∗ … … ∗ 4 x * (x-1) * (x-2) *…… * 4 x(x1)(x2)4

      int f(int x){
          if(x <= 3){
              return 1;
          }
          return x * f(x-1);//x-1在递归中会一直减下去
      }
      
    • f ( x ) = { 1 x = 1 2 x = 2 f ( x − 1 ) + 2 f ( x − 2 ) x > 2 f(x) = \begin{cases} 1 & x = 1\\ 2 & x = 2\\ f(x-1) + 2f(x-2) & x > 2 \end{cases} f(x)=12f(x1)+2f(x2)x=1x=2x>2

      int f(int x){
          if( x == 1 || x == 2){
              return x;
          }
          return f(x - 1) + 2 * f(x - 2);
      }
      
  • 快速幂的模板

    • 求a ^ b % m的值

      • 如果b为奇数:a^b = a * a ^ (b-1);
      • 如果b为偶数: a^b = a^(b/2) * a^(b/2);
      typedef long long LL;
      LL binaryPow(LL a, LL b, LL m){
          if(b == 0)return 1;//一个数的0次方为1
          if(b % 2 == 0)return a * binaryPow(a, b - 1, m) % m;//此处b % 2 == 0 可以用 if(b & 1)代替,判断b的末尾是否为1,进行了位与操作。可以加快执行速度
          else{//b为偶数
              LL mul = binaryPow(a, b / 2, m);//此处先算出单个,最后再相乘,降低时间复杂度
              return mul * mul % m;//得到结果
          }
      }
      
      • 在进入递归函数前需要进行的操作
        • 如果a的值大于等于m,可以先让a%m,再传值,减少计算量
        • 如果p == 1,则直接判断最后结果为0。

四、链表

  • new动态分配内存:int *p = new int; node *p = new node;

    搭配的delete(p)

  • 动态链表的创建、查询、元素插入、元素删除操作

    //创建结点
    struct node{
        typename data;//数据域
        node* next;//指针域
    }
    //创建链表
    node* create(int array[]){
        node* p, *pre, *head;//pre保存当前结点的前驱结点,head为头结点
        head = new node;  //创建头结点
        head -> next = NULL;//头结点不需要数据域,指针域初始化为NULL
        pre = head;
        for(int i = 0; i < 5; i++){
            p = new node;
            //将array数组中数赋值给结点作为数据域
            p->data = array[i];
            p->next = NULL;//新结点的指针域设置为NULL
            pre->next = p;
            pre = p;//把pre设置为p,作为下一个结点的前驱结点
        }
        return head;//返回头结点指针
    }
    int main(){
        int array[5] = {5, 3, 6,1, 2};
        node* L = create(array);
        L = L -> next; //从第一个结点开始有数据域
        while(L != NULL){
            printf("%d", L->data);
            L = L->next;
        }
        return 0;
    }
    
    //查找元素
    //在以head为头结点的链表上计数元素x的个数
    int search(node* head, int x){
        int count = 0;
        node* p = head -> next;//从第一个结点开始
        while(p!=NULL){
            if(p->data == x){
                count++:
            }
            p = p->next;
        }
        return count;//返回计数器
    }
    //插入元素
    //将x插入以head为头结点的链表的第pos个位置上
    void insert(node* head, int pos, int x){
        node *p = head;
        for(int i = 0; i < pos - 1; i++){//pos-1是为了到插入位置的前一个结点,这样子方便操作
            p = p->next;
        }
        node* q = new node;
        q->data = x;
        q->next = p->next;
        p->next = q;//前一个位置的结点指向新结点。
    }
    //删除元素
    //删除以head为头结点的链表中所有数据域为x的元素
    void del(node* head, int x){
        node* p = head->next;
        node* pre = head; //pre始终保存p的前驱结点的指针。
        while(p != NULL){
            if(p->data == x){
                pre->next = p->next;
                delete(p);
                p = pre -> next;
            }else{//如果数据域不是x,那么pre和p指针都往后移。这里保证pre始终是p的前驱结点的位置。
                pre = p;
                p = p->next;
            }
        }
    }
    
  • 静态链表

    • 有些问题结点的地址不是很大的时候(比如5位数的地址),就可以使用静态链表来做。静态链表的原理是hash。它不需要头结点

    • 静态链表的结点定义如下:

      struct Node{
          typename data;
          int next;//指针域,作为下标
      }node[size];
      
    • 注意:静态链表的结构体类型名和结构体变量名不能相同(像上面就是Node与node不能相同)!如果相同,sort函数会出错。

  • 题目多为静态链表,结合sort函数来使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦想总比行动多

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值